From b0c688a6c8270d6ea37bd4036c56fa25439aca36 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 27 May 2025 20:23:19 +0800 Subject: [PATCH 001/126] =?UTF-8?q?update:=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + src/CMakeLists.txt | 70 ++++++ src/CMakeLists.txt.user | 462 ++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 11 + src/mainwindow.cpp | 14 ++ src/mainwindow.h | 23 ++ src/mainwindow.ui | 31 +++ 7 files changed, 614 insertions(+) create mode 100644 .gitignore create mode 100644 src/CMakeLists.txt create mode 100644 src/CMakeLists.txt.user create mode 100644 src/main.cpp create mode 100644 src/mainwindow.cpp create mode 100644 src/mainwindow.h create mode 100644 src/mainwindow.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20e0241 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +.vscode +.cache \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..2e04149 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.16) + +project(Spark-Update-Tool VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(Spark-Update-Tool + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET Spark-Update-Tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation +else() + if(ANDROID) + add_library(Spark-Update-Tool SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") + else() + add_executable(Spark-Update-Tool + ${PROJECT_SOURCES} + ) + endif() +endif() + +target_link_libraries(Spark-Update-Tool PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.Spark-Update-Tool) +endif() +set_target_properties(Spark-Update-Tool PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS Spark-Update-Tool + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(Spark-Update-Tool) +endif() diff --git a/src/CMakeLists.txt.user b/src/CMakeLists.txt.user new file mode 100644 index 0000000..97d9b36 --- /dev/null +++ b/src/CMakeLists.txt.user @@ -0,0 +1,462 @@ + + + + + + EnvironmentId + {d06980a7-632a-4db9-8fae-55a42ba2ccc5} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 8 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 6.9.0 + Desktop Qt 6.9.0 + qt.qt6.690.linux_gcc_64_kit + 0 + 0 + 0 + + Debug + 2 + false + + -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} + 0 + /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-Debug + + + + + all + + false + + true + 构建 + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + 构建 + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + + + Release + 2 + false + + -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Release +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} + /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-Release + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + 2 + false + + -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} + /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-RelWithDebInfo + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release with Debug Information + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + 2 + false + + -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} + 0 + /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-Profile + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Profile + CMakeProjectManager.CMakeBuildConfiguration + + + MinSizeRel + 2 + false + + -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=MinSizeRel +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} + /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-MinSizeRel + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + 构建 + 构建 + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + 清除 + 清除 + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Minimum Size Release + CMakeProjectManager.CMakeBuildConfiguration + + 5 + + + 0 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + 部署 + 部署 + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph dwarf,4096 -F 250 + Spark-Update-Tool + CMakeProjectManager.CMakeRunConfiguration. + Spark-Update-Tool + false + true + true + true + /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..6395a77 --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,14 @@ +#include "mainwindow.h" +#include "./ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..f7a3da3 --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,23 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui new file mode 100644 index 0000000..c6854ca --- /dev/null +++ b/src/mainwindow.ui @@ -0,0 +1,31 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + 0 + 0 + 800 + 23 + + + + + + + + From e602dd8823e0da60536e00181577b6745a97ac90 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 28 May 2025 20:12:55 +0800 Subject: [PATCH 002/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E4=B8=BB?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 245 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 240 insertions(+), 5 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index c6854ca..d8e6412 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -6,25 +6,260 @@ 0 0 - 800 - 600 + 1440 + 858 MainWindow - + + + + + 30 + 20 + 1381 + 31 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Noto Sans Vai + 22 + + + + 软件更新中心 + + + + + + + Qt::Orientation::Horizontal + + + + 1204 + 20 + + + + + + + + + + 30 + 60 + 1376 + 746 + + + + + + 10 + 10 + 1351 + 58 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 320 + 38 + + + + + 320 + 38 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16 + 16 + + + + + 16 + 16 + + + + + + + + + + + + 200 + 38 + + + + 搜索软件... + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + 214 + 40 + + + + + 214 + 40 + + + + + + 0 + 0 + 102 + 38 + + + + + 102 + 38 + + + + + 102 + 38 + + + + + PreferNoHinting + + + + Qt::LayoutDirection::LeftToRight + + + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow + + + + 按名称 + + + + + + + 118 + 0 + 96 + 40 + + + + 更新全部 + + + + + + + + 0 0 - 800 + 1440 23 - From b8846eb9b6b49d06c48d367a2af092f492fb8819 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 28 May 2025 20:18:01 +0800 Subject: [PATCH 003/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E4=B8=BB?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 473 ++++++++++++++++++++++++---------------------- 1 file changed, 249 insertions(+), 224 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d8e6412..0e92b43 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -14,241 +14,266 @@ MainWindow - - - - 30 - 20 - 1381 - 31 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - Noto Sans Vai - 22 - + + + + + + 0 - - 软件更新中心 + + 0 - - - - - - Qt::Orientation::Horizontal + + 0 - - - 1204 - 20 - + + 0 - - - - - - - - 30 - 60 - 1376 - 746 - - - - - - 10 - 10 - 1351 - 58 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 320 - 38 - - - - - 320 - 38 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 16 - 16 - - - - - 16 - 16 - - - - - - - - - - - - 200 - 38 - - - - 搜索软件... - - - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - 214 - 40 - - - - - 214 - 40 - - - - - - 0 - 0 - 102 - 38 - - - - - 102 - 38 - - - - - 102 - 38 - - + + 0 + + + - PreferNoHinting + Noto Sans Vai + 22 - - Qt::LayoutDirection::LeftToRight - - - QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow - - - - 按名称 - - - - - - - 118 - 0 - 96 - 40 - - - 更新全部 + 软件更新中心 - - - - - + + + + + Qt::Orientation::Horizontal + + + + 1245 + 20 + + + + + + + + + + + + 1376 + 746 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 320 + 38 + + + + + 320 + 38 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16 + 16 + + + + + 16 + 16 + + + + + + + + + + + + 200 + 38 + + + + 搜索软件... + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + 214 + 40 + + + + + 214 + 40 + + + + + + 0 + 0 + 102 + 38 + + + + + 102 + 38 + + + + + 102 + 38 + + + + + PreferNoHinting + + + + Qt::LayoutDirection::LeftToRight + + + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow + + + + 按名称 + + + + + + + 118 + 0 + 96 + 40 + + + + 更新全部 + + + + + + + + + + + + 0 + 20 + + + + + + 0 + 0 + 96 + 24 + + + + 全选 + + + + + + + + + 598 + 1328 + + + + + + + + From 65172300bea60bf41011e2435bb27047af46a3da Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 28 May 2025 20:40:40 +0800 Subject: [PATCH 004/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E7=BB=88?= =?UTF-8?q?=E7=AB=AF=E8=B0=83=E7=94=A8aptss=E5=91=BD=E4=BB=A4=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E5=8F=AF=E6=9B=B4=E6=96=B0=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt | 1 + src/aptssupdater.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/aptssupdater.h | 19 +++++++++++++++++++ src/mainwindow.cpp | 14 ++++++++++++++ src/mainwindow.h | 3 ++- 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/aptssupdater.cpp create mode 100644 src/aptssupdater.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2e04149..e6f7623 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,7 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_add_executable(Spark-Update-Tool MANUAL_FINALIZATION ${PROJECT_SOURCES} + aptssupdater.h aptssupdater.cpp ) # Define target properties for Android with Qt 6 as: # set_property(TARGET Spark-Update-Tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp new file mode 100644 index 0000000..4e7fd13 --- /dev/null +++ b/src/aptssupdater.cpp @@ -0,0 +1,36 @@ +#include "aptssupdater.h" +#include +#include +#include +aptssUpdater::aptssUpdater(QWidget *parent) + : QWidget(parent) +{ + +} +QStringList aptssUpdater::getUpdateablePackages() +{ + QStringList packageDetails; + QProcess process; + process.start("bash", QStringList() << "-c" << "aptss list --upgradable"); + process.waitForFinished(); + + QString output = process.readAllStandardOutput(); + QTextStream stream(&output); + + // 跳过第一行(提示信息) + stream.readLine(); + + while (!stream.atEnd()) { + QString line = stream.readLine(); + QRegularExpression regex(R"((\S+)/\S+\s+\S+\s+\S+\s+\[可从该版本升级:(.+)\])"); + QRegularExpressionMatch match = regex.match(line); + + if (match.hasMatch()) { + QString packageName = match.captured(1); + QString updateDetails = match.captured(2); + packageDetails << QString("%1: %2").arg(packageName, updateDetails); + } + } + + return packageDetails; +} diff --git a/src/aptssupdater.h b/src/aptssupdater.h new file mode 100644 index 0000000..b79aab1 --- /dev/null +++ b/src/aptssupdater.h @@ -0,0 +1,19 @@ +#ifndef APTSSUPDATER_H +#define APTSSUPDATER_H + +#include +#include + +class aptssUpdater : public QWidget +{ + Q_OBJECT +public: + explicit aptssUpdater(QWidget *parent = nullptr); + + QStringList getUpdateablePackages(); // 查询可更新包列表及更新内容 + qint64 getTotalDownloadSize(); // 获取更新总大小预估 + +signals: +}; + +#endif // APTSSUPDATER_H \ No newline at end of file diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6395a77..815de12 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5,8 +5,22 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { + checkUpdates(); ui->setupUi(this); } +void MainWindow::checkUpdates() +{ + aptssUpdater updater; + + // 获取可更新包列表 + QStringList updateablePackages = updater.getUpdateablePackages(); + qDebug() << "可更新包列表:"; + for (const QString &package : updateablePackages) { + qDebug() << package; + } + + +} MainWindow::~MainWindow() { diff --git a/src/mainwindow.h b/src/mainwindow.h index f7a3da3..a172253 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -2,7 +2,7 @@ #define MAINWINDOW_H #include - +#include "aptssupdater.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; @@ -19,5 +19,6 @@ public: private: Ui::MainWindow *ui; + void checkUpdates(); }; #endif // MAINWINDOW_H From 17b5c9afcea6990eddadf9a6607a3b33dce2d1e2 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 28 May 2025 21:05:04 +0800 Subject: [PATCH 005/126] =?UTF-8?q?update:=E8=BE=93=E5=87=BA=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E5=8C=85=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 37 ++++++++++++++++++++++++++++++++++--- src/aptssupdater.h | 2 +- src/mainwindow.cpp | 10 ++++++---- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index 4e7fd13..de52504 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -22,15 +22,46 @@ QStringList aptssUpdater::getUpdateablePackages() while (!stream.atEnd()) { QString line = stream.readLine(); - QRegularExpression regex(R"((\S+)/\S+\s+\S+\s+\S+\s+\[可从该版本升级:(.+)\])"); + QRegularExpression regex(R"((\S+)/\S+\s+(\S+)\s+\S+\s+\[可从该版本升级:(.+)\])"); QRegularExpressionMatch match = regex.match(line); if (match.hasMatch()) { QString packageName = match.captured(1); - QString updateDetails = match.captured(2); - packageDetails << QString("%1: %2").arg(packageName, updateDetails); + QString currentVersion = match.captured(2); + QString updateVersion = match.captured(3); + packageDetails << QString("%1 (%2 -> %3)").arg(packageName, currentVersion, updateVersion); } } return packageDetails; } +QStringList aptssUpdater::getPackageSizes() +{ + QStringList packageSizes; + QProcess process; + process.start("bash", QStringList() << "-c" << "aptss upgrade --assume-no"); + + // 自动回复 "n" 以跳过交互式提示 + if (process.waitForStarted()) { + process.write("n\n"); + } + + process.waitForFinished(); + + QString output = process.readAllStandardOutput(); + QTextStream stream(&output); + + while (!stream.atEnd()) { + QString line = stream.readLine(); + QRegularExpression regex(R"((\S+)\s+\S+\s+(\d+(?:\.\d+)?(?:KiB|MiB)))"); + QRegularExpressionMatch match = regex.match(line); + + if (match.hasMatch()) { + QString packageName = match.captured(1); + QString packageSize = match.captured(2); + packageSizes << QString("%1: %2").arg(packageName, packageSize); + } + } + + return packageSizes; +} \ No newline at end of file diff --git a/src/aptssupdater.h b/src/aptssupdater.h index b79aab1..35a7061 100644 --- a/src/aptssupdater.h +++ b/src/aptssupdater.h @@ -12,7 +12,7 @@ public: QStringList getUpdateablePackages(); // 查询可更新包列表及更新内容 qint64 getTotalDownloadSize(); // 获取更新总大小预估 - + QStringList getPackageSizes(); // 获取每个包的大小 signals: }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 815de12..e6bb874 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -14,12 +14,14 @@ void MainWindow::checkUpdates() // 获取可更新包列表 QStringList updateablePackages = updater.getUpdateablePackages(); + QStringList packageSizes = updater.getPackageSizes(); + qDebug() << "可更新包列表:"; - for (const QString &package : updateablePackages) { - qDebug() << package; + for (int i = 0; i < updateablePackages.size(); ++i) { + QString packageInfo = updateablePackages.at(i); + QString packageSize = (i < packageSizes.size()) ? packageSizes.at(i) : "未知大小"; + qDebug() << QString("%1 (%2)").arg(packageInfo, packageSize); } - - } MainWindow::~MainWindow() From ddf1f7396b49121b147c3328f8bf94d98be6bd5a Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 28 May 2025 22:22:23 +0800 Subject: [PATCH 006/126] =?UTF-8?q?update:=E4=BB=8Edesktop=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E6=8F=90=E5=8F=96=E5=BA=94=E7=94=A8=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 34 ++++++++++++++++++++++++++++++++++ src/aptssupdater.h | 2 ++ src/mainwindow.cpp | 5 ++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index de52504..cab8801 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -2,11 +2,14 @@ #include #include #include +#include + aptssUpdater::aptssUpdater(QWidget *parent) : QWidget(parent) { } + QStringList aptssUpdater::getUpdateablePackages() { QStringList packageDetails; @@ -35,6 +38,7 @@ QStringList aptssUpdater::getUpdateablePackages() return packageDetails; } + QStringList aptssUpdater::getPackageSizes() { QStringList packageSizes; @@ -64,4 +68,34 @@ QStringList aptssUpdater::getPackageSizes() } return packageSizes; +} + +QStringList aptssUpdater::getDesktopAppNames() +{ + QStringList appNames; + QStringList updateablePackages = getUpdateablePackages(); + + for (const QString &packageDetail : updateablePackages) { + // 提取包名(忽略版本信息) + QString packageName = packageDetail.split(' ').first(); + QString desktopFilePath = QString("/usr/share/applications/%1.desktop").arg(packageName); + + QFile desktopFile(desktopFilePath); + if (desktopFile.exists() && desktopFile.open(QIODevice::ReadOnly)) { + QTextStream stream(&desktopFile); + while (!stream.atEnd()) { + QString line = stream.readLine(); + if (line.startsWith("Name=")) { + QString appName = line.mid(5); // 提取 Name 属性值 + appNames << appName; + break; + } + } + desktopFile.close(); + } else { + appNames << QString("未找到 .desktop 文件: %1").arg(packageName); + } + } + + return appNames; } \ No newline at end of file diff --git a/src/aptssupdater.h b/src/aptssupdater.h index 35a7061..7ff6f87 100644 --- a/src/aptssupdater.h +++ b/src/aptssupdater.h @@ -13,6 +13,8 @@ public: QStringList getUpdateablePackages(); // 查询可更新包列表及更新内容 qint64 getTotalDownloadSize(); // 获取更新总大小预估 QStringList getPackageSizes(); // 获取每个包的大小 + QStringList getDesktopAppNames(); // 获取桌面应用名称列表 + signals: }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e6bb874..3ee5562 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -15,13 +15,16 @@ void MainWindow::checkUpdates() // 获取可更新包列表 QStringList updateablePackages = updater.getUpdateablePackages(); QStringList packageSizes = updater.getPackageSizes(); - + QStringList appNames = updater.getDesktopAppNames(); qDebug() << "可更新包列表:"; for (int i = 0; i < updateablePackages.size(); ++i) { QString packageInfo = updateablePackages.at(i); QString packageSize = (i < packageSizes.size()) ? packageSizes.at(i) : "未知大小"; qDebug() << QString("%1 (%2)").arg(packageInfo, packageSize); } + for (const QString &appName : appNames) { + qDebug() << appName; + } } MainWindow::~MainWindow() From 782066cf57f347a3ac96b479f79e36db84c08b9e Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 29 May 2025 20:29:12 +0800 Subject: [PATCH 007/126] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index cab8801..e9e7eab 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -30,8 +30,8 @@ QStringList aptssUpdater::getUpdateablePackages() if (match.hasMatch()) { QString packageName = match.captured(1); - QString currentVersion = match.captured(2); - QString updateVersion = match.captured(3); + QString updateVersion = match.captured(2); + QString currentVersion = match.captured(3); packageDetails << QString("%1 (%2 -> %3)").arg(packageName, currentVersion, updateVersion); } } From fdd5fcaef14802ca92fe8a3074b5b28b3aa4035b Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 30 May 2025 21:31:12 +0800 Subject: [PATCH 008/126] =?UTF-8?q?docs:=E6=B7=BB=E5=8A=A0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- READE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 READE.md diff --git a/READE.md b/READE.md new file mode 100644 index 0000000..3a1fca0 --- /dev/null +++ b/READE.md @@ -0,0 +1,5 @@ +### 星火软件更新器 +#### 简介 + +欢迎使用星火软件更新器,你可以用这个更新器更新位于你电脑的程序。 + From 7a1eeb8d4d8435cc6908710be75cca8b2f128bc2 Mon Sep 17 00:00:00 2001 From: momen Date: Sat, 31 May 2025 13:01:56 +0800 Subject: [PATCH 009/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- READE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/READE.md b/READE.md index 3a1fca0..2e25a78 100644 --- a/READE.md +++ b/READE.md @@ -3,3 +3,6 @@ 欢迎使用星火软件更新器,你可以用这个更新器更新位于你电脑的程序。 +#### 当前支持的Linux发行版 + +#### 联系与反馈 \ No newline at end of file From 3dde50ce2783fff53a491ca5b5608dd042accb10 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 1 Jun 2025 13:11:16 +0800 Subject: [PATCH 010/126] =?UTF-8?q?update:=E5=88=9B=E5=BB=BA=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E6=A0=B7=E5=BC=8F=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 5 +++++ src/mainwindow.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3ee5562..8e4798d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -7,6 +7,11 @@ MainWindow::MainWindow(QWidget *parent) { checkUpdates(); ui->setupUi(this); +} +//初始化控件样式 +void MainWindow::initStyle() +{ + } void MainWindow::checkUpdates() { diff --git a/src/mainwindow.h b/src/mainwindow.h index a172253..361f01f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -20,5 +20,6 @@ public: private: Ui::MainWindow *ui; void checkUpdates(); + void initStyle(); }; #endif // MAINWINDOW_H From e331f8d58006153f88f3954af8996235e47f46d8 Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 2 Jun 2025 13:40:47 +0800 Subject: [PATCH 011/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8e4798d..158882c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -7,10 +7,18 @@ MainWindow::MainWindow(QWidget *parent) { checkUpdates(); ui->setupUi(this); + initStyle(); } //初始化控件样式 void MainWindow::initStyle() { + ui->plainTextEdit->setStyleSheet( + "QPlainTextEdit {" + " background-color: #FFFFFF;" + " border: 1px solid #E5E7EB;" + " border-radius: 4px;" + "}" + ); } void MainWindow::checkUpdates() From 8e9528dfa81a8d747190ab4e1eff02ccfe12a86e Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 13:54:52 +0800 Subject: [PATCH 012/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E5=AD=97?= =?UTF-8?q?=E4=BD=93=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 158882c..4322d6e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -17,8 +17,15 @@ void MainWindow::initStyle() " background-color: #FFFFFF;" " border: 1px solid #E5E7EB;" " border-radius: 4px;" + " padding-top: 8px;" // 上内边距 + " padding-bottom: 8px;" // 下内边距 + " font-size: 9px;" // 字体大小可调 + " line-height: 1.4;" // 可略调行距(非必须)" + " color: #9CA3AF;" "}" ); + ui->plainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->plainTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } void MainWindow::checkUpdates() From 6d6757154552038cc822d68aaf99ee32b675d796 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 13:56:42 +0800 Subject: [PATCH 013/126] =?UTF-8?q?update:=E6=8F=90=E5=8D=87=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4322d6e..cccc631 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -12,18 +12,19 @@ MainWindow::MainWindow(QWidget *parent) //初始化控件样式 void MainWindow::initStyle() { - ui->plainTextEdit->setStyleSheet( - "QPlainTextEdit {" - " background-color: #FFFFFF;" - " border: 1px solid #E5E7EB;" - " border-radius: 4px;" - " padding-top: 8px;" // 上内边距 - " padding-bottom: 8px;" // 下内边距 - " font-size: 9px;" // 字体大小可调 - " line-height: 1.4;" // 可略调行距(非必须)" - " color: #9CA3AF;" - "}" - ); + ui->plainTextEdit->setStyleSheet(R"( + QPlainTextEdit { + background-color: #FFFFFF; + border: 1px solid #E5E7EB; + border-radius: 4px; + padding-top: 8px; + padding-bottom: 8px; + font-size: 9px; + line-height: 1.4; + color: #9CA3AF; + } + )"); + ui->plainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->plainTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); From e62451ff89e1606bb4b0f1c4d392e00c030a9048 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 13:58:17 +0800 Subject: [PATCH 014/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E7=B1=BB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 0e92b43..526af36 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -181,7 +181,7 @@ 40 - + 0 @@ -219,7 +219,7 @@ - + 118 From aa0bcff0769dd30f9a53ef95ebe1cc0e36db2a80 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 14:07:09 +0800 Subject: [PATCH 015/126] =?UTF-8?q?update:=E6=9B=B4=E6=94=B9=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E6=A1=86=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cccc631..51861c6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -12,6 +12,7 @@ MainWindow::MainWindow(QWidget *parent) //初始化控件样式 void MainWindow::initStyle() { + //查询框样式 ui->plainTextEdit->setStyleSheet(R"( QPlainTextEdit { background-color: #FFFFFF; @@ -28,6 +29,31 @@ void MainWindow::initStyle() ui->plainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->plainTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + //筛选框样式 + ui->FilterComboBox->setStyleSheet(R"( + QComboBox { + background-color: #FFFFFF; + border: 1px solid #E5E7EB; + border-radius: 4px; + color: #4B5563; + padding: 4px 8px; + } + + QComboBox::drop-down { + border: none; + width: 20px; + } + + QComboBox QAbstractItemView { + background-color: #FFFFFF; + border: 1px solid #E5E7EB; + color: #4B5563; + selection-background-color: #F3F4F6; + selection-color: #111827; + } +)"); + + } void MainWindow::checkUpdates() { From c54785224c782ed2071be8906090f3aabe280285 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 14:13:58 +0800 Subject: [PATCH 016/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 20e0241..75ea44d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build .vscode -.cache \ No newline at end of file +.cache +CMakeLists.txt.user +CMakeLists.txt.user.* From 059ac5b27022429656548104feec0d2fd85e7622 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 14:17:22 +0800 Subject: [PATCH 017/126] =?UTF-8?q?update:=E6=B7=BB=E5=8A=A0=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E6=8C=89=E9=92=AEsvg=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt | 1 + src/icons.qrc | 1 + src/resources/down_arrow_4B5563.svg | 1 + 3 files changed, 3 insertions(+) create mode 100644 src/icons.qrc create mode 100644 src/resources/down_arrow_4B5563.svg diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e6f7623..b41f81c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,6 +24,7 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) MANUAL_FINALIZATION ${PROJECT_SOURCES} aptssupdater.h aptssupdater.cpp + icons.qrc ) # Define target properties for Android with Qt 6 as: # set_property(TARGET Spark-Update-Tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR diff --git a/src/icons.qrc b/src/icons.qrc new file mode 100644 index 0000000..7646d2b --- /dev/null +++ b/src/icons.qrc @@ -0,0 +1 @@ + diff --git a/src/resources/down_arrow_4B5563.svg b/src/resources/down_arrow_4B5563.svg new file mode 100644 index 0000000..4989662 --- /dev/null +++ b/src/resources/down_arrow_4B5563.svg @@ -0,0 +1 @@ + \ No newline at end of file From 623db2ea768f371965dd07850d3afae3f77bf8f9 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 14:20:29 +0800 Subject: [PATCH 018/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E6=A1=86=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/icons.qrc | 6 +++++- src/mainwindow.cpp | 5 +++++ src/resources/{down_arrow_4B5563.svg => down_arrow.svg} | 0 3 files changed, 10 insertions(+), 1 deletion(-) rename src/resources/{down_arrow_4B5563.svg => down_arrow.svg} (100%) diff --git a/src/icons.qrc b/src/icons.qrc index 7646d2b..41d5fd4 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -1 +1,5 @@ - + + + resources/down_arrow.svg + + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 51861c6..c0cf3f2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -44,6 +44,11 @@ void MainWindow::initStyle() width: 20px; } + QComboBox::down-arrow { + image: url(:/resources/down_arrow.svg); + width: 12px; + height: 16px; + } QComboBox QAbstractItemView { background-color: #FFFFFF; border: 1px solid #E5E7EB; diff --git a/src/resources/down_arrow_4B5563.svg b/src/resources/down_arrow.svg similarity index 100% rename from src/resources/down_arrow_4B5563.svg rename to src/resources/down_arrow.svg From 48257722fb228e25ea222efb01b70e47c57dfb49 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 14:50:47 +0800 Subject: [PATCH 019/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0"=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=85=A8=E9=83=A8"=E6=8C=89=E9=92=AE=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 75 +++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c0cf3f2..4ecb379 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -31,32 +31,59 @@ void MainWindow::initStyle() //筛选框样式 ui->FilterComboBox->setStyleSheet(R"( - QComboBox { - background-color: #FFFFFF; - border: 1px solid #E5E7EB; - border-radius: 4px; - color: #4B5563; - padding: 4px 8px; - } + QComboBox { + background-color: #FFFFFF; + border: 1px solid #E5E7EB; + border-radius: 4px; + color: #4B5563; + padding: 4px 8px; + } - QComboBox::drop-down { - border: none; - width: 20px; - } + QComboBox::drop-down { + border: none; + width: 20px; + } + + QComboBox::down-arrow { + image: url(:/resources/down_arrow.svg); + width: 12px; + height: 16px; + } + QComboBox QAbstractItemView { + background-color: #FFFFFF; + border: 1px solid #E5E7EB; + color: #4B5563; + selection-background-color: #F3F4F6; + selection-color: #111827; + } + )"); + + //更新软件按钮样式 + ui->updatePushButton->setStyleSheet(R"( + QPushButton { + background-color: #2563EB; + color: #FFFFFF; + border: none; + border-radius: 4px; + font-size: 14px; + padding: 6px 12px; + text-align: center; + } + + QPushButton:hover { + background-color: #1D4ED8; /* 深一点的 hover 效果,可选 */ + } + + QPushButton:pressed { + background-color: #1E40AF; /* 按下效果,可选 */ + } + + QPushButton:disabled { + background-color: #A5B4FC; + color: #F9FAFB; + } + )"); - QComboBox::down-arrow { - image: url(:/resources/down_arrow.svg); - width: 12px; - height: 16px; - } - QComboBox QAbstractItemView { - background-color: #FFFFFF; - border: 1px solid #E5E7EB; - color: #4B5563; - selection-background-color: #F3F4F6; - selection-color: #111827; - } -)"); } From a9c137110c5cc8ce0030c2535d55c3b715e8cfce Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 14:56:05 +0800 Subject: [PATCH 020/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 11 ++++++++++- src/mainwindow.ui | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4ecb379..f940bb2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5,7 +5,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { - checkUpdates(); + // checkUpdates(); ui->setupUi(this); initStyle(); } @@ -84,7 +84,16 @@ void MainWindow::initStyle() } )"); + //设置背景填充颜色 + ui->backgroundWidget->setStyleSheet(R"( + QWidget { + background-color: #FFFFFF; + border-radius: 12px; + } + )"); + //设置主背景颜色 + this->setStyleSheet("background-color: #F8FAFC;"); } void MainWindow::checkUpdates() diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 526af36..49462f5 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -63,7 +63,7 @@ - + 1376 From b35ac593cb3e142d3df846c702c652e1e0cb97f6 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 3 Jun 2025 15:03:48 +0800 Subject: [PATCH 021/126] =?UTF-8?q?fix:=E6=90=9C=E7=B4=A2=E6=A1=86?= =?UTF-8?q?=E6=94=B9=E4=B8=BAplaceholdertext=E6=98=BE=E7=A4=BA=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 49462f5..80ac609 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -147,6 +147,12 @@ + + + + 81.000000000000000 + + 搜索软件... From f7fcf5ef1157030430eb1487828e642cd29e4556 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 4 Jun 2025 08:51:36 +0800 Subject: [PATCH 022/126] =?UTF-8?q?fix:=E5=88=A0=E9=99=A4CmakeLists.txt.us?= =?UTF-8?q?er=E7=9A=84=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt.user | 462 ---------------------------------------- 1 file changed, 462 deletions(-) delete mode 100644 src/CMakeLists.txt.user diff --git a/src/CMakeLists.txt.user b/src/CMakeLists.txt.user deleted file mode 100644 index 97d9b36..0000000 --- a/src/CMakeLists.txt.user +++ /dev/null @@ -1,462 +0,0 @@ - - - - - - EnvironmentId - {d06980a7-632a-4db9-8fae-55a42ba2ccc5} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - true - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 0 - 80 - true - true - 1 - 0 - false - true - false - 2 - true - true - 0 - 8 - true - false - 1 - true - true - true - *.md, *.MD, Makefile - false - true - true - - - - ProjectExplorer.Project.PluginSettings - - - true - false - true - true - true - true - - false - - - 0 - true - - true - true - Builtin.DefaultTidyAndClazy - 8 - true - - - - true - - - - - ProjectExplorer.Project.Target.0 - - Desktop - Desktop Qt 6.9.0 - Desktop Qt 6.9.0 - qt.qt6.690.linux_gcc_64_kit - 0 - 0 - 0 - - Debug - 2 - false - - -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=Debug --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} --DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} - 0 - /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-Debug - - - - - all - - false - - true - 构建 - CMakeProjectManager.MakeStep - - 1 - 构建 - 构建 - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - 构建 - CMakeProjectManager.MakeStep - - 1 - 清除 - 清除 - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Debug - CMakeProjectManager.CMakeBuildConfiguration - - - Release - 2 - false - - -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=Release --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} --DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} - /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-Release - - - - - all - - false - - true - CMakeProjectManager.MakeStep - - 1 - 构建 - 构建 - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - CMakeProjectManager.MakeStep - - 1 - 清除 - 清除 - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Release - CMakeProjectManager.CMakeBuildConfiguration - - - RelWithDebInfo - 2 - false - - -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} --DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} - /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-RelWithDebInfo - - - - - all - - false - - true - CMakeProjectManager.MakeStep - - 1 - 构建 - 构建 - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - CMakeProjectManager.MakeStep - - 1 - 清除 - 清除 - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Release with Debug Information - CMakeProjectManager.CMakeBuildConfiguration - - - RelWithDebInfo - 2 - false - - -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} --DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} - 0 - /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-Profile - - - - - all - - false - - true - CMakeProjectManager.MakeStep - - 1 - 构建 - 构建 - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - CMakeProjectManager.MakeStep - - 1 - 清除 - 清除 - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Profile - CMakeProjectManager.CMakeBuildConfiguration - - - MinSizeRel - 2 - false - - -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON --DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} --DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=MinSizeRel --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} --DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} - /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-MinSizeRel - - - - - all - - false - - true - CMakeProjectManager.MakeStep - - 1 - 构建 - 构建 - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - CMakeProjectManager.MakeStep - - 1 - 清除 - 清除 - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Minimum Size Release - CMakeProjectManager.CMakeBuildConfiguration - - 5 - - - 0 - 部署 - 部署 - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ProjectExplorer.DefaultDeployConfiguration - - - - - - - - - false - - true - ApplicationManagerPlugin.Deploy.CMakePackageStep - - - install-package --acknowledge - true - Install Application Manager package - ApplicationManagerPlugin.Deploy.InstallPackageStep - - - - - - - - 2 - 部署 - 部署 - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ApplicationManagerPlugin.Deploy.Configuration - - 2 - - true - true - 0 - true - - 2 - - false - -e cpu-cycles --call-graph dwarf,4096 -F 250 - Spark-Update-Tool - CMakeProjectManager.CMakeRunConfiguration. - Spark-Update-Tool - false - true - true - true - /home/momen/Spark-Update-Tool/build/Desktop_Qt_6_9_0-Debug - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 22 - - - Version - 22 - - From 24f3be1373d0721959498afa65186c5961e7247c Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 4 Jun 2025 08:58:43 +0800 Subject: [PATCH 023/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E6=A0=87=E9=A2=98=E6=A0=8F=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f940bb2..daf51a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -12,6 +12,9 @@ MainWindow::MainWindow(QWidget *parent) //初始化控件样式 void MainWindow::initStyle() { + //设置窗口标题 + this->setWindowTitle("软件更新中心"); + //查询框样式 ui->plainTextEdit->setStyleSheet(R"( QPlainTextEdit { From 93dfdbc57ca8070068d2dfebd3c81810fcbd5254 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 4 Jun 2025 09:59:47 +0800 Subject: [PATCH 024/126] =?UTF-8?q?update:=E4=BD=BF=E7=94=A8=E5=95=86?= =?UTF-8?q?=E5=BA=97=E6=BA=90=E6=9D=A5=E6=98=BE=E7=A4=BA=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=8C=E8=80=8C=E9=9D=9E=E7=9B=B4=E6=8E=A5aptss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 30 +++++++++++++++++------------- src/mainwindow.cpp | 14 ++------------ 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index e9e7eab..2286218 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -14,31 +14,35 @@ QStringList aptssUpdater::getUpdateablePackages() { QStringList packageDetails; QProcess process; - process.start("bash", QStringList() << "-c" << "aptss list --upgradable"); - process.waitForFinished(); + 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(); - QTextStream stream(&output); + QStringList lines = output.split('\n', Qt::SkipEmptyParts); - // 跳过第一行(提示信息) - stream.readLine(); + for (const QString &line : lines) { + // 示例行格式: + // code/unknown 1.100.3-1748872405 amd64 [upgradable from: 1.100.2-1747260578] - while (!stream.atEnd()) { - QString line = stream.readLine(); - QRegularExpression regex(R"((\S+)/\S+\s+(\S+)\s+\S+\s+\[可从该版本升级:(.+)\])"); + QRegularExpression regex(R"(([\w\-\+\.]+)/\S+\s+([^\s]+)\s+\S+\s+\[upgradable from: ([^\]]+)\])"); QRegularExpressionMatch match = regex.match(line); - if (match.hasMatch()) { - QString packageName = match.captured(1); - QString updateVersion = match.captured(2); - QString currentVersion = match.captured(3); - packageDetails << QString("%1 (%2 -> %3)").arg(packageName, currentVersion, updateVersion); + QString name = match.captured(1); + QString newVersion = match.captured(2); + QString oldVersion = match.captured(3); + packageDetails << QString("%1: %2 → %3").arg(name, oldVersion, newVersion); } } return packageDetails; } + QStringList aptssUpdater::getPackageSizes() { QStringList packageSizes; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index daf51a6..8444a08 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5,7 +5,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { - // checkUpdates(); + checkUpdates(); ui->setupUi(this); initStyle(); } @@ -105,17 +105,7 @@ void MainWindow::checkUpdates() // 获取可更新包列表 QStringList updateablePackages = updater.getUpdateablePackages(); - QStringList packageSizes = updater.getPackageSizes(); - QStringList appNames = updater.getDesktopAppNames(); - qDebug() << "可更新包列表:"; - for (int i = 0; i < updateablePackages.size(); ++i) { - QString packageInfo = updateablePackages.at(i); - QString packageSize = (i < packageSizes.size()) ? packageSizes.at(i) : "未知大小"; - qDebug() << QString("%1 (%2)").arg(packageInfo, packageSize); - } - for (const QString &appName : appNames) { - qDebug() << appName; - } + qDebug()< Date: Wed, 4 Jun 2025 14:55:52 +0800 Subject: [PATCH 025/126] =?UTF-8?q?update:=E5=B0=86=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=88=B0=E5=BE=85=E6=9B=B4=E6=96=B0=E7=9A=84=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E6=94=BE=E5=88=B0/tmp=E4=B8=8B=E4=B8=B4=E6=97=B6=E5=AD=98?= =?UTF-8?q?=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 88 +++++++++++++------------------------------- src/aptssupdater.h | 4 +- 2 files changed, 28 insertions(+), 64 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index 2286218..65e9cf7 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -25,18 +25,33 @@ QStringList aptssUpdater::getUpdateablePackages() QString output = process.readAllStandardOutput(); QStringList lines = output.split('\n', Qt::SkipEmptyParts); - for (const QString &line : lines) { - // 示例行格式: - // code/unknown 1.100.3-1748872405 amd64 [upgradable from: 1.100.2-1747260578] - - 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); - packageDetails << QString("%1: %2 → %3").arg(name, oldVersion, newVersion); + // 创建临时文件 + 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); + + // 写入内存列表 + 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; @@ -45,61 +60,10 @@ QStringList aptssUpdater::getUpdateablePackages() QStringList aptssUpdater::getPackageSizes() { - QStringList packageSizes; - QProcess process; - process.start("bash", QStringList() << "-c" << "aptss upgrade --assume-no"); - // 自动回复 "n" 以跳过交互式提示 - if (process.waitForStarted()) { - process.write("n\n"); - } - - process.waitForFinished(); - - QString output = process.readAllStandardOutput(); - QTextStream stream(&output); - - while (!stream.atEnd()) { - QString line = stream.readLine(); - QRegularExpression regex(R"((\S+)\s+\S+\s+(\d+(?:\.\d+)?(?:KiB|MiB)))"); - QRegularExpressionMatch match = regex.match(line); - - if (match.hasMatch()) { - QString packageName = match.captured(1); - QString packageSize = match.captured(2); - packageSizes << QString("%1: %2").arg(packageName, packageSize); - } - } - - return packageSizes; } QStringList aptssUpdater::getDesktopAppNames() { - QStringList appNames; - QStringList updateablePackages = getUpdateablePackages(); - for (const QString &packageDetail : updateablePackages) { - // 提取包名(忽略版本信息) - QString packageName = packageDetail.split(' ').first(); - QString desktopFilePath = QString("/usr/share/applications/%1.desktop").arg(packageName); - - QFile desktopFile(desktopFilePath); - if (desktopFile.exists() && desktopFile.open(QIODevice::ReadOnly)) { - QTextStream stream(&desktopFile); - while (!stream.atEnd()) { - QString line = stream.readLine(); - if (line.startsWith("Name=")) { - QString appName = line.mid(5); // 提取 Name 属性值 - appNames << appName; - break; - } - } - desktopFile.close(); - } else { - appNames << QString("未找到 .desktop 文件: %1").arg(packageName); - } - } - - return appNames; } \ No newline at end of file diff --git a/src/aptssupdater.h b/src/aptssupdater.h index 7ff6f87..5a16823 100644 --- a/src/aptssupdater.h +++ b/src/aptssupdater.h @@ -3,7 +3,7 @@ #include #include - +#include class aptssUpdater : public QWidget { Q_OBJECT @@ -14,7 +14,7 @@ public: qint64 getTotalDownloadSize(); // 获取更新总大小预估 QStringList getPackageSizes(); // 获取每个包的大小 QStringList getDesktopAppNames(); // 获取桌面应用名称列表 - + QString m_tempFilePath; signals: }; From 0d1f23e017075765f10fad4d64ec01fbc9c5ddf4 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 4 Jun 2025 21:09:38 +0800 Subject: [PATCH 026/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0markdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- READE.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/READE.md b/READE.md index 2e25a78..3259e2d 100644 --- a/READE.md +++ b/READE.md @@ -4,5 +4,17 @@ 欢迎使用星火软件更新器,你可以用这个更新器更新位于你电脑的程序。 #### 当前支持的Linux发行版 +- [x] GXDE +- [x] Ubuntu +- [ ] deepin +- [ ] Kylin -#### 联系与反馈 \ No newline at end of file +#### 功能清单 +|功能模块|描述| +|--|--| +| 应用名识别|采用ss-do-upgrade.sh部分代码| +|应用包大小识别|dkpg完成| +|获取应用icon|使用QDesktopServices完成| +|支持ACE| | + +#### 联系与反馈 From 8ff21d81c4caa188c7bda81453117d901c84ab85 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 5 Jun 2025 08:54:56 +0800 Subject: [PATCH 027/126] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9E=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E8=BD=AF=E4=BB=B6=E5=90=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++++ src/aptssupdater.h | 7 ++++ src/mainwindow.cpp | 5 ++- 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index 65e9cf7..3cdfe80 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -65,5 +65,94 @@ QStringList aptssUpdater::getPackageSizes() QStringList aptssUpdater::getDesktopAppNames() { + QStringList appNames; + QProcess dpkgProcess; + + // 获取当前系统语言环境 + QString lang = QLocale().name().replace("_", "-"); + // 遍历所有可更新包(复用已有的临时文件数据) + QStringList packages = getUpdateablePackages(); + + foreach (const QString &package, packages) { + QString packageName = package.split(":")[0]; + + // 获取包文件列表 + dpkgProcess.start("dpkg", QStringList() << "-L" << packageName); + dpkgProcess.waitForFinished(); + QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', Qt::SkipEmptyParts); + + // 检查常规应用目录 + checkDesktopFiles(files.filter("/usr/share/applications/"), + appNames, + lang, + packageName); // 新增包名参数 + + // 检查特殊目录(/opt/apps) + checkDesktopFiles(files.filter(QRegularExpression(QString("/opt/apps/%1/entries/applications").arg(packageName))), + appNames, + lang, + packageName); // 新增包名参数 + + } + + return appNames; +} + +bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QStringList &appNames, const QString &lang, const QString &packageName) +{ + bool hasFoundName = false; + 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 name; + + 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 (name.isEmpty()) { + QRegularExpressionMatch match = nameRe.match(line); + if (match.hasMatch()) { + name = match.captured(1); + continue; + } + + // 匹配原始名称 + match = nameOrigRe.match(line); + if (match.hasMatch()) { + name = match.captured(1); + } + } + } + + if (!skip && !name.isEmpty() && !appNames.contains(name)) { + appNames << name; + hasFoundName = true; // 标记已找到有效名称 + } + } + + // 如果没有找到任何名称,则使用包名 + if (!hasFoundName && !appNames.contains(packageName)) { + appNames << packageName; + } + + return hasFoundName; } \ No newline at end of file diff --git a/src/aptssupdater.h b/src/aptssupdater.h index 5a16823..003160b 100644 --- a/src/aptssupdater.h +++ b/src/aptssupdater.h @@ -4,6 +4,7 @@ #include #include #include +#include class aptssUpdater : public QWidget { Q_OBJECT @@ -16,6 +17,12 @@ public: QStringList getDesktopAppNames(); // 获取桌面应用名称列表 QString m_tempFilePath; signals: +private: + bool checkDesktopFiles(const QStringList &desktopFiles, + QStringList &appNames, + const QString &lang, + const QString &packageName); // 新增包名参数 + }; #endif // APTSSUPDATER_H \ No newline at end of file diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8444a08..59299fb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -104,8 +104,9 @@ void MainWindow::checkUpdates() aptssUpdater updater; // 获取可更新包列表 - QStringList updateablePackages = updater.getUpdateablePackages(); - qDebug()< Date: Thu, 5 Jun 2025 09:09:57 +0800 Subject: [PATCH 028/126] =?UTF-8?q?fix:=E4=B8=80=E5=8C=85=E5=A4=9Adesktop?= =?UTF-8?q?=E5=8F=AA=E5=AF=B9=E5=BA=94=E6=9C=80=E5=90=8E=E4=B8=80=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index 3cdfe80..3459724 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -95,13 +95,14 @@ QStringList aptssUpdater::getDesktopAppNames() packageName); // 新增包名参数 } - + qDebug()<< "应用名称列表:" << appNames; return appNames; } bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QStringList &appNames, const QString &lang, const QString &packageName) { bool hasFoundName = false; + QString lastValidName; QRegularExpression noDisplayRe("^NoDisplay=(true|True)"); QRegularExpression nameRe("^Name\\[?" + lang + "?\\]?=(.*)"); QRegularExpression nameOrigRe("^Name=(.*)"); @@ -113,7 +114,7 @@ bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QStringLis if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) continue; bool skip = false; - QString name; + QString currentName; QTextStream in(&file); while (!in.atEnd()) { @@ -128,31 +129,37 @@ bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QStringLis } // 优先匹配本地化名称 - if (name.isEmpty()) { + if (currentName.isEmpty()) { QRegularExpressionMatch match = nameRe.match(line); if (match.hasMatch()) { - name = match.captured(1); + currentName = match.captured(1); continue; } // 匹配原始名称 match = nameOrigRe.match(line); if (match.hasMatch()) { - name = match.captured(1); + currentName = match.captured(1); } } } - if (!skip && !name.isEmpty() && !appNames.contains(name)) { - appNames << name; - hasFoundName = true; // 标记已找到有效名称 + if (!skip && !currentName.isEmpty()) { + lastValidName = currentName; // 只更新最后一个有效名称 } } - // 如果没有找到任何名称,则使用包名 - if (!hasFoundName && !appNames.contains(packageName)) { + // 处理最终的有效名称 + if (!lastValidName.isEmpty()) { + if (!appNames.contains(lastValidName)) { + appNames << lastValidName; + } + return true; + } + + // 回退到包名 + if (!appNames.contains(packageName)) { appNames << packageName; } - - return hasFoundName; + return false; } \ No newline at end of file From d454a586715d3fd86dc673ec494d9a5d12414216 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 5 Jun 2025 10:17:06 +0800 Subject: [PATCH 029/126] =?UTF-8?q?fix:=E4=BF=AE=E6=AD=A3=E4=BA=86?= =?UTF-8?q?=E6=97=A0=E8=BD=AF=E4=BB=B6=E5=90=8D=E6=97=B6=EF=BC=8C=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E5=90=8D=E5=86=85=E5=AE=B9=E4=B8=BA=E7=A9=BA=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 48 ++++++++++++++++++++++++++------------------ src/aptssupdater.h | 6 +----- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index 3459724..c671e1a 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -76,32 +76,44 @@ QStringList aptssUpdater::getDesktopAppNames() 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); - // 检查常规应用目录 - checkDesktopFiles(files.filter("/usr/share/applications/"), - appNames, - lang, - packageName); // 新增包名参数 + // 先检查常规应用目录 + QStringList regularDesktopFiles = files.filter("/usr/share/applications/"); + QString regularAppName; + if (!regularDesktopFiles.isEmpty()) { + checkDesktopFiles(regularDesktopFiles, regularAppName, lang, packageName); + } - // 检查特殊目录(/opt/apps) - checkDesktopFiles(files.filter(QRegularExpression(QString("/opt/apps/%1/entries/applications").arg(packageName))), - appNames, - 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, QStringList &appNames, const QString &lang, const QString &packageName) + +bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName) { - bool hasFoundName = false; QString lastValidName; QRegularExpression noDisplayRe("^NoDisplay=(true|True)"); QRegularExpression nameRe("^Name\\[?" + lang + "?\\]?=(.*)"); @@ -145,21 +157,17 @@ bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QStringLis } if (!skip && !currentName.isEmpty()) { - lastValidName = currentName; // 只更新最后一个有效名称 + lastValidName = currentName; } } // 处理最终的有效名称 if (!lastValidName.isEmpty()) { - if (!appNames.contains(lastValidName)) { - appNames << lastValidName; - } + appName = lastValidName; // 直接赋值而不是使用<< return true; } // 回退到包名 - if (!appNames.contains(packageName)) { - appNames << packageName; - } + appName = packageName; return false; -} \ No newline at end of file +} diff --git a/src/aptssupdater.h b/src/aptssupdater.h index 003160b..99be3b1 100644 --- a/src/aptssupdater.h +++ b/src/aptssupdater.h @@ -18,11 +18,7 @@ public: QString m_tempFilePath; signals: private: - bool checkDesktopFiles(const QStringList &desktopFiles, - QStringList &appNames, - const QString &lang, - const QString &packageName); // 新增包名参数 - +bool checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName); }; #endif // APTSSUPDATER_H \ No newline at end of file From 42b25b33dc28a0d99275b6aa9ad4d791541079d7 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 5 Jun 2025 10:41:04 +0800 Subject: [PATCH 030/126] =?UTF-8?q?feat:=E5=BA=94=E7=94=A8=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E7=9B=B8=E5=90=8C=E6=97=B6=E5=88=99=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index c671e1a..5a464dd 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -39,6 +39,12 @@ QStringList aptssUpdater::getUpdateablePackages() 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); From 4a846b825c75a2f29fb0b6a83d16af9e8c6b06fd Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 5 Jun 2025 14:25:31 +0800 Subject: [PATCH 031/126] =?UTF-8?q?feat:=E8=8E=B7=E5=8F=96=E5=8C=85?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++ src/mainwindow.cpp | 1 + 2 files changed, 48 insertions(+) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index 5a464dd..cb08da0 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -3,6 +3,7 @@ #include #include #include +#include aptssUpdater::aptssUpdater(QWidget *parent) : QWidget(parent) @@ -66,9 +67,55 @@ QStringList aptssUpdater::getUpdateablePackages() QStringList aptssUpdater::getPackageSizes() { + QStringList packageSizes; + QProcess process; + // 使用绝对路径 + 自动输入N跳过交互 + QString command = R"(echo N | /opt/durapps/spark-store/bin/aptss upgrade --assume-no)"; + + process.start("bash", QStringList() << "-c" << command); + if (!process.waitForFinished()) { + qWarning() << "获取包大小失败:进程未完成"; + return packageSizes; + } + + QString output = process.readAllStandardOutput() + process.readAllStandardError(); + + // 清理颜色控制字符(ANSI 转义序列) + output.remove(QRegularExpression(R"(\x1B\[[0-9;]*[a-zA-Z])")); // \x1B is ESC + + QStringList lines = output.split('\n', Qt::SkipEmptyParts); + + // 从 getUpdateablePackages 获取包名 + QStringList updateablePackages; + for (const QString &pkgInfo : getUpdateablePackages()) { + QString pkgName = pkgInfo.section(":", 0, 0).trimmed(); + updateablePackages << pkgName; + } + + // 匹配 [apt-fast xx:xx:xx] 行,提取内容 + QRegularExpression sizeLineRegex(R"(\[apt-fast [^\]]+\](\S+)\s+\S+\s+(\d+(\.\d+)?[KMG]?iB))"); + + for (const QString &line : lines) { + QRegularExpressionMatch match = sizeLineRegex.match(line.trimmed()); + if (match.hasMatch()) { + QString name = match.captured(1); + QString size = match.captured(2); + + if (updateablePackages.contains(name)) { + packageSizes << QString("%1: %2").arg(name, size); + } + } + } + + qDebug() << "包大小列表:" << packageSizes; + return packageSizes; } + + + + QStringList aptssUpdater::getDesktopAppNames() { QStringList appNames; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 59299fb..2aba374 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -106,6 +106,7 @@ void MainWindow::checkUpdates() // 获取可更新包列表 // QStringList updateablePackages = updater.getUpdateablePackages(); QStringList appName = updater.getDesktopAppNames(); + updater.getPackageSizes(); } From 86b57ecdc1854df6dda097c95d0728ecd6a4f157 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 5 Jun 2025 14:36:32 +0800 Subject: [PATCH 032/126] =?UTF-8?q?fix:getUpdateablePackages()=E8=A2=AB?= =?UTF-8?q?=E5=A4=9A=E6=AC=A1=E8=B0=83=E7=94=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 8 ++++---- src/aptssupdater.h | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index cb08da0..5f8c2aa 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -8,7 +8,7 @@ aptssUpdater::aptssUpdater(QWidget *parent) : QWidget(parent) { - + packageName = getUpdateablePackages(); } QStringList aptssUpdater::getUpdateablePackages() @@ -86,9 +86,9 @@ QStringList aptssUpdater::getPackageSizes() QStringList lines = output.split('\n', Qt::SkipEmptyParts); - // 从 getUpdateablePackages 获取包名 + // 从 packageName 获取包名 QStringList updateablePackages; - for (const QString &pkgInfo : getUpdateablePackages()) { + for (const QString &pkgInfo : packageName) { QString pkgName = pkgInfo.section(":", 0, 0).trimmed(); updateablePackages << pkgName; } @@ -125,7 +125,7 @@ QStringList aptssUpdater::getDesktopAppNames() QString lang = QLocale().name().replace("_", "-"); // 遍历所有可更新包(复用已有的临时文件数据) - QStringList packages = getUpdateablePackages(); + QStringList packages = packageName; foreach (const QString &package, packages) { QString packageName = package.split(":")[0]; diff --git a/src/aptssupdater.h b/src/aptssupdater.h index 99be3b1..3345d2d 100644 --- a/src/aptssupdater.h +++ b/src/aptssupdater.h @@ -18,7 +18,8 @@ public: QString m_tempFilePath; signals: private: -bool checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName); + bool checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName); + QStringList packageName; }; #endif // APTSSUPDATER_H \ No newline at end of file From 0d12df77cc7440ff9839f7b4fe1a9ce41db62be0 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 5 Jun 2025 14:44:57 +0800 Subject: [PATCH 033/126] =?UTF-8?q?chore:=E5=A2=9E=E5=8A=A0=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=8C=85=E5=9B=BE=E6=A0=87=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 5 +++++ src/aptssupdater.h | 2 +- src/mainwindow.cpp | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index 5f8c2aa..c32271c 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -224,3 +224,8 @@ bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QString &a appName = packageName; return false; } + +QStringList aptssUpdater::getPackageIcons() +{ + +} diff --git a/src/aptssupdater.h b/src/aptssupdater.h index 3345d2d..fbf5b27 100644 --- a/src/aptssupdater.h +++ b/src/aptssupdater.h @@ -12,9 +12,9 @@ public: explicit aptssUpdater(QWidget *parent = nullptr); QStringList getUpdateablePackages(); // 查询可更新包列表及更新内容 - qint64 getTotalDownloadSize(); // 获取更新总大小预估 QStringList getPackageSizes(); // 获取每个包的大小 QStringList getDesktopAppNames(); // 获取桌面应用名称列表 + QStringList getPackageIcons(); // 获取包图标列表 QString m_tempFilePath; signals: private: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2aba374..87f4987 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -107,6 +107,7 @@ void MainWindow::checkUpdates() // QStringList updateablePackages = updater.getUpdateablePackages(); QStringList appName = updater.getDesktopAppNames(); updater.getPackageSizes(); + updater.getPackageIcons(); } From 4e1b04bf6eaf17ac25aa732fcc39ecadb5b442ae Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 5 Jun 2025 15:19:37 +0800 Subject: [PATCH 034/126] =?UTF-8?q?chore:=E6=B7=BB=E5=8A=A0=E6=97=A0?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E5=BA=94=E7=94=A8=E7=9A=84=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/icons.qrc | 1 + src/resources/default_icon.svg | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/resources/default_icon.svg diff --git a/src/icons.qrc b/src/icons.qrc index 41d5fd4..cce5944 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -1,5 +1,6 @@ resources/down_arrow.svg + resources/default_icon.svg diff --git a/src/resources/default_icon.svg b/src/resources/default_icon.svg new file mode 100644 index 0000000..7c9cb42 --- /dev/null +++ b/src/resources/default_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file From f72ab76e2c17c90a3aefd2bada9adafc946a9f71 Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 6 Jun 2025 08:35:26 +0800 Subject: [PATCH 035/126] =?UTF-8?q?feat:=E6=8B=BF=E5=88=B0=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=AF=B9=E5=BA=94=E7=9A=84=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index c32271c..d3378ea 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -227,5 +227,75 @@ bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QString &a 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; } + From 051b04bf0b058094f2e57dd9d88761be6080e949 Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 6 Jun 2025 09:03:15 +0800 Subject: [PATCH 036/126] =?UTF-8?q?chore:=E8=BE=93=E5=87=BA=E5=8C=85?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 87 ++++++++++++++++++++++++++++++++++++++++++++ src/aptssupdater.h | 3 ++ src/mainwindow.cpp | 4 +- 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index d3378ea..e14690b 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -299,3 +299,90 @@ QStringList aptssUpdater::getPackageIcons() return packageIcons; } + +QJsonArray aptssUpdater::getUpdateInfoAsJson() +{ + QJsonArray jsonArray; + + // 获取所有需要的信息 + QStringList packages = getUpdateablePackages(); + QStringList sizes = getPackageSizes(); + QStringList names = getDesktopAppNames(); + QStringList icons = getPackageIcons(); + + // 创建包名到各种信息的映射 + QHash> packageInfo; + + // 解析包版本信息 + for (const QString &pkg : packages) { + 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]; + packageInfo[packageName]["size"] = parts[1]; + } + } + + // 解析应用名称信息 + 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"]; + } + + jsonArray.append(jsonObj); + } + qDebug()< #include #include +#include +#include class aptssUpdater : public QWidget { Q_OBJECT @@ -15,6 +17,7 @@ public: QStringList getPackageSizes(); // 获取每个包的大小 QStringList getDesktopAppNames(); // 获取桌面应用名称列表 QStringList getPackageIcons(); // 获取包图标列表 + QJsonArray getUpdateInfoAsJson(); // 获取更新信息的 JSON 格式 QString m_tempFilePath; signals: private: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 87f4987..a8deca0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -105,9 +105,7 @@ void MainWindow::checkUpdates() // 获取可更新包列表 // QStringList updateablePackages = updater.getUpdateablePackages(); - QStringList appName = updater.getDesktopAppNames(); - updater.getPackageSizes(); - updater.getPackageIcons(); + updater.getUpdateInfoAsJson(); } From 064b06813546218b18d83e85bd3b5af845882207 Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 6 Jun 2025 11:05:31 +0800 Subject: [PATCH 037/126] =?UTF-8?q?feat:=E8=8E=B7=E5=8F=96=E5=88=B0?= =?UTF-8?q?=E5=8C=85json=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index e14690b..05918c0 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -305,7 +305,6 @@ QJsonArray aptssUpdater::getUpdateInfoAsJson() QJsonArray jsonArray; // 获取所有需要的信息 - QStringList packages = getUpdateablePackages(); QStringList sizes = getPackageSizes(); QStringList names = getDesktopAppNames(); QStringList icons = getPackageIcons(); @@ -314,7 +313,7 @@ QJsonArray aptssUpdater::getUpdateInfoAsJson() QHash> packageInfo; // 解析包版本信息 - for (const QString &pkg : packages) { + for (const QString &pkg : packageName) { QStringList parts = pkg.split(": "); if (parts.size() >= 2) { QString packageName = parts[0]; From 3ba3364fb969998ebaa2762e6251d3939a2f36fd Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 6 Jun 2025 13:29:35 +0800 Subject: [PATCH 038/126] =?UTF-8?q?chore:=E7=BB=98=E5=88=B6=E5=8C=85?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E9=AA=A8=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt | 2 ++ src/appdelegate.cpp | 5 +++++ src/appdelegate.h | 15 +++++++++++++++ src/applistmodel.cpp | 5 +++++ src/applistmodel.h | 15 +++++++++++++++ 5 files changed, 42 insertions(+) create mode 100644 src/appdelegate.cpp create mode 100644 src/appdelegate.h create mode 100644 src/applistmodel.cpp create mode 100644 src/applistmodel.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b41f81c..2af8cb8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,8 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) ${PROJECT_SOURCES} aptssupdater.h aptssupdater.cpp icons.qrc + appdelegate.h appdelegate.cpp + applistmodel.h applistmodel.cpp ) # Define target properties for Android with Qt 6 as: # set_property(TARGET Spark-Update-Tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp new file mode 100644 index 0000000..f5162c6 --- /dev/null +++ b/src/appdelegate.cpp @@ -0,0 +1,5 @@ +#include "appdelegate.h" + +AppDelegate::AppDelegate(QObject *parent) + : QObject{parent} +{} diff --git a/src/appdelegate.h b/src/appdelegate.h new file mode 100644 index 0000000..e3b08a5 --- /dev/null +++ b/src/appdelegate.h @@ -0,0 +1,15 @@ +#ifndef APPDELEGATE_H +#define APPDELEGATE_H + +#include + +class AppDelegate : public QObject +{ + Q_OBJECT +public: + explicit AppDelegate(QObject *parent = nullptr); + +signals: +}; + +#endif // APPDELEGATE_H diff --git a/src/applistmodel.cpp b/src/applistmodel.cpp new file mode 100644 index 0000000..daebc97 --- /dev/null +++ b/src/applistmodel.cpp @@ -0,0 +1,5 @@ +#include "applistmodel.h" + +AppListModel::AppListModel(QObject *parent) + : QObject{parent} +{} diff --git a/src/applistmodel.h b/src/applistmodel.h new file mode 100644 index 0000000..a27ef78 --- /dev/null +++ b/src/applistmodel.h @@ -0,0 +1,15 @@ +#ifndef APPLISTMODEL_H +#define APPLISTMODEL_H + +#include + +class AppListModel : public QObject +{ + Q_OBJECT +public: + explicit AppListModel(QObject *parent = nullptr); + +signals: +}; + +#endif // APPLISTMODEL_H From 9e15e701c26243af3c9bf41f89a5ce901bc03130 Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 6 Jun 2025 14:34:24 +0800 Subject: [PATCH 039/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a8deca0..1d33641 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5,8 +5,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { - checkUpdates(); ui->setupUi(this); + checkUpdates(); initStyle(); } //初始化控件样式 @@ -104,7 +104,6 @@ void MainWindow::checkUpdates() aptssUpdater updater; // 获取可更新包列表 - // QStringList updateablePackages = updater.getUpdateablePackages(); updater.getUpdateInfoAsJson(); } From 50adf557623b804717e3ee4895096dd77b104e94 Mon Sep 17 00:00:00 2001 From: momen Date: Sat, 7 Jun 2025 23:34:38 +0800 Subject: [PATCH 040/126] =?UTF-8?q?chore:=E5=B0=86=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=9C=A8=E4=B8=BB=E7=95=8C=E9=9D=A2=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 40 +++++++++++++++++++++++++++++++++++++--- src/appdelegate.h | 12 +++++++++--- src/applistmodel.cpp | 41 ++++++++++++++++++++++++++++++++++++++--- src/applistmodel.h | 17 ++++++++++++++--- src/mainwindow.cpp | 18 +++++++++++++++--- src/mainwindow.h | 7 +++++++ 6 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index f5162c6..ff2c11b 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -1,5 +1,39 @@ #include "appdelegate.h" +#include +#include -AppDelegate::AppDelegate(QObject *parent) - : QObject{parent} -{} +AppDelegate::AppDelegate(QObject *parent) : QStyledItemDelegate(parent) {} + +void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + + // 获取数据 + QString name = index.data(Qt::DisplayRole).toString(); + QString currentVersion = index.data(Qt::UserRole + 2).toString(); + QString newVersion = index.data(Qt::UserRole + 3).toString(); + QString iconPath = index.data(Qt::UserRole + 4).toString(); + + // 绘制图标 + QIcon icon(iconPath); + QRect iconRect(option.rect.x() + 10, option.rect.y() + 10, 32, 32); + icon.paint(painter, iconRect); + + // 绘制文本 + QRect textRect(iconRect.right() + 10, option.rect.y() + 10, option.rect.width() - iconRect.width() - 20, option.rect.height() - 20); + painter->drawText(textRect, Qt::TextWordWrap, QString("%1\n当前版本: %2 → 新版本: %3").arg(name, currentVersion, newVersion)); +} + +QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + return QSize(option.rect.width(), 60); // 每行高度 60 +} + +bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if (event->type() == QEvent::MouseButtonRelease) { + qDebug() << "点击了第" << index.row() << "行"; + return true; + } + return QStyledItemDelegate::editorEvent(event, model, option, index); +} \ No newline at end of file diff --git a/src/appdelegate.h b/src/appdelegate.h index e3b08a5..893aacb 100644 --- a/src/appdelegate.h +++ b/src/appdelegate.h @@ -1,15 +1,21 @@ #ifndef APPDELEGATE_H #define APPDELEGATE_H -#include +#include +#include +// 添加 QEvent 头文件 +#include -class AppDelegate : public QObject +class AppDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit AppDelegate(QObject *parent = nullptr); -signals: + // 重写方法 + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; }; #endif // APPDELEGATE_H diff --git a/src/applistmodel.cpp b/src/applistmodel.cpp index daebc97..f18ffa3 100644 --- a/src/applistmodel.cpp +++ b/src/applistmodel.cpp @@ -1,5 +1,40 @@ #include "applistmodel.h" -AppListModel::AppListModel(QObject *parent) - : QObject{parent} -{} +AppListModel::AppListModel(QObject *parent) : QAbstractListModel(parent) {} + +int AppListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return m_data.size(); +} + +QVariant AppListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_data.size()) + return QVariant(); + + const QJsonObject obj = m_data.at(index.row()).toObject(); + + switch (role) { + case Qt::DisplayRole: + return obj["name"].toString(); + case Qt::UserRole + 1: // 包名 + return obj["package"].toString(); + case Qt::UserRole + 2: // 当前版本 + return obj["current_version"].toString(); + case Qt::UserRole + 3: // 新版本 + return obj["new_version"].toString(); + case Qt::UserRole + 4: // 图标路径 + return obj["icon"].toString(); + default: + return QVariant(); + } +} + +void AppListModel::setUpdateData(const QJsonArray &data) +{ + beginResetModel(); + m_data = data; + endResetModel(); +} diff --git a/src/applistmodel.h b/src/applistmodel.h index a27ef78..8fc82e7 100644 --- a/src/applistmodel.h +++ b/src/applistmodel.h @@ -1,15 +1,26 @@ #ifndef APPLISTMODEL_H #define APPLISTMODEL_H -#include +#include +#include +// 添加 QJsonObject 头文件 +#include -class AppListModel : public QObject +class AppListModel : public QAbstractListModel { Q_OBJECT public: explicit AppListModel(QObject *parent = nullptr); -signals: + // 重写方法 + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // 设置更新数据 + void setUpdateData(const QJsonArray &data); + +private: + QJsonArray m_data; }; #endif // APPLISTMODEL_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1d33641..b8a02f1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4,8 +4,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) + , m_model(new AppListModel(this)) + , m_delegate(new AppDelegate(this)) { ui->setupUi(this); + + // 创建 QListView 并设置父控件为 ui->appWidget + listView = new QListView(ui->appWidget); + listView->setModel(m_model); + listView->setItemDelegate(m_delegate); + + // 设置 QListView 填充 ui->appWidget + QVBoxLayout *layout = new QVBoxLayout(ui->appWidget); + layout->addWidget(listView); + layout->setContentsMargins(0, 0, 0, 0); + checkUpdates(); initStyle(); } @@ -102,9 +115,8 @@ void MainWindow::initStyle() void MainWindow::checkUpdates() { aptssUpdater updater; - - // 获取可更新包列表 - updater.getUpdateInfoAsJson(); + QJsonArray updateInfo = updater.getUpdateInfoAsJson(); + m_model->setUpdateData(updateInfo); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 361f01f..9cb6af8 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -3,6 +3,10 @@ #include #include "aptssupdater.h" +#include "applistmodel.h" +#include "appdelegate.h" +#include + QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; @@ -21,5 +25,8 @@ private: Ui::MainWindow *ui; void checkUpdates(); void initStyle(); + AppListModel *m_model; + AppDelegate *m_delegate; + QListView *listView; // 声明 QListView 指针 }; #endif // MAINWINDOW_H From a779a3532bcaa1ca7613a11d01d970ca2a16f7e9 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 8 Jun 2025 00:57:43 +0800 Subject: [PATCH 041/126] =?UTF-8?q?chore:=E6=98=BE=E7=A4=BA=E5=8C=85?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 7 +++++-- src/applistmodel.cpp | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index ff2c11b..8aeac93 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -13,15 +13,18 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c QString currentVersion = index.data(Qt::UserRole + 2).toString(); QString newVersion = index.data(Qt::UserRole + 3).toString(); QString iconPath = index.data(Qt::UserRole + 4).toString(); + // 获取包大小数据 + QVariant sizeVariant = index.data(Qt::UserRole + 5); + QString size = sizeVariant.isValid() ? sizeVariant.toString() : "未知"; // 绘制图标 QIcon icon(iconPath); QRect iconRect(option.rect.x() + 10, option.rect.y() + 10, 32, 32); icon.paint(painter, iconRect); - // 绘制文本 + // 绘制文本,添加包大小信息 QRect textRect(iconRect.right() + 10, option.rect.y() + 10, option.rect.width() - iconRect.width() - 20, option.rect.height() - 20); - painter->drawText(textRect, Qt::TextWordWrap, QString("%1\n当前版本: %2 → 新版本: %3").arg(name, currentVersion, newVersion)); + painter->drawText(textRect, Qt::TextWordWrap, QString("%1\n当前版本: %2 → 新版本: %3\n包大小: %4").arg(name, currentVersion, newVersion, size)); } QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const diff --git a/src/applistmodel.cpp b/src/applistmodel.cpp index f18ffa3..a896b0e 100644 --- a/src/applistmodel.cpp +++ b/src/applistmodel.cpp @@ -27,6 +27,8 @@ QVariant AppListModel::data(const QModelIndex &index, int role) const return obj["new_version"].toString(); case Qt::UserRole + 4: // 图标路径 return obj["icon"].toString(); + case Qt::UserRole + 5: // 文件大小 + return obj["size"].toVariant(); default: return QVariant(); } From 75ac14a8b83226af971b5e1a00ec4cf3137eaa72 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 8 Jun 2025 01:16:21 +0800 Subject: [PATCH 042/126] =?UTF-8?q?=20feat:=E6=98=BE=E7=A4=BA=E5=8C=85?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 74 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index 8aeac93..c5043ea 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -6,30 +6,78 @@ AppDelegate::AppDelegate(QObject *parent) : QStyledItemDelegate(parent) {} void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QStyledItemDelegate::paint(painter, option, index); + painter->save(); - // 获取数据 + // 绘制背景 + if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, option.palette.highlight()); + } else { + painter->fillRect(option.rect, QColor("#F3F4F6")); + } + + // 设置字体 + QFont boldFont = option.font; + boldFont.setBold(true); + + QFont normalFont = option.font; + + // 数据 QString name = index.data(Qt::DisplayRole).toString(); QString currentVersion = index.data(Qt::UserRole + 2).toString(); QString newVersion = index.data(Qt::UserRole + 3).toString(); QString iconPath = index.data(Qt::UserRole + 4).toString(); - // 获取包大小数据 - QVariant sizeVariant = index.data(Qt::UserRole + 5); - QString size = sizeVariant.isValid() ? sizeVariant.toString() : "未知"; + QString size = index.data(Qt::UserRole + 5).toString(); + QString description = index.data(Qt::UserRole + 6).toString(); // 假设额外说明文本 - // 绘制图标 - QIcon icon(iconPath); - QRect iconRect(option.rect.x() + 10, option.rect.y() + 10, 32, 32); - icon.paint(painter, iconRect); + // 区域定义 + QRect rect = option.rect; + int margin = 10; + int spacing = 6; + int iconSize = 40; - // 绘制文本,添加包大小信息 - QRect textRect(iconRect.right() + 10, option.rect.y() + 10, option.rect.width() - iconRect.width() - 20, option.rect.height() - 20); - painter->drawText(textRect, Qt::TextWordWrap, QString("%1\n当前版本: %2 → 新版本: %3\n包大小: %4").arg(name, currentVersion, newVersion, size)); + // 图标 + QRect iconRect(rect.left() + margin, rect.top() + (rect.height() - iconSize) / 2, iconSize, iconSize); + QIcon(iconPath).paint(painter, iconRect); + + // 文本起点 + int textX = iconRect.right() + margin; + int textWidth = rect.width() - textX - 100; // 留出按钮区域 + + // 绘制应用名称 + QRect nameRect(textX, rect.top() + margin, textWidth, 20); + painter->setFont(boldFont); + painter->setPen(QColor("#333333")); // 改为深色,确保清晰 + painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name); // 绘制应用名称 + + // 绘制版本信息 + QRect versionRect(textX, nameRect.bottom() + spacing, textWidth, 20); + painter->setFont(normalFont); + painter->setPen(QColor("#888888")); // 使用浅灰色,保持对比 + painter->drawText(versionRect, Qt::AlignLeft | Qt::AlignVCenter, + QString("当前版本: %1 → 新版本: %2").arg(currentVersion, newVersion)); + + // 描述行 + QRect descRect(textX, versionRect.bottom() + spacing, textWidth, 40); + painter->setFont(normalFont); + painter->setPen(QColor("#AAAAAA")); + painter->drawText(descRect, Qt::TextWordWrap, + QString("更新说明:%1\n包大小:%2").arg(description, size)); + + // 更新按钮(样式占位) + QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#267AFF")); + painter->drawRoundedRect(buttonRect, 6, 6); + painter->setPen(Qt::white); + painter->drawText(buttonRect, Qt::AlignCenter, "更新"); + + painter->restore(); } + QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - return QSize(option.rect.width(), 60); // 每行高度 60 + return QSize(option.rect.width(), 110); // 每行高度 110 } bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) From 705478fdf0c3157543bb0afca540527e550d762b Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 8 Jun 2025 10:33:34 +0800 Subject: [PATCH 043/126] =?UTF-8?q?chore:=E5=8F=96=E6=B6=88=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BF=A1=E6=81=AF=E7=9A=84=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index c5043ea..682247b 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -61,7 +61,8 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c painter->setFont(normalFont); painter->setPen(QColor("#AAAAAA")); painter->drawText(descRect, Qt::TextWordWrap, - QString("更新说明:%1\n包大小:%2").arg(description, size)); + // QString("更新说明:%1\n包大小:%2").arg(description, size)); + QString("包大小:%1").arg(size)); // 更新按钮(样式占位) QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); From 2cb69c72f3355ea7e7c8d17d0809896280e6cb9e Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 8 Jun 2025 10:44:51 +0800 Subject: [PATCH 044/126] =?UTF-8?q?=20chore:=E6=9B=B4=E6=96=B0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E9=AA=A8=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt | 1 + src/appdelegate.cpp | 85 +++++++++++++++++++++++++++++++++++------ src/appdelegate.h | 12 +++++- src/downloadmanager.cpp | 53 +++++++++++++++++++++++++ src/downloadmanager.h | 32 ++++++++++++++++ 5 files changed, 171 insertions(+), 12 deletions(-) create mode 100644 src/downloadmanager.cpp create mode 100644 src/downloadmanager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2af8cb8..29f7d58 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,7 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) icons.qrc appdelegate.h appdelegate.cpp applistmodel.h applistmodel.cpp + downloadmanager.h downloadmanager.cpp ) # Define target properties for Android with Qt 6 as: # set_property(TARGET Spark-Update-Tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index 682247b..9d6fd41 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -1,9 +1,27 @@ #include "appdelegate.h" #include #include +#include "downloadmanager.h" +#include +#include +#include // 包含 QApplication 头文件 -AppDelegate::AppDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - +AppDelegate::AppDelegate(QObject *parent) : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)) +{ + connect(m_downloadManager, &DownloadManager::downloadProgress, this, [this](int progress) { + m_progress = progress; + emit updateDisplay(); // 触发重绘 + }); + connect(m_downloadManager, &DownloadManager::downloadFinished, this, [this](bool success) { + m_isDownloading = false; + emit updateDisplay(); // 触发重绘 + if (success) { + qDebug() << "下载完成"; + } else { + qDebug() << "下载失败"; + } + }); +} void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); @@ -64,18 +82,38 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c // QString("更新说明:%1\n包大小:%2").arg(description, size)); QString("包大小:%1").arg(size)); - // 更新按钮(样式占位) - QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); - painter->setPen(Qt::NoPen); - painter->setBrush(QColor("#267AFF")); - painter->drawRoundedRect(buttonRect, 6, 6); - painter->setPen(Qt::white); - painter->drawText(buttonRect, Qt::AlignCenter, "更新"); + if (m_isDownloading) { + // 进度条 + QRect progressRect(rect.right() - 180, rect.top() + (rect.height() - 20) / 2, 100, 20); + QStyleOptionProgressBar progressBarOption; + progressBarOption.rect = progressRect; + progressBarOption.minimum = 0; + progressBarOption.maximum = 100; + progressBarOption.progress = m_progress; + progressBarOption.text = QString("%1%").arg(m_progress); + progressBarOption.textVisible = true; + QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); + + // 取消按钮 + QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); + QStyleOptionButton cancelButtonOption; + cancelButtonOption.rect = cancelButtonRect; + cancelButtonOption.text = "取消"; + cancelButtonOption.state |= QStyle::State_Enabled; + QApplication::style()->drawControl(QStyle::CE_PushButton, &cancelButtonOption, painter); + } else { + // 更新按钮 + QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#267AFF")); + painter->drawRoundedRect(buttonRect, 6, 6); + painter->setPen(Qt::white); + painter->drawText(buttonRect, Qt::AlignCenter, "更新"); + } painter->restore(); } - QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { return QSize(option.rect.width(), 110); // 每行高度 110 @@ -84,8 +122,33 @@ QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInde bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + QRect rect = option.rect; + if (m_isDownloading) { + // 取消按钮区域 + QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); + if (cancelButtonRect.contains(mouseEvent->pos())) { + // 修正方法调用,假设 DownloadManager 有 killProcess 方法 + if (m_downloadManager->isRunning()) { + m_downloadManager->killProcess(); + m_isDownloading = false; + emit updateDisplay(); // 触发重绘 + } + return true; + } + } else { + // 更新按钮区域 + QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + if (buttonRect.contains(mouseEvent->pos())) { + QString appName = index.data(Qt::DisplayRole).toString(); + m_isDownloading = true; + m_progress = 0; + m_downloadManager->startDownload(appName); + emit updateDisplay(); // 触发重绘 + return true; + } + } qDebug() << "点击了第" << index.row() << "行"; - return true; } return QStyledItemDelegate::editorEvent(event, model, option, index); } \ No newline at end of file diff --git a/src/appdelegate.h b/src/appdelegate.h index 893aacb..11c78bf 100644 --- a/src/appdelegate.h +++ b/src/appdelegate.h @@ -3,8 +3,10 @@ #include #include -// 添加 QEvent 头文件 #include +#include +// 前向声明 DownloadManager 类 +class DownloadManager; class AppDelegate : public QStyledItemDelegate { @@ -16,6 +18,14 @@ public: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; + +signals: + void updateDisplay(); // 声明更新显示的信号 + +private: + DownloadManager *m_downloadManager; // 声明下载管理器指针 + int m_progress = 0; // 声明下载进度 + bool m_isDownloading = false; // 声明下载状态 }; #endif // APPDELEGATE_H diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp new file mode 100644 index 0000000..e491c7c --- /dev/null +++ b/src/downloadmanager.cpp @@ -0,0 +1,53 @@ +#include "downloadmanager.h" + +DownloadManager::DownloadManager(QObject *parent) : QObject(parent), m_process(new QProcess(this)) +{ + m_progressRegex = QRegularExpression(R"(\((\d+)%\))"); + connect(m_process, &QProcess::readyReadStandardOutput, this, &DownloadManager::onProcessReadyRead); + connect(m_process, QOverload::of(&QProcess::finished), this, &DownloadManager::onProcessFinished); +} + +DownloadManager::~DownloadManager() +{ + if (m_process->state() == QProcess::Running) { + m_process->kill(); + m_process->waitForFinished(); + } +} + +void DownloadManager::startDownload(const QString &appName) +{ + QString command = QString("aptss download --print-uris %1").arg(appName); + m_process->start(command); +} + +void DownloadManager::onProcessReadyRead() +{ + QByteArray output = m_process->readAllStandardOutput(); + QString outputStr = QString::fromUtf8(output); + + QRegularExpressionMatch match = m_progressRegex.match(outputStr); + if (match.hasMatch()) { + int progress = match.captured(1).toInt(); + emit downloadProgress(progress); + } +} + +void DownloadManager::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + bool success = (exitStatus == QProcess::NormalExit && exitCode == 0); + emit downloadFinished(success); +} + +bool DownloadManager::isRunning() const +{ + return m_process->state() == QProcess::Running; +} + +void DownloadManager::killProcess() +{ + if (m_process->state() == QProcess::Running) { + m_process->kill(); + m_process->waitForFinished(); + } +} diff --git a/src/downloadmanager.h b/src/downloadmanager.h new file mode 100644 index 0000000..5cdf54a --- /dev/null +++ b/src/downloadmanager.h @@ -0,0 +1,32 @@ +#ifndef DOWNLOADMANAGER_H +#define DOWNLOADMANAGER_H + +#include +#include +#include + +class DownloadManager : public QObject +{ + Q_OBJECT +public: + explicit DownloadManager(QObject *parent = nullptr); + ~DownloadManager(); + + void startDownload(const QString &appName); + bool isRunning() const; // 声明 isRunning 方法 + void killProcess(); // 声明 killProcess 方法 + +signals: + void downloadProgress(int progress); + void downloadFinished(bool success); + +private slots: + void onProcessReadyRead(); + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + QProcess *m_process; + QRegularExpression m_progressRegex; +}; + +#endif // DOWNLOADMANAGER_H From 9ae6e00d71d49c11fd1535fa2dadc65a95398253 Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 9 Jun 2025 21:10:37 +0800 Subject: [PATCH 045/126] =?UTF-8?q?fix:=E4=BF=AE=E6=AD=A3=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=BD=AF=E4=BB=B6=E5=90=8D=E8=8E=B7=E5=8F=96=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 5 +++-- src/downloadmanager.cpp | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index 9d6fd41..c799c8e 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -140,10 +140,11 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QS // 更新按钮区域 QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); if (buttonRect.contains(mouseEvent->pos())) { - QString appName = index.data(Qt::DisplayRole).toString(); + // 修改这里:使用Qt::UserRole +1获取包名 + QString packageName = index.data(Qt::UserRole + 1).toString(); m_isDownloading = true; m_progress = 0; - m_downloadManager->startDownload(appName); + m_downloadManager->startDownload(packageName); emit updateDisplay(); // 触发重绘 return true; } diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index e491c7c..eb48f49 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1,4 +1,5 @@ #include "downloadmanager.h" +#include // 包含 QDebug 头文件 DownloadManager::DownloadManager(QObject *parent) : QObject(parent), m_process(new QProcess(this)) { @@ -17,7 +18,11 @@ DownloadManager::~DownloadManager() void DownloadManager::startDownload(const QString &appName) { + // 输出开始下载的日志 + qDebug() << "开始下载应用:" << appName; QString command = QString("aptss download --print-uris %1").arg(appName); + // 输出执行的命令日志 + qDebug() << "执行命令:" << command; m_process->start(command); } From ef2a0027c12615d233b7f3fa115ad28d9e1aba56 Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 9 Jun 2025 21:38:25 +0800 Subject: [PATCH 046/126] =?UTF-8?q?fix:=E4=BF=AE=E6=AD=A3=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloadmanager.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index eb48f49..21c8ed2 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -19,11 +19,12 @@ DownloadManager::~DownloadManager() void DownloadManager::startDownload(const QString &appName) { // 输出开始下载的日志 - qDebug() << "开始下载应用:" << appName; - QString command = QString("aptss download --print-uris %1").arg(appName); - // 输出执行的命令日志 - qDebug() << "执行命令:" << command; - m_process->start(command); + QString program = "aptss"; + QStringList arguments; + arguments << "download" << "--print-uris" << appName; + + qDebug() << "执行命令:" << program << arguments.join(" "); + m_process->start(program, arguments); } void DownloadManager::onProcessReadyRead() From 8b408d42d3c2a4edc549aab599aa88cf97930c9a Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 9 Jun 2025 21:40:59 +0800 Subject: [PATCH 047/126] =?UTF-8?q?chore:=E5=A2=9E=E5=8A=A0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E8=B6=85=E6=97=B6=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloadmanager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 21c8ed2..2bdbfd8 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -12,7 +12,13 @@ DownloadManager::~DownloadManager() { if (m_process->state() == QProcess::Running) { m_process->kill(); - m_process->waitForFinished(); + // 原代码 + // m_process->waitForFinished(); + + // 修改后增加超时处理 + if (!m_process->waitForFinished(5000)) { + qWarning() << "进程终止超时"; + } } } From 7ab203c86a69f46e800ded424739281292a3dec9 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 10 Jun 2025 11:03:31 +0800 Subject: [PATCH 048/126] =?UTF-8?q?chore:=E5=8F=96=E6=B6=88appwidget?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 80ac609..fb71fcd 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -268,11 +268,11 @@ - - - 598 - 1328 - + + + 0 + 0 + From a743002b1286ef724bb42c08b2b298c4534f97d2 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 10 Jun 2025 11:08:26 +0800 Subject: [PATCH 049/126] =?UTF-8?q?chore:=E8=B0=83=E6=95=B4widget=5F4?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E9=AB=98=E5=BA=A6=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index fb71fcd..25ddd74 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -64,15 +64,15 @@ - - - 1376 - 746 - - + + + 0 + 0 + + 0 From 3ef8f4da7fdbc0b8e5ceb367b86ab119cfed8dea Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 10 Jun 2025 11:09:55 +0800 Subject: [PATCH 050/126] =?UTF-8?q?chore:=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E5=85=B3=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 25ddd74..f0aace0 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -119,25 +119,6 @@ 0 - - - - - 16 - 16 - - - - - 16 - 16 - - - - - - - From 616453ed7d1cc3939ee4219b28f37f165e48087f Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 10 Jun 2025 14:26:41 +0800 Subject: [PATCH 051/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0listview?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=EF=BC=8C=E7=BE=8E=E5=8C=96=E6=BB=9A=E5=8A=A8?= =?UTF-8?q?=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b8a02f1..ba23551 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -111,6 +111,40 @@ void MainWindow::initStyle() //设置主背景颜色 this->setStyleSheet("background-color: #F8FAFC;"); + // 添加滚动条样式 + listView->setStyleSheet(R"( + QScrollBar:vertical { + background: #F3F4F6; + width: 8px; + margin: 0px; + } + QScrollBar::handle:vertical { + background: #D1D5DB; + border-radius: 4px; + min-height: 30px; + } + QScrollBar::handle:vertical:hover { + background: #9CA3AF; + } + QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + background: none; + height: 0px; + } + + QScrollBar:horizontal { + background: #F3F4F6; + height: 8px; + margin: 0px; + } + QScrollBar::handle:horizontal { + background: #D1D5DB; + border-radius: 4px; + min-width: 30px; + } + QScrollBar::handle:horizontal:hover { + background: #9CA3AF; + } + )"); } void MainWindow::checkUpdates() { From d5af9c2cf1ee93bde04fb52b1f5b7aa9321f544c Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 11 Jun 2025 11:18:44 +0800 Subject: [PATCH 052/126] =?UTF-8?q?chore:qdebug=E8=BE=93=E5=87=BA=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E4=B8=8B=E8=BD=BD=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloadmanager.cpp | 27 +++++++++++++++++++++------ src/downloadmanager.h | 1 - 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 2bdbfd8..a074514 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -35,16 +35,31 @@ void DownloadManager::startDownload(const QString &appName) void DownloadManager::onProcessReadyRead() { - QByteArray output = m_process->readAllStandardOutput(); - QString outputStr = QString::fromUtf8(output); + static QString buffer; // 缓存未处理的输出 + buffer += QString::fromUtf8(m_process->readAllStandardOutput()); - QRegularExpressionMatch match = m_progressRegex.match(outputStr); - if (match.hasMatch()) { - int progress = match.captured(1).toInt(); - emit downloadProgress(progress); + const QStringList lines = buffer.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); + + for (const QString &line : lines) { + QRegularExpressionMatch match = m_progressRegex.match(line); + if (match.hasMatch()) { + int progress = match.captured(1).toInt(); + qDebug() << "匹配进度:" << progress << "% => " << line; + emit downloadProgress(progress); + } else { + // qDebug() << "未匹配行:" << line; + } + } + + // 如果最后不是完整的一行,保留最后一部分 + if (!buffer.endsWith('\n') && !buffer.endsWith('\r')) { + buffer = lines.last(); + } else { + buffer.clear(); } } + void DownloadManager::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { bool success = (exitStatus == QProcess::NormalExit && exitCode == 0); diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 5cdf54a..36d204f 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -4,7 +4,6 @@ #include #include #include - class DownloadManager : public QObject { Q_OBJECT From 59f506c8e616bee9d576971b74557ffa69bd3d26 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 11 Jun 2025 22:36:36 +0800 Subject: [PATCH 053/126] =?UTF-8?q?update:=E4=BD=BF=E7=94=A8apt=20download?= =?UTF-8?q?=20=20=20--print-uris=20=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=8C=85=E5=A4=A7=E5=B0=8F=EF=BC=8C=E4=B8=8B=E8=BD=BD=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E3=80=81sha512=E7=AD=89=E5=B9=B6=E8=BF=94=E5=9B=9Ejso?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aptssupdater.cpp | 74 ++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index 05918c0..d5605bf 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -67,49 +67,46 @@ QStringList aptssUpdater::getUpdateablePackages() QStringList aptssUpdater::getPackageSizes() { - QStringList packageSizes; + QStringList packageDetails; QProcess process; - // 使用绝对路径 + 自动输入N跳过交互 - QString command = R"(echo N | /opt/durapps/spark-store/bin/aptss upgrade --assume-no)"; - - process.start("bash", QStringList() << "-c" << command); - if (!process.waitForFinished()) { - qWarning() << "获取包大小失败:进程未完成"; - return packageSizes; - } - - QString output = process.readAllStandardOutput() + process.readAllStandardError(); - - // 清理颜色控制字符(ANSI 转义序列) - output.remove(QRegularExpression(R"(\x1B\[[0-9;]*[a-zA-Z])")); // \x1B is ESC - - QStringList lines = output.split('\n', Qt::SkipEmptyParts); - - // 从 packageName 获取包名 + // 获取可更新包名列表 QStringList updateablePackages; for (const QString &pkgInfo : packageName) { - QString pkgName = pkgInfo.section(":", 0, 0).trimmed(); - updateablePackages << pkgName; + updateablePackages << pkgInfo.section(":", 0, 0).trimmed(); } - // 匹配 [apt-fast xx:xx:xx] 行,提取内容 - QRegularExpression sizeLineRegex(R"(\[apt-fast [^\]]+\](\S+)\s+\S+\s+(\d+(\.\d+)?[KMG]?iB))"); + 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); - for (const QString &line : lines) { - QRegularExpressionMatch match = sizeLineRegex.match(line.trimmed()); if (match.hasMatch()) { - QString name = match.captured(1); - QString size = match.captured(2); - - if (updateablePackages.contains(name)) { - packageSizes << QString("%1: %2").arg(name, size); - } + 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() << "包大小列表:" << packageSizes; - return packageSizes; + qDebug() << "完整包信息:" << packageDetails; + return packageDetails; } @@ -325,12 +322,17 @@ QJsonArray aptssUpdater::getUpdateInfoAsJson() } } - // 解析包大小信息 + // 解析包详细信息(新增部分) for (const QString &sizeInfo : sizes) { QStringList parts = sizeInfo.split(": "); if (parts.size() == 2) { QString packageName = parts[0]; - packageInfo[packageName]["size"] = parts[1]; + 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]; + } } } @@ -378,6 +380,10 @@ QJsonArray aptssUpdater::getUpdateInfoAsJson() jsonObj["size"] = packageInfo[packageName]["size"]; } + // 在构建JSON对象时添加新字段(在jsonObj中添加): + jsonObj["download_url"] = packageInfo[packageName]["url"]; + jsonObj["sha512"] = packageInfo[packageName]["sha512"]; + jsonArray.append(jsonObj); } qDebug()< Date: Wed, 11 Jun 2025 22:49:00 +0800 Subject: [PATCH 054/126] =?UTF-8?q?update:=E6=98=BE=E7=A4=BA=E5=8C=85?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E6=97=B6=E7=BB=9F=E4=B8=80=E6=8D=A2=E7=AE=97?= =?UTF-8?q?=E4=B8=BAMiB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index c799c8e..e23c0cf 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -80,7 +80,7 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c painter->setPen(QColor("#AAAAAA")); painter->drawText(descRect, Qt::TextWordWrap, // QString("更新说明:%1\n包大小:%2").arg(description, size)); - QString("包大小:%1").arg(size)); + QString("包大小:%1 MB").arg(QString::number(size.toDouble() / (1024 * 1024), 'f', 2))); if (m_isDownloading) { // 进度条 From a2aadf44e33d223af1f7e65fb043c645a28b004c Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 11 Jun 2025 23:08:35 +0800 Subject: [PATCH 055/126] =?UTF-8?q?chore:=E4=B8=8B=E8=BD=BDmetalink?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 0 src/CMakeLists.txt | 6 +- src/appdelegate.cpp | 26 ++++++--- src/applistmodel.cpp | 40 ++++++++++---- src/applistmodel.h | 2 +- src/aptssupdater.cpp | 9 ++- src/downloadmanager.cpp | 118 ++++++++++++++++++---------------------- src/downloadmanager.h | 24 ++++---- src/mainwindow.cpp | 7 ++- 9 files changed, 129 insertions(+), 103 deletions(-) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 29f7d58..f7f76af 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,8 +9,8 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network) set(PROJECT_SOURCES main.cpp @@ -47,7 +47,7 @@ else() endif() endif() -target_link_libraries(Spark-Update-Tool PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) +target_link_libraries(Spark-Update-Tool PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # If you are developing for iOS or macOS you should consider setting an diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index e23c0cf..c2b6eb7 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -5,6 +5,7 @@ #include #include #include // 包含 QApplication 头文件 +#include // 添加 QDir 头文件 AppDelegate::AppDelegate(QObject *parent) : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)) { @@ -128,23 +129,32 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QS // 取消按钮区域 QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); if (cancelButtonRect.contains(mouseEvent->pos())) { - // 修正方法调用,假设 DownloadManager 有 killProcess 方法 - if (m_downloadManager->isRunning()) { - m_downloadManager->killProcess(); - m_isDownloading = false; - emit updateDisplay(); // 触发重绘 - } + // 修正方法调用,假设 DownloadManager 有 cancelDownload 方法 + m_downloadManager->cancelDownload(); + m_isDownloading = false; + emit updateDisplay(); // 触发重绘 return true; } } else { // 更新按钮区域 QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); if (buttonRect.contains(mouseEvent->pos())) { - // 修改这里:使用Qt::UserRole +1获取包名 QString packageName = index.data(Qt::UserRole + 1).toString(); + QString downloadUrl = index.data(Qt::UserRole + 7).toString(); + + qDebug() << "从模型中获取的包名:" << packageName; + qDebug() << "从模型中获取的下载 URL:" << downloadUrl; // 检查模型中是否正确传递 URL + + if (downloadUrl.isEmpty()) { + qWarning() << "下载 URL 为空,无法开始下载,包名:" << packageName; + return false; + } + + QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); + m_isDownloading = true; m_progress = 0; - m_downloadManager->startDownload(packageName); + m_downloadManager->startDownload(downloadUrl, outputPath); emit updateDisplay(); // 触发重绘 return true; } diff --git a/src/applistmodel.cpp b/src/applistmodel.cpp index a896b0e..31e244a 100644 --- a/src/applistmodel.cpp +++ b/src/applistmodel.cpp @@ -14,29 +14,49 @@ QVariant AppListModel::data(const QModelIndex &index, int role) const if (!index.isValid() || index.row() >= m_data.size()) return QVariant(); - const QJsonObject obj = m_data.at(index.row()).toObject(); - + const QVariantMap &map = m_data.at(index.row()); // 直接访问 QVariantMap switch (role) { case Qt::DisplayRole: - return obj["name"].toString(); + return map.value("name"); case Qt::UserRole + 1: // 包名 - return obj["package"].toString(); + return map.value("package"); case Qt::UserRole + 2: // 当前版本 - return obj["current_version"].toString(); + return map.value("current_version"); case Qt::UserRole + 3: // 新版本 - return obj["new_version"].toString(); + return map.value("new_version"); case Qt::UserRole + 4: // 图标路径 - return obj["icon"].toString(); + return map.value("icon"); case Qt::UserRole + 5: // 文件大小 - return obj["size"].toVariant(); + return map.value("size"); + case Qt::UserRole + 6: // 描述 + return map.value("description"); + case Qt::UserRole + 7: // 下载 URL + return map.value("download_url"); // 返回下载 URL default: return QVariant(); } } -void AppListModel::setUpdateData(const QJsonArray &data) +void AppListModel::setUpdateData(const QJsonArray &updateInfo) { beginResetModel(); - m_data = data; + m_data.clear(); // 清空 QList + + for (const auto &item : updateInfo) { + QJsonObject obj = item.toObject(); + QVariantMap map; + map["package"] = obj["package"].toString(); + map["name"] = obj["name"].toString(); + map["current_version"] = obj["current_version"].toString(); + map["new_version"] = obj["new_version"].toString(); + map["icon"] = obj["icon"].toString(); + map["size"] = obj["size"].toString(); + map["download_url"] = obj["download_url"].toString(); // 确保设置下载 URL + m_data.append(map); // 添加到 QList + + qDebug() << "设置到模型的包名:" << map["package"].toString(); + qDebug() << "设置到模型的下载 URL:" << map["download_url"].toString(); // 检查设置的数据 + } + endResetModel(); } diff --git a/src/applistmodel.h b/src/applistmodel.h index 8fc82e7..2fb36c0 100644 --- a/src/applistmodel.h +++ b/src/applistmodel.h @@ -20,7 +20,7 @@ public: void setUpdateData(const QJsonArray &data); private: - QJsonArray m_data; + QList m_data; // 修改类型为 QList }; #endif // APPLISTMODEL_H diff --git a/src/aptssupdater.cpp b/src/aptssupdater.cpp index d5605bf..c4bd906 100644 --- a/src/aptssupdater.cpp +++ b/src/aptssupdater.cpp @@ -381,9 +381,14 @@ QJsonArray aptssUpdater::getUpdateInfoAsJson() } // 在构建JSON对象时添加新字段(在jsonObj中添加): - jsonObj["download_url"] = packageInfo[packageName]["url"]; + 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()< // 包含 QDebug 头文件 +#include +#include -DownloadManager::DownloadManager(QObject *parent) : QObject(parent), m_process(new QProcess(this)) +DownloadManager::DownloadManager(QObject *parent) : QObject(parent) {} + +void DownloadManager::startDownload(const QString &url, const QString &outputPath) { - m_progressRegex = QRegularExpression(R"(\((\d+)%\))"); - connect(m_process, &QProcess::readyReadStandardOutput, this, &DownloadManager::onProcessReadyRead); - connect(m_process, QOverload::of(&QProcess::finished), this, &DownloadManager::onProcessFinished); + qDebug() << "接收到的下载 URL:" << url; // 检查 URL 是否正确传递 + + // 验证 URL 是否包含协议 + QUrl metalinkUrl(url + ".metalink"); + if (!metalinkUrl.isValid() || metalinkUrl.scheme().isEmpty()) { + qWarning() << "无效的 URL:" << metalinkUrl.toString(); + emit downloadFinished(false); + return; + } + + QNetworkRequest request(metalinkUrl); + m_reply = m_networkManager.get(request); + + connect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadManager::onDownloadProgress); + connect(m_reply, &QNetworkReply::finished, this, &DownloadManager::onDownloadFinished); + + // 保存路径 + m_reply->setProperty("outputPath", outputPath); } -DownloadManager::~DownloadManager() +void DownloadManager::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (m_process->state() == QProcess::Running) { - m_process->kill(); - // 原代码 - // m_process->waitForFinished(); - - // 修改后增加超时处理 - if (!m_process->waitForFinished(5000)) { - qWarning() << "进程终止超时"; - } + if (bytesTotal > 0) { + int progress = static_cast((bytesReceived * 100) / bytesTotal); + emit downloadProgress(progress); } } -void DownloadManager::startDownload(const QString &appName) +void DownloadManager::onDownloadFinished() { - // 输出开始下载的日志 - QString program = "aptss"; - QStringList arguments; - arguments << "download" << "--print-uris" << appName; - - qDebug() << "执行命令:" << program << arguments.join(" "); - m_process->start(program, arguments); -} - -void DownloadManager::onProcessReadyRead() -{ - static QString buffer; // 缓存未处理的输出 - buffer += QString::fromUtf8(m_process->readAllStandardOutput()); - - const QStringList lines = buffer.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); - - for (const QString &line : lines) { - QRegularExpressionMatch match = m_progressRegex.match(line); - if (match.hasMatch()) { - int progress = match.captured(1).toInt(); - qDebug() << "匹配进度:" << progress << "% => " << line; - emit downloadProgress(progress); + if (m_reply->error() == QNetworkReply::NoError) { + QString outputPath = m_reply->property("outputPath").toString(); + QFile file(outputPath); + if (file.open(QIODevice::WriteOnly)) { + file.write(m_reply->readAll()); + file.close(); + qDebug() << "文件已保存到路径:" << outputPath; // 输出保存路径 + emit downloadFinished(true); } else { - // qDebug() << "未匹配行:" << line; + qWarning() << "无法保存文件到路径:" << outputPath; + emit downloadFinished(false); } - } - - // 如果最后不是完整的一行,保留最后一部分 - if (!buffer.endsWith('\n') && !buffer.endsWith('\r')) { - buffer = lines.last(); } else { - buffer.clear(); - } -} - - -void DownloadManager::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - bool success = (exitStatus == QProcess::NormalExit && exitCode == 0); - emit downloadFinished(success); -} - -bool DownloadManager::isRunning() const -{ - return m_process->state() == QProcess::Running; -} - -void DownloadManager::killProcess() -{ - if (m_process->state() == QProcess::Running) { - m_process->kill(); - m_process->waitForFinished(); + qWarning() << "下载失败:" << m_reply->errorString(); + emit downloadFinished(false); + } + + m_reply->deleteLater(); + m_reply = nullptr; +} + +void DownloadManager::cancelDownload() +{ + if (m_reply) { + m_reply->abort(); // 取消当前下载 + m_reply->deleteLater(); + m_reply = nullptr; + emit downloadFinished(false); // 发送下载失败信号 } } diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 36d204f..28f4a30 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -2,30 +2,28 @@ #define DOWNLOADMANAGER_H #include -#include -#include +#include +#include + class DownloadManager : public QObject { Q_OBJECT public: explicit DownloadManager(QObject *parent = nullptr); - ~DownloadManager(); - - void startDownload(const QString &appName); - bool isRunning() const; // 声明 isRunning 方法 - void killProcess(); // 声明 killProcess 方法 + void startDownload(const QString &url, const QString &outputPath); + void cancelDownload(); // 添加取消下载的方法 signals: - void downloadProgress(int progress); - void downloadFinished(bool success); + void downloadProgress(int progress); // 下载进度信号 + void downloadFinished(bool success); // 下载完成信号 private slots: - void onProcessReadyRead(); - void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void onDownloadFinished(); private: - QProcess *m_process; - QRegularExpression m_progressRegex; + QNetworkAccessManager m_networkManager; + QNetworkReply *m_reply = nullptr; }; #endif // DOWNLOADMANAGER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ba23551..3aa8cee 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -151,7 +151,12 @@ void MainWindow::checkUpdates() aptssUpdater updater; QJsonArray updateInfo = updater.getUpdateInfoAsJson(); m_model->setUpdateData(updateInfo); - + + for (const auto &item : updateInfo) { + QJsonObject obj = item.toObject(); + qDebug() << "模型设置的包名:" << obj["package"].toString(); + qDebug() << "模型设置的下载 URL:" << obj["download_url"].toString(); // 检查模型数据 + } } MainWindow::~MainWindow() From f071fcb00d99e488858889d89c718475e80b36cb Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 11 Jun 2025 23:11:29 +0800 Subject: [PATCH 056/126] =?UTF-8?q?chore:aria2=E4=B8=8B=E8=BD=BD=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 27 ++++++++------- src/downloadmanager.cpp | 73 +++++++++++++++-------------------------- src/downloadmanager.h | 9 +++-- 3 files changed, 46 insertions(+), 63 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index c2b6eb7..f211bf7 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -129,8 +129,6 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QS // 取消按钮区域 QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); if (cancelButtonRect.contains(mouseEvent->pos())) { - // 修正方法调用,假设 DownloadManager 有 cancelDownload 方法 - m_downloadManager->cancelDownload(); m_isDownloading = false; emit updateDisplay(); // 触发重绘 return true; @@ -141,19 +139,26 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QS if (buttonRect.contains(mouseEvent->pos())) { QString packageName = index.data(Qt::UserRole + 1).toString(); QString downloadUrl = index.data(Qt::UserRole + 7).toString(); - - qDebug() << "从模型中获取的包名:" << packageName; - qDebug() << "从模型中获取的下载 URL:" << downloadUrl; // 检查模型中是否正确传递 URL - - if (downloadUrl.isEmpty()) { - qWarning() << "下载 URL 为空,无法开始下载,包名:" << packageName; - return false; - } - QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); m_isDownloading = true; m_progress = 0; + + connect(m_downloadManager, &DownloadManager::downloadProgress, this, [this](int progress) { + m_progress = progress; + emit updateDisplay(); // 更新界面显示 + }); + + connect(m_downloadManager, &DownloadManager::downloadFinished, this, [this](bool success) { + m_isDownloading = false; + emit updateDisplay(); // 更新界面显示 + if (success) { + qDebug() << "下载完成"; + } else { + qDebug() << "下载失败"; + } + }); + m_downloadManager->startDownload(downloadUrl, outputPath); emit updateDisplay(); // 触发重绘 return true; diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 37a78e1..bcc7a80 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1,68 +1,47 @@ #include "downloadmanager.h" #include #include +#include +#include // 添加 QFileInfo 头文件 DownloadManager::DownloadManager(QObject *parent) : QObject(parent) {} void DownloadManager::startDownload(const QString &url, const QString &outputPath) { - qDebug() << "接收到的下载 URL:" << url; // 检查 URL 是否正确传递 + QString metalinkUrl = url + ".metalink"; // 构造 Metalink URL + qDebug() << "开始下载 Metalink 文件:" << metalinkUrl; - // 验证 URL 是否包含协议 - QUrl metalinkUrl(url + ".metalink"); - if (!metalinkUrl.isValid() || metalinkUrl.scheme().isEmpty()) { - qWarning() << "无效的 URL:" << metalinkUrl.toString(); - emit downloadFinished(false); - return; - } + QStringList arguments; + arguments << "--enable-rpc=false" << "--console-log-level=warn" + << "--summary-interval=1" << "--dir=" + QFileInfo(outputPath).absolutePath() + << "--out=" + QFileInfo(outputPath).fileName() << metalinkUrl; - QNetworkRequest request(metalinkUrl); - m_reply = m_networkManager.get(request); + connect(&m_aria2Process, &QProcess::readyReadStandardOutput, this, &DownloadManager::onAria2Progress); + connect(&m_aria2Process, QOverload::of(&QProcess::finished), this, &DownloadManager::onAria2Finished); - connect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadManager::onDownloadProgress); - connect(m_reply, &QNetworkReply::finished, this, &DownloadManager::onDownloadFinished); - - // 保存路径 - m_reply->setProperty("outputPath", outputPath); + m_aria2Process.start("aria2c", arguments); } -void DownloadManager::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) +void DownloadManager::onAria2Progress() { - if (bytesTotal > 0) { - int progress = static_cast((bytesReceived * 100) / bytesTotal); - emit downloadProgress(progress); + QString output = m_aria2Process.readAllStandardOutput(); + QRegularExpression regex(R"(Download Progress: (\d+)%.*)"); + QRegularExpressionMatch match = regex.match(output); + + if (match.hasMatch()) { + int progress = match.captured(1).toInt(); + emit downloadProgress(progress); // 发送进度信号 + qDebug() << "下载进度:" << progress << "%"; } } -void DownloadManager::onDownloadFinished() +void DownloadManager::onAria2Finished(int exitCode, QProcess::ExitStatus exitStatus) { - if (m_reply->error() == QNetworkReply::NoError) { - QString outputPath = m_reply->property("outputPath").toString(); - QFile file(outputPath); - if (file.open(QIODevice::WriteOnly)) { - file.write(m_reply->readAll()); - file.close(); - qDebug() << "文件已保存到路径:" << outputPath; // 输出保存路径 - emit downloadFinished(true); - } else { - qWarning() << "无法保存文件到路径:" << outputPath; - emit downloadFinished(false); - } + if (exitCode == 0 && exitStatus == QProcess::NormalExit) { + qDebug() << "下载完成"; + emit downloadFinished(true); // 发送完成信号 } else { - qWarning() << "下载失败:" << m_reply->errorString(); - emit downloadFinished(false); - } - - m_reply->deleteLater(); - m_reply = nullptr; -} - -void DownloadManager::cancelDownload() -{ - if (m_reply) { - m_reply->abort(); // 取消当前下载 - m_reply->deleteLater(); - m_reply = nullptr; - emit downloadFinished(false); // 发送下载失败信号 + qWarning() << "下载失败,退出代码:" << exitCode; + emit downloadFinished(false); // 发送失败信号 } } diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 28f4a30..ca80330 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -4,6 +4,7 @@ #include #include #include +#include class DownloadManager : public QObject { @@ -11,19 +12,17 @@ class DownloadManager : public QObject public: explicit DownloadManager(QObject *parent = nullptr); void startDownload(const QString &url, const QString &outputPath); - void cancelDownload(); // 添加取消下载的方法 signals: void downloadProgress(int progress); // 下载进度信号 void downloadFinished(bool success); // 下载完成信号 private slots: - void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void onDownloadFinished(); + void onAria2Progress(); // 处理 aria2 的进度 + void onAria2Finished(int exitCode, QProcess::ExitStatus exitStatus); // 处理 aria2 完成事件 private: - QNetworkAccessManager m_networkManager; - QNetworkReply *m_reply = nullptr; + QProcess m_aria2Process; // 用于运行 aria2 的进程 }; #endif // DOWNLOADMANAGER_H From e416ffdbffee4bdc68df2290d56273a06bd99d48 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 11 Jun 2025 23:31:10 +0800 Subject: [PATCH 057/126] =?UTF-8?q?chore:=E6=98=BE=E7=A4=BA=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E7=99=BE=E5=88=86=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloadmanager.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index bcc7a80..1603928 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -25,16 +25,20 @@ void DownloadManager::startDownload(const QString &url, const QString &outputPat void DownloadManager::onAria2Progress() { QString output = m_aria2Process.readAllStandardOutput(); - QRegularExpression regex(R"(Download Progress: (\d+)%.*)"); - QRegularExpressionMatch match = regex.match(output); + QRegularExpression regex(R"(\((\d+)%\))"); + QRegularExpressionMatchIterator i = regex.globalMatch(output); - if (match.hasMatch()) { - int progress = match.captured(1).toInt(); - emit downloadProgress(progress); // 发送进度信号 - qDebug() << "下载进度:" << progress << "%"; + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + if (match.hasMatch()) { + int progress = match.captured(1).toInt(); + emit downloadProgress(progress); // 发送进度信号 + qDebug() << "下载进度:" << progress << "%"; + } } } + void DownloadManager::onAria2Finished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode == 0 && exitStatus == QProcess::NormalExit) { From 90b21d40cfe22fd944190881e2489277fc675d69 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 12 Jun 2025 09:23:28 +0800 Subject: [PATCH 058/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E8=BF=9B=E5=BA=A6=E6=9D=A1=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 91 +++++++++++++++++------------------------ src/appdelegate.h | 10 +++-- src/downloadmanager.cpp | 70 ++++++++++++++++--------------- src/downloadmanager.h | 14 +++---- 4 files changed, 87 insertions(+), 98 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index f211bf7..095b571 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -9,17 +9,22 @@ AppDelegate::AppDelegate(QObject *parent) : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)) { - connect(m_downloadManager, &DownloadManager::downloadProgress, this, [this](int progress) { - m_progress = progress; - emit updateDisplay(); // 触发重绘 + connect(m_downloadManager, &DownloadManager::downloadProgress, this, [this](const QString &packageName, int progress) { + if (m_downloads.contains(packageName)) { + m_downloads[packageName].progress = progress; + emit updateDisplay(); // 触发重绘 + } }); - connect(m_downloadManager, &DownloadManager::downloadFinished, this, [this](bool success) { - m_isDownloading = false; - emit updateDisplay(); // 触发重绘 - if (success) { - qDebug() << "下载完成"; - } else { - qDebug() << "下载失败"; + + connect(m_downloadManager, &DownloadManager::downloadFinished, this, [this](const QString &packageName, bool success) { + if (m_downloads.contains(packageName)) { + m_downloads[packageName].isDownloading = false; + emit updateDisplay(); // 触发重绘 + if (success) { + qDebug() << "下载完成:" << packageName; + } else { + qDebug() << "下载失败:" << packageName; + } } }); } @@ -83,33 +88,27 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c // QString("更新说明:%1\n包大小:%2").arg(description, size)); QString("包大小:%1 MB").arg(QString::number(size.toDouble() / (1024 * 1024), 'f', 2))); - if (m_isDownloading) { - // 进度条 + QString packageName = index.data(Qt::UserRole + 1).toString(); + bool isDownloading = m_downloads.contains(packageName) && m_downloads[packageName].isDownloading; + int progress = m_downloads.value(packageName, DownloadInfo{0, false}).progress; + if (isDownloading) { + // 进度条区域 QRect progressRect(rect.right() - 180, rect.top() + (rect.height() - 20) / 2, 100, 20); QStyleOptionProgressBar progressBarOption; progressBarOption.rect = progressRect; progressBarOption.minimum = 0; progressBarOption.maximum = 100; - progressBarOption.progress = m_progress; - progressBarOption.text = QString("%1%").arg(m_progress); + progressBarOption.progress = progress; + progressBarOption.text = QString("%1%").arg(progress); progressBarOption.textVisible = true; QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); - - // 取消按钮 - QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); - QStyleOptionButton cancelButtonOption; - cancelButtonOption.rect = cancelButtonRect; - cancelButtonOption.text = "取消"; - cancelButtonOption.state |= QStyle::State_Enabled; - QApplication::style()->drawControl(QStyle::CE_PushButton, &cancelButtonOption, painter); } else { - // 更新按钮 - QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); - painter->setPen(Qt::NoPen); - painter->setBrush(QColor("#267AFF")); - painter->drawRoundedRect(buttonRect, 6, 6); - painter->setPen(Qt::white); - painter->drawText(buttonRect, Qt::AlignCenter, "更新"); + // 新增:绘制更新按钮 + QStyleOptionButton buttonOption; + buttonOption.rect = QRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + buttonOption.text = "更新"; + buttonOption.state = QStyle::State_Enabled; + QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter); } painter->restore(); @@ -125,46 +124,30 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QS if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); QRect rect = option.rect; - if (m_isDownloading) { + QString packageName = index.data(Qt::UserRole + 1).toString(); + + if (m_downloads.contains(packageName) && m_downloads[packageName].isDownloading) { // 取消按钮区域 QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); if (cancelButtonRect.contains(mouseEvent->pos())) { - m_isDownloading = false; - emit updateDisplay(); // 触发重绘 + m_downloadManager->cancelDownload(packageName); + m_downloads.remove(packageName); + emit updateDisplay(); return true; } } else { // 更新按钮区域 QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); if (buttonRect.contains(mouseEvent->pos())) { - QString packageName = index.data(Qt::UserRole + 1).toString(); QString downloadUrl = index.data(Qt::UserRole + 7).toString(); QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); - m_isDownloading = true; - m_progress = 0; - - connect(m_downloadManager, &DownloadManager::downloadProgress, this, [this](int progress) { - m_progress = progress; - emit updateDisplay(); // 更新界面显示 - }); - - connect(m_downloadManager, &DownloadManager::downloadFinished, this, [this](bool success) { - m_isDownloading = false; - emit updateDisplay(); // 更新界面显示 - if (success) { - qDebug() << "下载完成"; - } else { - qDebug() << "下载失败"; - } - }); - - m_downloadManager->startDownload(downloadUrl, outputPath); - emit updateDisplay(); // 触发重绘 + m_downloads[packageName] = {0, true}; + m_downloadManager->startDownload(packageName, downloadUrl, outputPath); + emit updateDisplay(); return true; } } - qDebug() << "点击了第" << index.row() << "行"; } return QStyledItemDelegate::editorEvent(event, model, option, index); } \ No newline at end of file diff --git a/src/appdelegate.h b/src/appdelegate.h index 11c78bf..4afa130 100644 --- a/src/appdelegate.h +++ b/src/appdelegate.h @@ -23,9 +23,13 @@ signals: void updateDisplay(); // 声明更新显示的信号 private: - DownloadManager *m_downloadManager; // 声明下载管理器指针 - int m_progress = 0; // 声明下载进度 - bool m_isDownloading = false; // 声明下载状态 + struct DownloadInfo { + int progress = 0; + bool isDownloading = false; + }; + QHash m_downloads; // 使用包名作为键的下载状态 + DownloadManager *m_downloadManager; + }; #endif // APPDELEGATE_H diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 1603928..cef9fb5 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -6,46 +6,50 @@ DownloadManager::DownloadManager(QObject *parent) : QObject(parent) {} -void DownloadManager::startDownload(const QString &url, const QString &outputPath) +void DownloadManager::startDownload(const QString &packageName, const QString &url, const QString &outputPath) { - QString metalinkUrl = url + ".metalink"; // 构造 Metalink URL - qDebug() << "开始下载 Metalink 文件:" << metalinkUrl; - + QString metalinkUrl = url + ".metalink"; QStringList arguments; - arguments << "--enable-rpc=false" << "--console-log-level=warn" - << "--summary-interval=1" << "--dir=" + QFileInfo(outputPath).absolutePath() - << "--out=" + QFileInfo(outputPath).fileName() << metalinkUrl; + arguments << "--enable-rpc=false" + << "--console-log-level=warn" + << "--summary-interval=1" + << "--dir=" + QFileInfo(outputPath).absolutePath() + << "--out=" + QFileInfo(outputPath).fileName() + << metalinkUrl; - connect(&m_aria2Process, &QProcess::readyReadStandardOutput, this, &DownloadManager::onAria2Progress); - connect(&m_aria2Process, QOverload::of(&QProcess::finished), this, &DownloadManager::onAria2Finished); + QProcess* process = new QProcess(this); + m_processes.insert(packageName, process); - m_aria2Process.start("aria2c", arguments); -} - -void DownloadManager::onAria2Progress() -{ - QString output = m_aria2Process.readAllStandardOutput(); - QRegularExpression regex(R"(\((\d+)%\))"); - QRegularExpressionMatchIterator i = regex.globalMatch(output); - - while (i.hasNext()) { - QRegularExpressionMatch match = i.next(); - if (match.hasMatch()) { - int progress = match.captured(1).toInt(); - emit downloadProgress(progress); // 发送进度信号 - qDebug() << "下载进度:" << progress << "%"; + connect(process, &QProcess::readyReadStandardOutput, [this, process, packageName]() { + QString output = process->readAllStandardOutput(); + QRegularExpression regex(R"(\((\d+)%\))"); + QRegularExpressionMatchIterator i = regex.globalMatch(output); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + if (match.hasMatch()) { + int progress = match.captured(1).toInt(); + emit downloadProgress(packageName, progress); + qDebug() << "下载进度:" << progress << "%"; + } } - } + }); + + connect(process, QOverload::of(&QProcess::finished), + [this, packageName](int exitCode, QProcess::ExitStatus exitStatus) { + bool success = (exitCode == 0 && exitStatus == QProcess::NormalExit); + emit downloadFinished(packageName, success); + m_processes.remove(packageName); + }); + + process->start("aria2c", arguments); } - -void DownloadManager::onAria2Finished(int exitCode, QProcess::ExitStatus exitStatus) +void DownloadManager::cancelDownload(const QString &packageName) { - if (exitCode == 0 && exitStatus == QProcess::NormalExit) { - qDebug() << "下载完成"; - emit downloadFinished(true); // 发送完成信号 - } else { - qWarning() << "下载失败,退出代码:" << exitCode; - emit downloadFinished(false); // 发送失败信号 + if (m_processes.contains(packageName)) { + QProcess* process = m_processes[packageName]; + process->terminate(); + process->waitForFinished(); + m_processes.remove(packageName); } } diff --git a/src/downloadmanager.h b/src/downloadmanager.h index ca80330..362867d 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -11,18 +11,16 @@ class DownloadManager : public QObject Q_OBJECT public: explicit DownloadManager(QObject *parent = nullptr); - void startDownload(const QString &url, const QString &outputPath); + void startDownload(const QString &packageName, const QString &url, const QString &outputPath); // 修改参数列表 + void cancelDownload(const QString &packageName); // 移动到public区域 signals: - void downloadProgress(int progress); // 下载进度信号 - void downloadFinished(bool success); // 下载完成信号 - -private slots: - void onAria2Progress(); // 处理 aria2 的进度 - void onAria2Finished(int exitCode, QProcess::ExitStatus exitStatus); // 处理 aria2 完成事件 + void downloadProgress(const QString &packageName, int progress); + void downloadFinished(const QString &packageName, bool success); private: - QProcess m_aria2Process; // 用于运行 aria2 的进程 + QHash m_processes; // 移除旧的m_aria2Process + // 移除旧的onAria2Progress和onAria2Finished声明 }; #endif // DOWNLOADMANAGER_H From f7baf015c04d17e5ccbce6017a3072a67030cb8b Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 12 Jun 2025 17:31:06 +0800 Subject: [PATCH 059/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloadmanager.cpp | 36 ++++++++++++++++++------------------ src/downloadmanager.h | 11 +++++------ 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index cef9fb5..1b31533 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1,8 +1,7 @@ #include "downloadmanager.h" -#include -#include +#include #include -#include // 添加 QFileInfo 头文件 +#include DownloadManager::DownloadManager(QObject *parent) : QObject(parent) {} @@ -10,22 +9,22 @@ void DownloadManager::startDownload(const QString &packageName, const QString &u { QString metalinkUrl = url + ".metalink"; QStringList arguments; - arguments << "--enable-rpc=false" + arguments << "--enable-rpc=false" << "--console-log-level=warn" - << "--summary-interval=1" + << "--summary-interval=1" << "--dir=" + QFileInfo(outputPath).absolutePath() - << "--out=" + QFileInfo(outputPath).fileName() + << "--out=" + QFileInfo(outputPath).fileName() << metalinkUrl; - QProcess* process = new QProcess(this); + QProcess *process = new QProcess(this); m_processes.insert(packageName, process); + // ✅ 改为按行读取(逐行处理) connect(process, &QProcess::readyReadStandardOutput, [this, process, packageName]() { - QString output = process->readAllStandardOutput(); - QRegularExpression regex(R"(\((\d+)%\))"); - QRegularExpressionMatchIterator i = regex.globalMatch(output); - while (i.hasNext()) { - QRegularExpressionMatch match = i.next(); + while (process->canReadLine()) { + QString line = process->readLine().trimmed(); + QRegularExpression regex(R"(\((\d+)%\))"); + QRegularExpressionMatch match = regex.match(line); if (match.hasMatch()) { int progress = match.captured(1).toInt(); emit downloadProgress(packageName, progress); @@ -34,12 +33,13 @@ void DownloadManager::startDownload(const QString &packageName, const QString &u } }); + // 下载完成 connect(process, QOverload::of(&QProcess::finished), - [this, packageName](int exitCode, QProcess::ExitStatus exitStatus) { - bool success = (exitCode == 0 && exitStatus == QProcess::NormalExit); - emit downloadFinished(packageName, success); - m_processes.remove(packageName); - }); + [this, packageName](int exitCode, QProcess::ExitStatus exitStatus) { + bool success = (exitCode == 0 && exitStatus == QProcess::NormalExit); + emit downloadFinished(packageName, success); + m_processes.remove(packageName); + }); process->start("aria2c", arguments); } @@ -47,7 +47,7 @@ void DownloadManager::startDownload(const QString &packageName, const QString &u void DownloadManager::cancelDownload(const QString &packageName) { if (m_processes.contains(packageName)) { - QProcess* process = m_processes[packageName]; + QProcess *process = m_processes[packageName]; process->terminate(); process->waitForFinished(); m_processes.remove(packageName); diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 362867d..f47cab3 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -2,25 +2,24 @@ #define DOWNLOADMANAGER_H #include -#include -#include #include +#include class DownloadManager : public QObject { Q_OBJECT + public: explicit DownloadManager(QObject *parent = nullptr); - void startDownload(const QString &packageName, const QString &url, const QString &outputPath); // 修改参数列表 - void cancelDownload(const QString &packageName); // 移动到public区域 + void startDownload(const QString &packageName, const QString &url, const QString &outputPath); + void cancelDownload(const QString &packageName); signals: void downloadProgress(const QString &packageName, int progress); void downloadFinished(const QString &packageName, bool success); private: - QHash m_processes; // 移除旧的m_aria2Process - // 移除旧的onAria2Progress和onAria2Finished声明 + QMap m_processes; }; #endif // DOWNLOADMANAGER_H From 50c916a8cc3d85c1eb52a6dbb0dd58988866bb3c Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 12 Jun 2025 22:44:20 +0800 Subject: [PATCH 060/126] =?UTF-8?q?update:=E6=98=BE=E7=A4=BA=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 104 ++++++++++++++++++++------------------------ src/appdelegate.h | 36 +++++++-------- src/mainwindow.cpp | 10 ++++- 3 files changed, 71 insertions(+), 79 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index 095b571..ccb3eec 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -1,98 +1,89 @@ #include "appdelegate.h" #include #include -#include "downloadmanager.h" +#include +#include #include #include -#include // 包含 QApplication 头文件 -#include // 添加 QDir 头文件 +#include +#include -AppDelegate::AppDelegate(QObject *parent) : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)) -{ - connect(m_downloadManager, &DownloadManager::downloadProgress, this, [this](const QString &packageName, int progress) { - if (m_downloads.contains(packageName)) { - m_downloads[packageName].progress = progress; - emit updateDisplay(); // 触发重绘 - } - }); - - connect(m_downloadManager, &DownloadManager::downloadFinished, this, [this](const QString &packageName, bool success) { +AppDelegate::AppDelegate(QObject *parent) + : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)) { + + connect(m_downloadManager, &DownloadManager::downloadFinished, this, + [this](const QString &packageName, bool success) { if (m_downloads.contains(packageName)) { m_downloads[packageName].isDownloading = false; - emit updateDisplay(); // 触发重绘 - if (success) { - qDebug() << "下载完成:" << packageName; - } else { - qDebug() << "下载失败:" << packageName; - } + emit updateDisplay(packageName); + qDebug() << (success ? "下载完成:" : "下载失败:") << packageName; + } + }); + + connect(m_downloadManager, &DownloadManager::downloadProgress, this, + [this](const QString &packageName, int progress) { + if (m_downloads.contains(packageName)) { + m_downloads[packageName].progress = progress; + qDebug()<save(); - // 绘制背景 - if (option.state & QStyle::State_Selected) { + if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, option.palette.highlight()); - } else { - painter->fillRect(option.rect, QColor("#F3F4F6")); - } + else + painter->fillRect(option.rect, QColor("#F3F4F6")); - // 设置字体 QFont boldFont = option.font; boldFont.setBold(true); - QFont normalFont = option.font; - // 数据 QString name = index.data(Qt::DisplayRole).toString(); QString currentVersion = index.data(Qt::UserRole + 2).toString(); QString newVersion = index.data(Qt::UserRole + 3).toString(); QString iconPath = index.data(Qt::UserRole + 4).toString(); QString size = index.data(Qt::UserRole + 5).toString(); - QString description = index.data(Qt::UserRole + 6).toString(); // 假设额外说明文本 + QString description = index.data(Qt::UserRole + 6).toString(); - // 区域定义 QRect rect = option.rect; - int margin = 10; - int spacing = 6; - int iconSize = 40; + int margin = 10, spacing = 6, iconSize = 40; - // 图标 QRect iconRect(rect.left() + margin, rect.top() + (rect.height() - iconSize) / 2, iconSize, iconSize); QIcon(iconPath).paint(painter, iconRect); - // 文本起点 int textX = iconRect.right() + margin; - int textWidth = rect.width() - textX - 100; // 留出按钮区域 + int textWidth = rect.width() - textX - 100; - // 绘制应用名称 QRect nameRect(textX, rect.top() + margin, textWidth, 20); painter->setFont(boldFont); - painter->setPen(QColor("#333333")); // 改为深色,确保清晰 - painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name); // 绘制应用名称 + painter->setPen(QColor("#333333")); + painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name); - // 绘制版本信息 QRect versionRect(textX, nameRect.bottom() + spacing, textWidth, 20); painter->setFont(normalFont); - painter->setPen(QColor("#888888")); // 使用浅灰色,保持对比 + painter->setPen(QColor("#888888")); painter->drawText(versionRect, Qt::AlignLeft | Qt::AlignVCenter, QString("当前版本: %1 → 新版本: %2").arg(currentVersion, newVersion)); - // 描述行 QRect descRect(textX, versionRect.bottom() + spacing, textWidth, 40); painter->setFont(normalFont); painter->setPen(QColor("#AAAAAA")); painter->drawText(descRect, Qt::TextWordWrap, - // QString("更新说明:%1\n包大小:%2").arg(description, size)); - QString("包大小:%1 MB").arg(QString::number(size.toDouble() / (1024 * 1024), 'f', 2))); + QString("包大小:%1 MB").arg(QString::number(size.toDouble() / (1024 * 1024), 'f', 2))); QString packageName = index.data(Qt::UserRole + 1).toString(); bool isDownloading = m_downloads.contains(packageName) && m_downloads[packageName].isDownloading; int progress = m_downloads.value(packageName, DownloadInfo{0, false}).progress; + if (isDownloading) { - // 进度条区域 QRect progressRect(rect.right() - 180, rect.top() + (rect.height() - 20) / 2, 100, 20); QStyleOptionProgressBar progressBarOption; progressBarOption.rect = progressRect; @@ -103,7 +94,6 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c progressBarOption.textVisible = true; QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); } else { - // 新增:绘制更新按钮 QStyleOptionButton buttonOption; buttonOption.rect = QRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); buttonOption.text = "更新"; @@ -114,29 +104,26 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c painter->restore(); } -QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - return QSize(option.rect.width(), 110); // 每行高度 110 +QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { + return QSize(option.rect.width(), 110); } -bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) -{ +bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) { if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEvent = static_cast(event); + QMouseEvent *mouseEvent = static_cast(event); QRect rect = option.rect; QString packageName = index.data(Qt::UserRole + 1).toString(); if (m_downloads.contains(packageName) && m_downloads[packageName].isDownloading) { - // 取消按钮区域 QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); if (cancelButtonRect.contains(mouseEvent->pos())) { m_downloadManager->cancelDownload(packageName); m_downloads.remove(packageName); - emit updateDisplay(); + emit updateDisplay(packageName); return true; } } else { - // 更新按钮区域 QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); if (buttonRect.contains(mouseEvent->pos())) { QString downloadUrl = index.data(Qt::UserRole + 7).toString(); @@ -144,10 +131,11 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QS m_downloads[packageName] = {0, true}; m_downloadManager->startDownload(packageName, downloadUrl, outputPath); - emit updateDisplay(); + emit updateDisplay(packageName); return true; } } } + return QStyledItemDelegate::editorEvent(event, model, option, index); -} \ No newline at end of file +} diff --git a/src/appdelegate.h b/src/appdelegate.h index 4afa130..37f7a77 100644 --- a/src/appdelegate.h +++ b/src/appdelegate.h @@ -1,35 +1,31 @@ -#ifndef APPDELEGATE_H -#define APPDELEGATE_H +#pragma once #include -#include -#include -#include -// 前向声明 DownloadManager 类 -class DownloadManager; +#include +#include "downloadmanager.h" -class AppDelegate : public QStyledItemDelegate -{ +struct DownloadInfo { + int progress = 0; + bool isDownloading = false; +}; + +class AppDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit AppDelegate(QObject *parent = nullptr); - // 重写方法 + void setModel(QAbstractItemModel *model); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) override; signals: - void updateDisplay(); // 声明更新显示的信号 + void updateDisplay(const QString &packageName); private: - struct DownloadInfo { - int progress = 0; - bool isDownloading = false; - }; - QHash m_downloads; // 使用包名作为键的下载状态 DownloadManager *m_downloadManager; - + QHash m_downloads; + QAbstractItemModel *m_model = nullptr; }; - -#endif // APPDELEGATE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3aa8cee..9b20071 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -18,7 +18,15 @@ MainWindow::MainWindow(QWidget *parent) QVBoxLayout *layout = new QVBoxLayout(ui->appWidget); layout->addWidget(listView); layout->setContentsMargins(0, 0, 0, 0); - + connect(m_delegate, &AppDelegate::updateDisplay, this, [=](const QString &packageName) { + for (int i = 0; i < m_model->rowCount(); ++i) { + QModelIndex index = m_model->index(i); + if (index.data(Qt::UserRole + 1).toString() == packageName) { + m_model->dataChanged(index, index); // 刷新该行 + break; + } + } + }); checkUpdates(); initStyle(); } From 73a3733bc1923e48d80e05f271da9024bb05370a Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 13 Jun 2025 09:04:25 +0800 Subject: [PATCH 061/126] =?UTF-8?q?update:=E8=BF=90=E8=A1=8C=E5=A4=9A?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8C=E6=97=B6=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloadmanager.cpp | 85 +++++++++++++++++++++++++++++++---------- src/downloadmanager.h | 7 +++- 2 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 1b31533..271b7bf 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1,44 +1,62 @@ #include "downloadmanager.h" #include +#include +#include #include #include -DownloadManager::DownloadManager(QObject *parent) : QObject(parent) {} +DownloadManager::DownloadManager(QObject *parent) : QObject(parent) +{ + cleanupTempFiles(); +} void DownloadManager::startDownload(const QString &packageName, const QString &url, const QString &outputPath) { + if (m_processes.contains(packageName)) { + qWarning() << packageName << " is already downloading."; + return; + } + QString metalinkUrl = url + ".metalink"; - QStringList arguments; - arguments << "--enable-rpc=false" - << "--console-log-level=warn" - << "--summary-interval=1" - << "--dir=" + QFileInfo(outputPath).absolutePath() - << "--out=" + QFileInfo(outputPath).fileName() - << metalinkUrl; + QFileInfo fileInfo(outputPath); + + QStringList arguments = { + "--enable-rpc=false", + "--console-log-level=warn", + "--summary-interval=1", + "--allow-overwrite=true", + "--dir=" + fileInfo.absolutePath(), + "--out=" + fileInfo.fileName(), + metalinkUrl + }; QProcess *process = new QProcess(this); m_processes.insert(packageName, process); - // ✅ 改为按行读取(逐行处理) - connect(process, &QProcess::readyReadStandardOutput, [this, process, packageName]() { + connect(process, &QProcess::readyReadStandardOutput, this, [this, packageName, process]() { while (process->canReadLine()) { - QString line = process->readLine().trimmed(); + QString line = QString::fromUtf8(process->readLine()).trimmed(); QRegularExpression regex(R"(\((\d+)%\))"); QRegularExpressionMatch match = regex.match(line); if (match.hasMatch()) { int progress = match.captured(1).toInt(); emit downloadProgress(packageName, progress); - qDebug() << "下载进度:" << progress << "%"; } } }); - // 下载完成 connect(process, QOverload::of(&QProcess::finished), - [this, packageName](int exitCode, QProcess::ExitStatus exitStatus) { - bool success = (exitCode == 0 && exitStatus == QProcess::NormalExit); + this, [this, packageName, outputPath](int exitCode, QProcess::ExitStatus status) { + bool success = (exitCode == 0 && status == QProcess::NormalExit); + if (!success) { + qWarning() << "Download failed for" << packageName << "exit code:" << exitCode; + } + + removeAria2Files(outputPath); // 清理残留 .aria2 emit downloadFinished(packageName, success); - m_processes.remove(packageName); + + QProcess *proc = m_processes.take(packageName); + if (proc) proc->deleteLater(); }); process->start("aria2c", arguments); @@ -46,10 +64,35 @@ void DownloadManager::startDownload(const QString &packageName, const QString &u void DownloadManager::cancelDownload(const QString &packageName) { - if (m_processes.contains(packageName)) { - QProcess *process = m_processes[packageName]; - process->terminate(); - process->waitForFinished(); - m_processes.remove(packageName); + if (!m_processes.contains(packageName)) return; + + QProcess *process = m_processes.take(packageName); + if (process) { + process->kill(); // 立即终止 + process->waitForFinished(3000); // 最多等待3秒 + process->deleteLater(); + } + + emit downloadFinished(packageName, false); // 显式通知取消 + +} + +void DownloadManager::removeAria2Files(const QString &filePath) +{ + QString ariaFile = filePath + ".aria2"; + QFile::remove(ariaFile); +} + +bool DownloadManager::isDownloading(const QString &packageName) const +{ + return m_processes.contains(packageName); +} + +void DownloadManager::cleanupTempFiles() +{ + QDir tempDir(QDir::tempPath()); + QStringList leftovers = tempDir.entryList(QStringList() << "*.aria2", QDir::Files); + for (const QString &f : leftovers) { + tempDir.remove(f); } } diff --git a/src/downloadmanager.h b/src/downloadmanager.h index f47cab3..0614368 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -2,23 +2,26 @@ #define DOWNLOADMANAGER_H #include -#include #include +#include class DownloadManager : public QObject { Q_OBJECT - public: explicit DownloadManager(QObject *parent = nullptr); void startDownload(const QString &packageName, const QString &url, const QString &outputPath); void cancelDownload(const QString &packageName); + bool isDownloading(const QString &packageName) const; signals: void downloadProgress(const QString &packageName, int progress); void downloadFinished(const QString &packageName, bool success); private: + void cleanupTempFiles(); + void removeAria2Files(const QString &filePath); + QMap m_processes; }; From 7da6689e6d70006d8852a51d38c1719ac96a1be8 Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 13 Jun 2025 09:07:25 +0800 Subject: [PATCH 062/126] =?UTF-8?q?chore:=E5=A2=9E=E5=8A=A0=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E6=8C=89=E9=92=AE=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index ccb3eec..dc78577 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -93,6 +93,13 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c progressBarOption.text = QString("%1%").arg(progress); progressBarOption.textVisible = true; QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); + QStyleOptionButton cancelOption; + cancelOption.rect = QRect(progressRect.right() + 10, rect.top() + (rect.height() - 30) / 2, 70, 30); + cancelOption.text = "取消"; + cancelOption.palette.setColor(QPalette::Button, QColor("#ff4444")); + cancelOption.palette.setColor(QPalette::ButtonText, Qt::white); + cancelOption.state = QStyle::State_Enabled; + QApplication::style()->drawControl(QStyle::CE_PushButton, &cancelOption, painter); } else { QStyleOptionButton buttonOption; buttonOption.rect = QRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); From 34b8670a407cde1695e151bba3af61f73754cd9a Mon Sep 17 00:00:00 2001 From: shenmo Date: Sat, 14 Jun 2025 08:36:58 +0000 Subject: [PATCH 063/126] =?UTF-8?q?Revert=20"=E4=BF=AE=E5=A4=8D=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E5=BC=B9=E5=87=BAACE=E6=8F=90=E7=A4=BA=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d5b2d4ddf404facd4aec2ff69755bd3667338832. --- src/widgets/common/downloaditem.cpp | 5 +++-- tool/ssinstall | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/widgets/common/downloaditem.cpp b/src/widgets/common/downloaditem.cpp index 7f1e3ae..45cdc54 100644 --- a/src/widgets/common/downloaditem.cpp +++ b/src/widgets/common/downloaditem.cpp @@ -177,12 +177,13 @@ void DownloadItem::slotAsyncInstall(int t) case 0: { QStringList args; - args << "/tmp/spark-store/" + ui->label_filename->text().toUtf8(); + args << "/usr/local/bin/ssinstall" + << "/tmp/spark-store/" + ui->label_filename->text().toUtf8(); if (!installExtraArg.isEmpty()) { args << installExtraArg; } args << "--delete-after-install"; - installer.start("/usr/local/bin/ssinstall", args); + installer.start("pkexec", args); } break; case 1: diff --git a/tool/ssinstall b/tool/ssinstall index e06c1a6..dee4ff2 100755 --- a/tool/ssinstall +++ b/tool/ssinstall @@ -128,8 +128,9 @@ function parse_args() { # 验证当前用户 function validate_user() { if [ "$(id -u)" != "0" ]; then - pkexec DISPLAY=$DISPLAY "$0" "$@" - exit $? + echo "${TRANSHELL_CONTENT_PLEASE_RUN_AS_ROOT}" + echo "OMG-IT-GOES-WRONG" + exit 1 fi } From 88a157dd45567c784b1292c386a8f1081141909c Mon Sep 17 00:00:00 2001 From: shenmo Date: Sat, 14 Jun 2025 16:41:26 +0800 Subject: [PATCH 064/126] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=B9=E4=B8=8D?= =?UTF-8?q?=E5=87=BA=E6=9D=A5ace=E6=8F=90=E7=A4=BA=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tool/ssaudit | 2 +- tool/ssinstall | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/ssaudit b/tool/ssaudit index 73b0c6a..4909876 100755 --- a/tool/ssaudit +++ b/tool/ssaudit @@ -482,7 +482,7 @@ function main_install() { fi done # 安装ACE环境(示例使用aptss工具,可根据实际情况调整) - aptss install "$ace_pkg" -y + ensure_ace_env "$ace_pkg" -y fi # 再次确认ACE环境命令是否可用 diff --git a/tool/ssinstall b/tool/ssinstall index dee4ff2..e84a186 100755 --- a/tool/ssinstall +++ b/tool/ssinstall @@ -494,7 +494,7 @@ function main_install() { fi done # 安装ACE环境(示例使用aptss工具,可根据实际情况调整) - aptss install "$ace_pkg" -y + ensure_ace_env "$ace_pkg" -y fi # 再次确认ACE环境命令是否可用 From b6de24e0988f325b5c37b0d8becb0d61331cac06 Mon Sep 17 00:00:00 2001 From: momen Date: Sat, 14 Jun 2025 22:34:23 +0800 Subject: [PATCH 065/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0=E2=80=98?= =?UTF-8?q?=E2=80=98=E6=9B=B4=E6=96=B0=E2=80=99=E2=80=99=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index dc78577..f68492f 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -105,6 +105,10 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c buttonOption.rect = QRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); buttonOption.text = "更新"; buttonOption.state = QStyle::State_Enabled; + QPalette palette = buttonOption.palette; + palette.setColor(QPalette::Button, QColor("#e9effd")); // 背景色 + palette.setColor(QPalette::ButtonText, QColor("#2563EB")); // 文字颜色 + buttonOption.palette = palette; QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter); } From 703713a706f7c2cab377e77942adb514aa847863 Mon Sep 17 00:00:00 2001 From: MeowVing Date: Sun, 15 Jun 2025 07:40:34 +0000 Subject: [PATCH 066/126] rename READE.md to README.md. update README.md. Signed-off-by: MeowVing --- READE.md | 20 -------------------- README.md | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 20 deletions(-) delete mode 100644 READE.md create mode 100644 README.md diff --git a/READE.md b/READE.md deleted file mode 100644 index 3259e2d..0000000 --- a/READE.md +++ /dev/null @@ -1,20 +0,0 @@ -### 星火软件更新器 -#### 简介 - -欢迎使用星火软件更新器,你可以用这个更新器更新位于你电脑的程序。 - -#### 当前支持的Linux发行版 -- [x] GXDE -- [x] Ubuntu -- [ ] deepin -- [ ] Kylin - -#### 功能清单 -|功能模块|描述| -|--|--| -| 应用名识别|采用ss-do-upgrade.sh部分代码| -|应用包大小识别|dkpg完成| -|获取应用icon|使用QDesktopServices完成| -|支持ACE| | - -#### 联系与反馈 diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc8a11e --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +### 星火软件更新器 +#### 简介 + +欢迎使用星火软件更新器,您可以使用此更新器更新位于您 Linux 计算机的程序。 + +#### 当前支持的 Linux 发行版 +- [x] GXDE OS +- [x] Ubuntu +- [ ] deepin +- [ ] Kylin + +#### 功能清单 +|功能模块|描述| +|--|--| +|应用名识别|采用 `ss-do-upgrade.sh` 部分代码| +|应用包大小识别|使用 dkpg 完成| +|获取应用 Icon|使用 QDesktopServices 完成| +|支持 ACE 兼容环境| | + +#### 联系与反馈 From e539fb59127c9e97fb7c5c6c2e9532b5ee936dd8 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 15 Jun 2025 16:44:28 +0800 Subject: [PATCH 067/126] =?UTF-8?q?chore:=E5=BC=BA=E5=88=B6=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=87=AA=E5=AE=9A=E4=B9=89=E6=8C=89=E9=92=AE=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index f68492f..c6d6235 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -101,15 +101,15 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c cancelOption.state = QStyle::State_Enabled; QApplication::style()->drawControl(QStyle::CE_PushButton, &cancelOption, painter); } else { - QStyleOptionButton buttonOption; - buttonOption.rect = QRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); - buttonOption.text = "更新"; - buttonOption.state = QStyle::State_Enabled; - QPalette palette = buttonOption.palette; - palette.setColor(QPalette::Button, QColor("#e9effd")); // 背景色 - palette.setColor(QPalette::ButtonText, QColor("#2563EB")); // 文字颜色 - buttonOption.palette = palette; - QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter); + // 修改后的更新按钮绘制代码 + QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#e9effd")); // 背景色 + painter->drawRoundedRect(buttonRect, 4, 4); // 圆角矩形 + + painter->setPen(QColor("#2563EB")); // 文字颜色 + painter->setFont(option.font); + painter->drawText(buttonRect, Qt::AlignCenter, "更新"); } painter->restore(); From 525a90f441b6f477aae9d088637fd62db89352e6 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 15 Jun 2025 16:47:23 +0800 Subject: [PATCH 068/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E6=8C=89=E9=92=AE=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index c6d6235..df89b8c 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -93,15 +93,17 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c progressBarOption.text = QString("%1%").arg(progress); progressBarOption.textVisible = true; QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); - QStyleOptionButton cancelOption; - cancelOption.rect = QRect(progressRect.right() + 10, rect.top() + (rect.height() - 30) / 2, 70, 30); - cancelOption.text = "取消"; - cancelOption.palette.setColor(QPalette::Button, QColor("#ff4444")); - cancelOption.palette.setColor(QPalette::ButtonText, Qt::white); - cancelOption.state = QStyle::State_Enabled; - QApplication::style()->drawControl(QStyle::CE_PushButton, &cancelOption, painter); + + // 修改后的取消按钮绘制代码 + QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#ff4444")); // 红色背景 + painter->drawRoundedRect(buttonRect, 4, 4); // 圆角矩形 + + painter->setPen(Qt::white); // 白色文字 + painter->setFont(option.font); + painter->drawText(buttonRect, Qt::AlignCenter, "取消"); } else { - // 修改后的更新按钮绘制代码 QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); painter->setPen(Qt::NoPen); painter->setBrush(QColor("#e9effd")); // 背景色 From 5211bbb95139c8b00726a3bb4e55eacb01d7a468 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 15 Jun 2025 16:54:21 +0800 Subject: [PATCH 069/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E6=9D=A1=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index df89b8c..913610d 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -84,7 +84,7 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c int progress = m_downloads.value(packageName, DownloadInfo{0, false}).progress; if (isDownloading) { - QRect progressRect(rect.right() - 180, rect.top() + (rect.height() - 20) / 2, 100, 20); + QRect progressRect(rect.right() - 270, rect.top() + (rect.height() - 20) / 2, 150, 20); QStyleOptionProgressBar progressBarOption; progressBarOption.rect = progressRect; progressBarOption.minimum = 0; From 567a24a6ecb5e16ce34858d968b5c5c2b333322e Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 16 Jun 2025 20:39:33 +0800 Subject: [PATCH 070/126] =?UTF-8?q?chore:=E4=B8=8B=E8=BD=BD=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=90=8E=E6=94=B9=E4=B8=BA=E2=80=9C=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E2=80=9D=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index 913610d..57987f9 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -104,14 +104,25 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c painter->setFont(option.font); painter->drawText(buttonRect, Qt::AlignCenter, "取消"); } else { + QString packageName = index.data(Qt::UserRole + 1).toString(); + bool isDownloaded = m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading; + QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); painter->setPen(Qt::NoPen); - painter->setBrush(QColor("#e9effd")); // 背景色 - painter->drawRoundedRect(buttonRect, 4, 4); // 圆角矩形 - painter->setPen(QColor("#2563EB")); // 文字颜色 - painter->setFont(option.font); - painter->drawText(buttonRect, Qt::AlignCenter, "更新"); + if (isDownloaded) { + // 下载完成状态 + painter->setBrush(QColor("#10B981")); // 绿色背景 + painter->drawRoundedRect(buttonRect, 4, 4); + painter->setPen(Qt::white); + painter->drawText(buttonRect, Qt::AlignCenter, "下载完成"); + } else { + // 更新按钮状态 + painter->setBrush(QColor("#e9effd")); // 背景色 + painter->drawRoundedRect(buttonRect, 4, 4); + painter->setPen(QColor("#2563EB")); // 文字颜色 + painter->drawText(buttonRect, Qt::AlignCenter, "更新"); + } } painter->restore(); From 03c53c397760a9850d2f9e2f469aa5a592e7e8c8 Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 16 Jun 2025 21:16:13 +0800 Subject: [PATCH 071/126] =?UTF-8?q?chore:=E6=8F=90=E6=9D=83=E8=BF=90?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.cpp | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index fd3e533..4f4956f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,43 @@ #include "mainwindow.h" - #include +#include +#include +#include // for geteuid +#include // for getenv + +bool isRoot() { + return geteuid() == 0; +} + +bool elevateToRoot() { + QString program = QCoreApplication::applicationFilePath(); + + // 获取当前 DISPLAY 和 XAUTHORITY 环境变量 + QByteArray display = qgetenv("DISPLAY"); + QByteArray xauthority = qgetenv("XAUTHORITY"); + + // 构造带环境变量的 pkexec 命令 + QStringList args; + args << "env" + << "DISPLAY=" + display + << "XAUTHORITY=" + xauthority + << program; + + // 使用 pkexec 启动程序(带环境变量) + return QProcess::startDetached("pkexec", args); +} int main(int argc, char *argv[]) { QApplication a(argc, argv); + + if (!isRoot()) { + if (!elevateToRoot()) { + QMessageBox::critical(nullptr, "权限不足", "提权失败,请确保系统支持 pkexec 并已正确安装 polkit。"); + } + return 0; + } + MainWindow w; w.show(); return a.exec(); From 8177556e5d5a6a0c20952530ff4aa728335aa17b Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 16 Jun 2025 22:39:34 +0800 Subject: [PATCH 072/126] =?UTF-8?q?chore:=E5=AE=8C=E6=88=90=E2=80=9C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=85=A8=E9=83=A8=E2=80=9D=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 15 +++++++++++++++ src/appdelegate.h | 1 + src/mainwindow.cpp | 10 ++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index 57987f9..506506b 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -163,3 +163,18 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, return QStyledItemDelegate::editorEvent(event, model, option, index); } + +void AppDelegate::startDownloadForAll() { + if (!m_model) return; + for (int row = 0; row < m_model->rowCount(); ++row) { + QModelIndex index = m_model->index(row, 0); + QString packageName = index.data(Qt::UserRole + 1).toString(); + if (m_downloads.contains(packageName) && m_downloads[packageName].isDownloading) + continue; // 跳过正在下载的 + QString downloadUrl = index.data(Qt::UserRole + 7).toString(); + QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); + m_downloads[packageName] = {0, true}; + m_downloadManager->startDownload(packageName, downloadUrl, outputPath); + emit updateDisplay(packageName); + } +} diff --git a/src/appdelegate.h b/src/appdelegate.h index 37f7a77..9e4ac3a 100644 --- a/src/appdelegate.h +++ b/src/appdelegate.h @@ -20,6 +20,7 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; + void startDownloadForAll(); // 新增:批量下载所有应用 signals: void updateDisplay(const QString &packageName); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9b20071..f961afc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -14,6 +14,9 @@ MainWindow::MainWindow(QWidget *parent) listView->setModel(m_model); listView->setItemDelegate(m_delegate); + // 新增:确保 delegate 拥有 model 指针 + m_delegate->setModel(m_model); + // 设置 QListView 填充 ui->appWidget QVBoxLayout *layout = new QVBoxLayout(ui->appWidget); layout->addWidget(listView); @@ -27,6 +30,13 @@ MainWindow::MainWindow(QWidget *parent) } } }); + + // 新增:点击“更新全部”按钮批量下载 + connect(ui->updatePushButton, &QPushButton::clicked, this, [=](){ + qDebug()<<"更新全部按钮被点击"; + m_delegate->startDownloadForAll(); + }); + checkUpdates(); initStyle(); } From a9d9f035defae785921b43d7c6eed6dbfe6e49b4 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 17 Jun 2025 22:21:53 +0800 Subject: [PATCH 073/126] =?UTF-8?q?chore:=E5=AE=8C=E6=88=90ssinstall?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 127 ++++++++++++++++++++++++++++++++++++++------ src/appdelegate.h | 16 +++++- 2 files changed, 126 insertions(+), 17 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index 506506b..e873d53 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -9,14 +9,16 @@ #include AppDelegate::AppDelegate(QObject *parent) - : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)) { - + : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)), m_installProcess(nullptr) { connect(m_downloadManager, &DownloadManager::downloadFinished, this, [this](const QString &packageName, bool success) { if (m_downloads.contains(packageName)) { m_downloads[packageName].isDownloading = false; emit updateDisplay(packageName); qDebug() << (success ? "下载完成:" : "下载失败:") << packageName; + if (success) { + enqueueInstall(packageName); + } } }); @@ -28,6 +30,7 @@ AppDelegate::AppDelegate(QObject *parent) emit updateDisplay(packageName); // 实时刷新进度条 } }); + m_spinnerTimer.start(); } void AppDelegate::setModel(QAbstractItemModel *model) { @@ -82,6 +85,8 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c QString packageName = index.data(Qt::UserRole + 1).toString(); bool isDownloading = m_downloads.contains(packageName) && m_downloads[packageName].isDownloading; int progress = m_downloads.value(packageName, DownloadInfo{0, false}).progress; + bool isInstalled = m_downloads.value(packageName).isInstalled; + bool isInstalling = m_downloads.value(packageName).isInstalling; if (isDownloading) { QRect progressRect(rect.right() - 270, rect.top() + (rect.height() - 20) / 2, 150, 20); @@ -103,24 +108,37 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c painter->setPen(Qt::white); // 白色文字 painter->setFont(option.font); painter->drawText(buttonRect, Qt::AlignCenter, "取消"); + } else if (isInstalling) { + // 安装中:显示转圈和文字 + QRect spinnerRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 30, 30); + int angle = (m_spinnerTimer.elapsed() / 10) % 360; + QPen pen(QColor("#2563EB"), 3); + painter->setPen(pen); + painter->setRenderHint(QPainter::Antialiasing, true); + QRectF arcRect = spinnerRect.adjusted(3, 3, -3, -3); + painter->drawArc(arcRect, angle * 16, 120 * 16); // 120度弧 + + QRect textRect(option.rect.right() - 120, option.rect.top() + (option.rect.height() - 30) / 2, 110, 30); + painter->setPen(QColor("#2563EB")); + painter->setFont(option.font); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, "正在安装中"); } else { - QString packageName = index.data(Qt::UserRole + 1).toString(); - bool isDownloaded = m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading; - - QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + QRect buttonRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30); painter->setPen(Qt::NoPen); - - if (isDownloaded) { - // 下载完成状态 - painter->setBrush(QColor("#10B981")); // 绿色背景 + if (isInstalled) { + painter->setBrush(QColor("#10B981")); + painter->drawRoundedRect(buttonRect, 4, 4); + painter->setPen(Qt::white); + painter->drawText(buttonRect, Qt::AlignCenter, "已安装"); + } else if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { + painter->setBrush(QColor("#10B981")); painter->drawRoundedRect(buttonRect, 4, 4); painter->setPen(Qt::white); painter->drawText(buttonRect, Qt::AlignCenter, "下载完成"); } else { - // 更新按钮状态 - painter->setBrush(QColor("#e9effd")); // 背景色 + painter->setBrush(QColor("#e9effd")); painter->drawRoundedRect(buttonRect, 4, 4); - painter->setPen(QColor("#2563EB")); // 文字颜色 + painter->setPen(QColor("#2563EB")); painter->drawText(buttonRect, Qt::AlignCenter, "更新"); } } @@ -169,12 +187,89 @@ void AppDelegate::startDownloadForAll() { for (int row = 0; row < m_model->rowCount(); ++row) { QModelIndex index = m_model->index(row, 0); QString packageName = index.data(Qt::UserRole + 1).toString(); - if (m_downloads.contains(packageName) && m_downloads[packageName].isDownloading) - continue; // 跳过正在下载的 + if (m_downloads.contains(packageName) && (m_downloads[packageName].isDownloading || m_downloads[packageName].isInstalled)) + continue; // 跳过正在下载或已安装的 QString downloadUrl = index.data(Qt::UserRole + 7).toString(); QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); - m_downloads[packageName] = {0, true}; + m_downloads[packageName] = {0, true, false}; m_downloadManager->startDownload(packageName, downloadUrl, outputPath); emit updateDisplay(packageName); } } + +// 新增:安装队列相关实现 +void AppDelegate::enqueueInstall(const QString &packageName) { + m_installQueue.enqueue(packageName); + if (!m_isInstalling) { + startNextInstall(); + } +} + +void AppDelegate::startNextInstall() { + if (m_installQueue.isEmpty()) { + m_isInstalling = false; + m_installingPackage.clear(); + return; + } + m_isInstalling = true; + QString packageName = m_installQueue.dequeue(); + m_installingPackage = packageName; + m_downloads[packageName].isInstalling = true; + emit updateDisplay(packageName); + + // 查找 /tmp 下以包名开头的 .deb 文件 + QDir tempDir(QDir::tempPath()); + QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName), QDir::Files); + QString debPath; + if (!debs.isEmpty()) { + debPath = tempDir.absoluteFilePath(debs.first()); + } else { + debs = tempDir.entryList(QStringList() << QString("%1*.deb").arg(packageName), QDir::Files); + if (!debs.isEmpty()) { + debPath = tempDir.absoluteFilePath(debs.first()); + } + } + + if (debPath.isEmpty()) { + qWarning() << "未找到deb文件,包名:" << packageName; + m_downloads[packageName].isInstalling = false; + emit updateDisplay(packageName); + m_installingPackage.clear(); + startNextInstall(); + return; + } + + m_installProcess = new QProcess(this); + connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName]() { + QByteArray out = m_installProcess->readAllStandardOutput(); + QString text = QString::fromLocal8Bit(out); + qDebug().noquote() << text; + // 检查“软件包已安装”关键字 + if (text.contains(QStringLiteral("软件包已安装"))) { + m_downloads[packageName].isInstalling = false; + m_downloads[packageName].isInstalled = true; + emit updateDisplay(packageName); + } + }); + connect(m_installProcess, &QProcess::readyReadStandardError, this, [this]() { + QByteArray err = m_installProcess->readAllStandardError(); + qDebug().noquote() << QString::fromLocal8Bit(err); + }); + connect(m_installProcess, QOverload::of(&QProcess::finished), + this, [this, packageName](int /*exitCode*/, QProcess::ExitStatus /*status*/) { + // 若未检测到“软件包已安装”,此处兜底 + if (!m_downloads[packageName].isInstalled) { + m_downloads[packageName].isInstalling = false; + } + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + }); + + // 注意参数顺序:deb路径在前,--no-create-desktop-entry在后 + QStringList args; + args << debPath << "--no-create-desktop-entry"; + m_installProcess->start("ssinstall", args); +} diff --git a/src/appdelegate.h b/src/appdelegate.h index 9e4ac3a..0545155 100644 --- a/src/appdelegate.h +++ b/src/appdelegate.h @@ -2,11 +2,16 @@ #include #include +#include +#include +#include #include "downloadmanager.h" struct DownloadInfo { int progress = 0; bool isDownloading = false; + bool isInstalled = false; + bool isInstalling = false; // 新增:标记是否正在安装 }; class AppDelegate : public QStyledItemDelegate { @@ -20,7 +25,7 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; - void startDownloadForAll(); // 新增:批量下载所有应用 + void startDownloadForAll(); signals: void updateDisplay(const QString &packageName); @@ -29,4 +34,13 @@ private: DownloadManager *m_downloadManager; QHash m_downloads; QAbstractItemModel *m_model = nullptr; + + QQueue m_installQueue; + bool m_isInstalling = false; + QProcess *m_installProcess = nullptr; + QString m_installingPackage; // 当前正在安装的包名 + QElapsedTimer m_spinnerTimer; // 用于转圈动画 + + void enqueueInstall(const QString &packageName); + void startNextInstall(); }; From f925ac72424c7cb0b2f6ad73b5a194fbd3bce142 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 18 Jun 2025 20:47:48 +0800 Subject: [PATCH 074/126] =?UTF-8?q?update:=E4=BF=9D=E5=AD=98=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=88=B0/tmp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/appdelegate.cpp | 91 +++++++++++++++++++++++++++++------------ src/downloadmanager.cpp | 41 ++++++++++++++----- 2 files changed, 95 insertions(+), 37 deletions(-) diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index e873d53..315dcdf 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -240,33 +240,72 @@ void AppDelegate::startNextInstall() { } m_installProcess = new QProcess(this); - connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName]() { - QByteArray out = m_installProcess->readAllStandardOutput(); - QString text = QString::fromLocal8Bit(out); - qDebug().noquote() << text; - // 检查“软件包已安装”关键字 - if (text.contains(QStringLiteral("软件包已安装"))) { - m_downloads[packageName].isInstalling = false; - m_downloads[packageName].isInstalled = true; + + // 新增:准备安装日志文件 + QString logPath = QString("/tmp/%1_install.log").arg(packageName); + QFile *logFile = new QFile(logPath, m_installProcess); + if (logFile->open(QIODevice::Append | QIODevice::Text)) { + // 设置权限为777 + QFile::setPermissions(logPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | + QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | + QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); + connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName, logFile]() { + QByteArray out = m_installProcess->readAllStandardOutput(); + logFile->write(out); + logFile->flush(); + QString text = QString::fromLocal8Bit(out); + qDebug().noquote() << text; + // 检查“软件包已安装”关键字 + if (text.contains(QStringLiteral("软件包已安装"))) { + m_downloads[packageName].isInstalling = false; + m_downloads[packageName].isInstalled = true; + emit updateDisplay(packageName); + } + }); + connect(m_installProcess, &QProcess::readyReadStandardError, this, [this, logFile]() { + QByteArray err = m_installProcess->readAllStandardError(); + logFile->write(err); + logFile->flush(); + qDebug().noquote() << QString::fromLocal8Bit(err); + }); + connect(m_installProcess, QOverload::of(&QProcess::finished), + this, [this, packageName, logFile](int /*exitCode*/, QProcess::ExitStatus /*status*/) { + if (logFile) logFile->close(); + // 若未检测到“软件包已安装”,此处兜底 + if (!m_downloads[packageName].isInstalled) { + m_downloads[packageName].isInstalling = false; + } emit updateDisplay(packageName); - } - }); - connect(m_installProcess, &QProcess::readyReadStandardError, this, [this]() { - QByteArray err = m_installProcess->readAllStandardError(); - qDebug().noquote() << QString::fromLocal8Bit(err); - }); - connect(m_installProcess, QOverload::of(&QProcess::finished), - this, [this, packageName](int /*exitCode*/, QProcess::ExitStatus /*status*/) { - // 若未检测到“软件包已安装”,此处兜底 - if (!m_downloads[packageName].isInstalled) { - m_downloads[packageName].isInstalling = false; - } - emit updateDisplay(packageName); - m_installProcess->deleteLater(); - m_installProcess = nullptr; - m_installingPackage.clear(); - startNextInstall(); - }); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + }); + } else { + // 日志文件无法打开时,仍然要连接原有信号 + connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName]() { + QByteArray out = m_installProcess->readAllStandardOutput(); + QString text = QString::fromLocal8Bit(out); + qDebug().noquote() << text; + if (text.contains(QStringLiteral("软件包已安装"))) { + m_downloads[packageName].isInstalling = false; + m_downloads[packageName].isInstalled = true; + emit updateDisplay(packageName); + } + }); + connect(m_installProcess, &QProcess::readyReadStandardError, this, [this]() { + QByteArray err = m_installProcess->readAllStandardError(); + qDebug().noquote() << QString::fromLocal8Bit(err); + }); + connect(m_installProcess, QOverload::of(&QProcess::finished), + this, [this, packageName](int /*exitCode*/, QProcess::ExitStatus /*status*/) { + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + }); + } // 注意参数顺序:deb路径在前,--no-create-desktop-entry在后 QStringList args; diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 271b7bf..9780324 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -33,20 +33,37 @@ void DownloadManager::startDownload(const QString &packageName, const QString &u QProcess *process = new QProcess(this); m_processes.insert(packageName, process); - connect(process, &QProcess::readyReadStandardOutput, this, [this, packageName, process]() { - while (process->canReadLine()) { - QString line = QString::fromUtf8(process->readLine()).trimmed(); - QRegularExpression regex(R"(\((\d+)%\))"); - QRegularExpressionMatch match = regex.match(line); - if (match.hasMatch()) { - int progress = match.captured(1).toInt(); - emit downloadProgress(packageName, progress); + // 新增:准备日志文件 + QString logPath = QString("/tmp/%1_download.log").arg(packageName); + QFile *logFile = new QFile(logPath, process); + if (logFile->open(QIODevice::Append | QIODevice::Text)) { + // 设置权限为777 + QFile::setPermissions(logPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | + QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | + QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); + connect(process, &QProcess::readyReadStandardOutput, this, [this, packageName, process, logFile]() { + while (process->canReadLine()) { + QString line = QString::fromUtf8(process->readLine()).trimmed(); + // 写入日志 + logFile->write(line.toUtf8() + '\n'); + logFile->flush(); + QRegularExpression regex(R"(\((\d+)%\))"); + QRegularExpressionMatch match = regex.match(line); + if (match.hasMatch()) { + int progress = match.captured(1).toInt(); + emit downloadProgress(packageName, progress); + } } - } - }); + }); + connect(process, &QProcess::readyReadStandardError, this, [process, logFile]() { + QByteArray err = process->readAllStandardError(); + logFile->write(err); + logFile->flush(); + }); + } connect(process, QOverload::of(&QProcess::finished), - this, [this, packageName, outputPath](int exitCode, QProcess::ExitStatus status) { + this, [this, packageName, outputPath, logFile](int exitCode, QProcess::ExitStatus status) { bool success = (exitCode == 0 && status == QProcess::NormalExit); if (!success) { qWarning() << "Download failed for" << packageName << "exit code:" << exitCode; @@ -55,6 +72,8 @@ void DownloadManager::startDownload(const QString &packageName, const QString &u removeAria2Files(outputPath); // 清理残留 .aria2 emit downloadFinished(packageName, success); + if (logFile) logFile->close(); + QProcess *proc = m_processes.take(packageName); if (proc) proc->deleteLater(); }); From 0607df0967067b539d04038bedccbf798b46d110 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 18 Jun 2025 21:14:23 +0800 Subject: [PATCH 075/126] =?UTF-8?q?fix:=E6=9B=B4=E6=AD=A3=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=AE=8C=E6=88=90=E5=90=8E=E5=86=8D=E6=AC=A1=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E6=8C=89=E9=92=AE=E6=97=B6=E9=87=8D=E6=96=B0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DownloadButton.jsx | 0 src/appdelegate.cpp | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 DownloadButton.jsx diff --git a/DownloadButton.jsx b/DownloadButton.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/appdelegate.cpp b/src/appdelegate.cpp index 315dcdf..1a1c3e9 100644 --- a/src/appdelegate.cpp +++ b/src/appdelegate.cpp @@ -131,10 +131,12 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c painter->setPen(Qt::white); painter->drawText(buttonRect, Qt::AlignCenter, "已安装"); } else if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { + // 下载完成,按钮绿色,样式不变 painter->setBrush(QColor("#10B981")); painter->drawRoundedRect(buttonRect, 4, 4); painter->setPen(Qt::white); painter->drawText(buttonRect, Qt::AlignCenter, "下载完成"); + // 不需要特殊处理样式,交互在 editorEvent 控制 } else { painter->setBrush(QColor("#e9effd")); painter->drawRoundedRect(buttonRect, 4, 4); @@ -168,6 +170,11 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, } else { QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); if (buttonRect.contains(mouseEvent->pos())) { + // 判断是否为“下载完成”状态,如果是则不响应点击 + if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { + // “下载完成”状态,按钮失效,点击无效 + return false; + } QString downloadUrl = index.data(Qt::UserRole + 7).toString(); QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); From 8e617ceda46dbbe4fe9a89c05b26cb9b1c7db8d3 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 18 Jun 2025 21:27:24 +0800 Subject: [PATCH 076/126] =?UTF-8?q?chore:=E4=BD=BF=E7=94=A8aptss=20update?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 22 ++++++++++++++++++++++ src/mainwindow.h | 1 + 2 files changed, 23 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f961afc..ec1be40 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,5 +1,7 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -7,6 +9,8 @@ MainWindow::MainWindow(QWidget *parent) , m_model(new AppListModel(this)) , m_delegate(new AppDelegate(this)) { + runAptssUpgrade(); + ui->setupUi(this); // 创建 QListView 并设置父控件为 ui->appWidget @@ -177,6 +181,24 @@ void MainWindow::checkUpdates() } } +void MainWindow::runAptssUpgrade() +{ + QProcess process; + QStringList args; + args << "aptss" << "upgrade"; + process.start("sudo", args); + if (!process.waitForStarted(5000)) { + QMessageBox::warning(this, "升级失败", "无法启动 sudo aptss upgrade。"); + return; + } + process.write("n\n"); + process.closeWriteChannel(); + process.waitForFinished(-1); + if (process.exitCode() != 0) { + QMessageBox::warning(this, "升级失败", "执行 sudo aptss upgrade 失败,请检查系统环境。"); + } +} + MainWindow::~MainWindow() { delete ui; diff --git a/src/mainwindow.h b/src/mainwindow.h index 9cb6af8..e36db88 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -25,6 +25,7 @@ private: Ui::MainWindow *ui; void checkUpdates(); void initStyle(); + void runAptssUpgrade(); AppListModel *m_model; AppDelegate *m_delegate; QListView *listView; // 声明 QListView 指针 From b5e0b709cc46f1a8347d6b090a3e3f5b1d7f6773 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 18 Jun 2025 22:04:40 +0800 Subject: [PATCH 077/126] =?UTF-8?q?fix:=E5=88=A0=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 0 DownloadButton.jsx | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 CMakeLists.txt delete mode 100644 DownloadButton.jsx diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index e69de29..0000000 diff --git a/DownloadButton.jsx b/DownloadButton.jsx deleted file mode 100644 index e69de29..0000000 From b68029f5db8bdd933973484592e16098977a1226 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 18 Jun 2025 22:17:56 +0800 Subject: [PATCH 078/126] =?UTF-8?q?chore:=E5=BC=82=E6=AD=A5=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt | 6 ++-- src/mainwindow.cpp | 76 +++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f7f76af..10e7b56 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,8 +9,8 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network Concurrent) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Concurrent) set(PROJECT_SOURCES main.cpp @@ -47,7 +47,7 @@ else() endif() endif() -target_link_libraries(Spark-Update-Tool PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) +target_link_libraries(Spark-Update-Tool PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Concurrent) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # If you are developing for iOS or macOS you should consider setting an diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ec1be40..3b01b23 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2,6 +2,9 @@ #include "./ui_mainwindow.h" #include #include +#include +#include // 新增 +#include // 新增 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -9,40 +12,57 @@ MainWindow::MainWindow(QWidget *parent) , m_model(new AppListModel(this)) , m_delegate(new AppDelegate(this)) { - runAptssUpgrade(); + QProgressDialog *progressDialog = new QProgressDialog("正在与服务器通信,获取更新信息中...", QString(), 0, 0, this); + progressDialog->setWindowModality(Qt::ApplicationModal); + progressDialog->setCancelButton(nullptr); + progressDialog->setWindowTitle("请稍候"); + progressDialog->setMinimumDuration(0); + progressDialog->setWindowFlags(progressDialog->windowFlags() & ~Qt::WindowCloseButtonHint); // 禁用关闭按钮 + progressDialog->show(); + //异步执行runAptssUpgrade + QFutureWatcher *watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, [=]() { + progressDialog->close(); + progressDialog->deleteLater(); + watcher->deleteLater(); + ui->setupUi(this); - ui->setupUi(this); + // 创建 QListView 并设置父控件为 ui->appWidget + listView = new QListView(ui->appWidget); + listView->setModel(m_model); + listView->setItemDelegate(m_delegate); - // 创建 QListView 并设置父控件为 ui->appWidget - listView = new QListView(ui->appWidget); - listView->setModel(m_model); - listView->setItemDelegate(m_delegate); + // 新增:确保 delegate 拥有 model 指针 + m_delegate->setModel(m_model); - // 新增:确保 delegate 拥有 model 指针 - m_delegate->setModel(m_model); - - // 设置 QListView 填充 ui->appWidget - QVBoxLayout *layout = new QVBoxLayout(ui->appWidget); - layout->addWidget(listView); - layout->setContentsMargins(0, 0, 0, 0); - connect(m_delegate, &AppDelegate::updateDisplay, this, [=](const QString &packageName) { - for (int i = 0; i < m_model->rowCount(); ++i) { - QModelIndex index = m_model->index(i); - if (index.data(Qt::UserRole + 1).toString() == packageName) { - m_model->dataChanged(index, index); // 刷新该行 - break; + // 设置 QListView 填充 ui->appWidget + QVBoxLayout *layout = new QVBoxLayout(ui->appWidget); + layout->addWidget(listView); + layout->setContentsMargins(0, 0, 0, 0); + connect(m_delegate, &AppDelegate::updateDisplay, this, [=](const QString &packageName) { + for (int i = 0; i < m_model->rowCount(); ++i) { + QModelIndex index = m_model->index(i); + if (index.data(Qt::UserRole + 1).toString() == packageName) { + m_model->dataChanged(index, index); // 刷新该行 + break; + } } - } + }); + + // 新增:点击“更新全部”按钮批量下载 + connect(ui->updatePushButton, &QPushButton::clicked, this, [=](){ + qDebug()<<"更新全部按钮被点击"; + m_delegate->startDownloadForAll(); + }); + + checkUpdates(); + initStyle(); }); - // 新增:点击“更新全部”按钮批量下载 - connect(ui->updatePushButton, &QPushButton::clicked, this, [=](){ - qDebug()<<"更新全部按钮被点击"; - m_delegate->startDownloadForAll(); - }); - - checkUpdates(); - initStyle(); + // 启动异步任务 + watcher->setFuture(QtConcurrent::run([this](){ + runAptssUpgrade(); + })); } //初始化控件样式 void MainWindow::initStyle() From 196ae84eb5a079775d74280563604029652f259f Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 19 Jun 2025 00:35:38 +0800 Subject: [PATCH 079/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0debian=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 57 ++++++++++++++++++++++++++++ src/CMakeLists.txt => CMakeLists.txt | 38 +++++++++---------- debian/changelog | 5 +++ debian/compat | 1 + debian/control | 12 ++++++ debian/copyright | 7 ++++ debian/install | 2 + debian/momodesk-mind.desktop | 9 +++++ debian/postrm | 11 ++++++ debian/rules | 7 ++++ debian/source/format | 1 + spark-update-tool.desktop | 9 +++++ src/mainwindow.ui | 1 - 13 files changed, 140 insertions(+), 20 deletions(-) rename src/CMakeLists.txt => CMakeLists.txt (69%) create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/install create mode 100644 debian/momodesk-mind.desktop create mode 100644 debian/postrm create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 spark-update-tool.desktop diff --git a/.gitignore b/.gitignore index 75ea44d..79c9ac7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,60 @@ build .cache CMakeLists.txt.user CMakeLists.txt.user.* +obj-x86_64-linux-gnu +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* + +# Qt unit tests +target_wrapper.* + +# Qt qm files +translations/*.qm + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* +build + +# Debian dpkg-buildpackage +debian/*.debhelper* +debian/files +debian/*.substvars +debian/spark-update-tool + +.vscode/* +src/spark-update-tool diff --git a/src/CMakeLists.txt b/CMakeLists.txt similarity index 69% rename from src/CMakeLists.txt rename to CMakeLists.txt index 10e7b56..6c624b4 100644 --- a/src/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -project(Spark-Update-Tool VERSION 0.1 LANGUAGES CXX) +project(spark-update-tool VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) @@ -13,49 +13,49 @@ find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network Concurrent) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Concurrent) set(PROJECT_SOURCES - main.cpp - mainwindow.cpp - mainwindow.h - mainwindow.ui + src/main.cpp + src/mainwindow.cpp + src/mainwindow.h + src/mainwindow.ui ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) - qt_add_executable(Spark-Update-Tool + qt_add_executable(spark-update-tool MANUAL_FINALIZATION ${PROJECT_SOURCES} - aptssupdater.h aptssupdater.cpp - icons.qrc - appdelegate.h appdelegate.cpp - applistmodel.h applistmodel.cpp - downloadmanager.h downloadmanager.cpp + src/aptssupdater.h src/aptssupdater.cpp + src/icons.qrc + src/appdelegate.h src/appdelegate.cpp + src/applistmodel.h src/applistmodel.cpp + src/downloadmanager.h src/downloadmanager.cpp ) # Define target properties for Android with Qt 6 as: -# set_property(TARGET Spark-Update-Tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# set_property(TARGET spark-update-tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR # ${CMAKE_CURRENT_SOURCE_DIR}/android) # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation else() if(ANDROID) - add_library(Spark-Update-Tool SHARED + add_library(spark-update-tool SHARED ${PROJECT_SOURCES} ) # Define properties for Android with Qt 5 after find_package() calls as: # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") else() - add_executable(Spark-Update-Tool + add_executable(spark-update-tool ${PROJECT_SOURCES} ) endif() endif() -target_link_libraries(Spark-Update-Tool PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Concurrent) +target_link_libraries(spark-update-tool PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Concurrent) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # If you are developing for iOS or macOS you should consider setting an # explicit, fixed bundle identifier manually though. if(${QT_VERSION} VERSION_LESS 6.1.0) - set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.Spark-Update-Tool) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.spark-update-tool) endif() -set_target_properties(Spark-Update-Tool PROPERTIES +set_target_properties(spark-update-tool PROPERTIES ${BUNDLE_ID_OPTION} MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} @@ -64,12 +64,12 @@ set_target_properties(Spark-Update-Tool PROPERTIES ) include(GNUInstallDirs) -install(TARGETS Spark-Update-Tool +install(TARGETS spark-update-tool BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) if(QT_VERSION_MAJOR EQUAL 6) - qt_finalize_executable(Spark-Update-Tool) + qt_finalize_executable(spark-update-tool) endif() diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..de17899 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +spark-update-tool (1.0.0-1) unstable; urgency=low + + * Initial release. + + -- momen Wed, 18 Jun 2025 00:00:00 +0000 \ No newline at end of file diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f11c82a --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..35ec34c --- /dev/null +++ b/debian/control @@ -0,0 +1,12 @@ +Source: spark-update-tool +Section: utils +Priority: optional +Maintainer: momen +Build-Depends: debhelper (>= 9),qt6-base-dev +Standards-Version: 3.9.6 +Homepage: https://gitee.com/spark-store-project/Spark-Update-Tool + +Package: spark-update-tool +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: A Qt-based application for managing tasks and reminders. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..163489e --- /dev/null +++ b/debian/copyright @@ -0,0 +1,7 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: spark-update-tool +Source: https://gitee.com/spark-store-project/Spark-Update-Tool + +Files: * +Copyright: 2025, momen +License: GPL-3.0+ \ No newline at end of file diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..62266e3 --- /dev/null +++ b/debian/install @@ -0,0 +1,2 @@ +build/spark-update-tool /usr/bin/ +spark-update-tool.desktop /usr/share/applications diff --git a/debian/momodesk-mind.desktop b/debian/momodesk-mind.desktop new file mode 100644 index 0000000..2ef7139 --- /dev/null +++ b/debian/momodesk-mind.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Name=spark-update-tool +Comment=A Qt-based application for managing tasks and reminders +Exec=/usr/bin/spark-update-tool +Icon=/usr/share/spark-update-tool/img/momen.jpg +Terminal=false +Type=Application +Categories=Utility; \ No newline at end of file diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..263e9c4 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +case "$1" in + purge) + + rm -rf /usr/share/spark-update-tool + ;; +esac + +exit 0 \ No newline at end of file diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..1159ac4 --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f + +%: + dh $@ + +override_dh_auto_configure: + dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/usr diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..9f67427 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) \ No newline at end of file diff --git a/spark-update-tool.desktop b/spark-update-tool.desktop new file mode 100644 index 0000000..688ba0c --- /dev/null +++ b/spark-update-tool.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Name=Spark Update Tool +Comment=A Qt-based application for managing and updating software +Exec=spark-update-tool +Icon=spark-update-tool +Terminal=false +Type=Application +Categories=Utility; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index f0aace0..1feb345 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -191,7 +191,6 @@ - PreferNoHinting From b261d6433a1136386b919583dd41677f29799111 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 19 Jun 2025 21:37:59 +0800 Subject: [PATCH 080/126] =?UTF-8?q?docs:=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fc8a11e..e6b20a5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ 欢迎使用星火软件更新器,您可以使用此更新器更新位于您 Linux 计算机的程序。 +此版本专为仅有qt5的Linux发行版所使用。 #### 当前支持的 Linux 发行版 - [x] GXDE OS - [x] Ubuntu From 1e8fcf9bb0625581587ffd75f7cb807febb3cf66 Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 20 Jun 2025 23:48:21 +0800 Subject: [PATCH 081/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0readme?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fc8a11e..b06078c 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,4 @@ |支持 ACE 兼容环境| | #### 联系与反馈 +momen@momen.world \ No newline at end of file From f37d73708b024e31c713b51f7744fd3ef517180f Mon Sep 17 00:00:00 2001 From: momen Date: Sat, 21 Jun 2025 23:01:13 +0800 Subject: [PATCH 082/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E6=8F=90?= =?UTF-8?q?=E6=9D=83=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 4f4956f..71c172d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include // for geteuid #include // for getenv +#include // For debugging output bool isRoot() { return geteuid() == 0; @@ -11,10 +12,13 @@ bool isRoot() { bool elevateToRoot() { QString program = QCoreApplication::applicationFilePath(); + qDebug() << "Current application path:" << program; // 获取当前 DISPLAY 和 XAUTHORITY 环境变量 QByteArray display = qgetenv("DISPLAY"); QByteArray xauthority = qgetenv("XAUTHORITY"); + qDebug() << "DISPLAY:" << display; + qDebug() << "XAUTHORITY:" << xauthority; // 构造带环境变量的 pkexec 命令 QStringList args; @@ -23,22 +27,72 @@ bool elevateToRoot() { << "XAUTHORITY=" + xauthority << program; - // 使用 pkexec 启动程序(带环境变量) - return QProcess::startDetached("pkexec", args); + QProcess *process = new QProcess(); + process->setProgram("pkexec"); + process->setArguments(args); + + qDebug() << "Attempting to elevate using pkexec with arguments:" << args; + + // 启动进程并等待完成 + process->start(); + // 等待 pkexec 完成,通常会弹出密码框 + // 设置一个合理的超时时间,例如 30 秒 + if (!process->waitForFinished(30000)) { + qDebug() << "pkexec process timed out."; + process->kill(); // 如果超时,终止进程 + delete process; + return false; + } + + int exitCode = process->exitCode(); + QProcess::ExitStatus exitStatus = process->exitStatus(); + + qDebug() << "pkexec exit code:" << exitCode; + qDebug() << "pkexec exit status:" << exitStatus; + qDebug() << "pkexec standard error:" << process->readAllStandardError(); + + // 检查 pkexec 是否成功 + // pkexec 成功执行通常返回 0,或者如果用户取消授权,会返回非零值。 + // 但是,如果 pkexec 本身执行失败(例如找不到命令),exitStatus 会是 CrashOrKilled + // 这里我们认为只要pkexec成功启动了,即使授权失败,也算pkexec命令执行成功了, + // 因为这表示pkexec本身是可用的。 + // 只有当pkexec命令无法执行时,才认为是pkexec的问题 + bool pkexecCommandExecuted = (exitStatus == QProcess::NormalExit && exitCode == 0); + + delete process; + return pkexecCommandExecuted; } int main(int argc, char *argv[]) { + // 必须在 QGuiApplication 实例创建之前调用 + // QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); + QApplication a(argc, argv); if (!isRoot()) { + qDebug() << "Not running as root. Attempting to elevate..."; if (!elevateToRoot()) { - QMessageBox::critical(nullptr, "权限不足", "提权失败,请确保系统支持 pkexec 并已正确安装 polkit。"); + qDebug() << "Elevation failed or pkexec command was not executed successfully."; + QMessageBox::critical(nullptr, + "权限不足", + "提权失败!\n\n您的系统可能不支持 `pkexec` 或 `polkit` 配置不正确," + "或者您取消了授权。\n\n请尝试使用 `sudo` 命令运行此程序:" + "\n\n在终端中输入:\n`sudo " + QCoreApplication::applicationName() + "`"); + return 0; // 提权失败,退出程序 + } else { + // 如果 elevateToRoot 返回 true,说明 pkexec 命令本身执行成功 + // 但这并不意味着原始程序以 root 权限启动了 + // 因为 elevateToRoot 启动的是一个新的进程,当前进程应该退出 + // 否则会并行运行两个程序实例 + qDebug() << "pkexec command executed successfully (new process likely started). Exiting current process."; + return 0; // 当前非root进程退出 } - return 0; + } else { + qDebug() << "Running as root."; } MainWindow w; w.show(); return a.exec(); -} +} \ No newline at end of file From 9fdf0051a1e42958a544fac676ee45d17d278666 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 22 Jun 2025 10:33:14 +0800 Subject: [PATCH 083/126] =?UTF-8?q?chore:cmakelist.txt=E4=BD=BF=E7=94=A8qt?= =?UTF-8?q?5,=E4=BB=A5=E6=AD=A4=E6=9D=A5=E6=94=AF=E6=8C=81=E6=97=A7?= =?UTF-8?q?=E7=89=88=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 75 +++++++++++++++++++------------------------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c624b4..49a4d5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,58 +9,44 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network Concurrent) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Concurrent) +find_package(Qt5 REQUIRED COMPONENTS Widgets Network Concurrent) + + +find_package(Qt5 REQUIRED COMPONENTS Widgets Network Concurrent Core Gui) + +# 定义所有项目源文件,现在无需条件判断 set(PROJECT_SOURCES - src/main.cpp - src/mainwindow.cpp - src/mainwindow.h - src/mainwindow.ui + src/main.cpp + src/mainwindow.cpp + src/mainwindow.h + src/mainwindow.ui + src/aptssupdater.h src/aptssupdater.cpp + src/icons.qrc + src/appdelegate.h src/appdelegate.cpp + src/applistmodel.h src/applistmodel.cpp + src/downloadmanager.h src/downloadmanager.cpp ) -if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) - qt_add_executable(spark-update-tool - MANUAL_FINALIZATION - ${PROJECT_SOURCES} - src/aptssupdater.h src/aptssupdater.cpp - src/icons.qrc - src/appdelegate.h src/appdelegate.cpp - src/applistmodel.h src/applistmodel.cpp - src/downloadmanager.h src/downloadmanager.cpp - ) -# Define target properties for Android with Qt 6 as: -# set_property(TARGET spark-update-tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR -# ${CMAKE_CURRENT_SOURCE_DIR}/android) -# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation -else() - if(ANDROID) - add_library(spark-update-tool SHARED - ${PROJECT_SOURCES} - ) -# Define properties for Android with Qt 5 after find_package() calls as: -# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") - else() - add_executable(spark-update-tool - ${PROJECT_SOURCES} - ) - endif() -endif() -target_link_libraries(spark-update-tool PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Concurrent) +add_executable(spark-update-tool + ${PROJECT_SOURCES} +) + +target_link_libraries(spark-update-tool PRIVATE + Qt5::Widgets + Qt5::Network + Qt5::Concurrent + Qt5::Core + Qt5::Gui +) + -# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. -# If you are developing for iOS or macOS you should consider setting an -# explicit, fixed bundle identifier manually though. -if(${QT_VERSION} VERSION_LESS 6.1.0) - set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.spark-update-tool) -endif() set_target_properties(spark-update-tool PROPERTIES - ${BUNDLE_ID_OPTION} + # ${BUNDLE_ID_OPTION} # 如果上面取消注释,这里也取消注释 MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - MACOSX_BUNDLE TRUE - WIN32_EXECUTABLE TRUE + ) include(GNUInstallDirs) @@ -70,6 +56,3 @@ install(TARGETS spark-update-tool RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -if(QT_VERSION_MAJOR EQUAL 6) - qt_finalize_executable(spark-update-tool) -endif() From 68d7f427512fb0da4de21692055ab95c783b7910 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 22 Jun 2025 15:26:04 +0800 Subject: [PATCH 084/126] =?UTF-8?q?fix:=E6=8F=90=E6=9D=83=E5=90=8E30s?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=98=BE=E7=A4=BA=E6=8F=90=E6=9D=83=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.cpp | 44 ++++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 71c172d..c834b96 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,53 +14,41 @@ bool elevateToRoot() { QString program = QCoreApplication::applicationFilePath(); qDebug() << "Current application path:" << program; - // 获取当前 DISPLAY 和 XAUTHORITY 环境变量 QByteArray display = qgetenv("DISPLAY"); QByteArray xauthority = qgetenv("XAUTHORITY"); - qDebug() << "DISPLAY:" << display; - qDebug() << "XAUTHORITY:" << xauthority; - // 构造带环境变量的 pkexec 命令 QStringList args; args << "env" << "DISPLAY=" + display << "XAUTHORITY=" + xauthority << program; - QProcess *process = new QProcess(); - process->setProgram("pkexec"); - process->setArguments(args); + QProcess process; + process.setProgram("pkexec"); + process.setArguments(args); qDebug() << "Attempting to elevate using pkexec with arguments:" << args; - // 启动进程并等待完成 - process->start(); - // 等待 pkexec 完成,通常会弹出密码框 - // 设置一个合理的超时时间,例如 30 秒 - if (!process->waitForFinished(30000)) { - qDebug() << "pkexec process timed out."; - process->kill(); // 如果超时,终止进程 - delete process; + process.start(); + if (!process.waitForStarted(5000)) { + qDebug() << "Failed to start pkexec."; return false; } - int exitCode = process->exitCode(); - QProcess::ExitStatus exitStatus = process->exitStatus(); + // 阻塞等待提权进程退出(比如主程序窗口关闭) + if (!process.waitForFinished(-1)) { // 等待直到新进程退出 + qDebug() << "pkexec process waitForFinished failed."; + return false; + } + + int exitCode = process.exitCode(); + QProcess::ExitStatus exitStatus = process.exitStatus(); qDebug() << "pkexec exit code:" << exitCode; qDebug() << "pkexec exit status:" << exitStatus; - qDebug() << "pkexec standard error:" << process->readAllStandardError(); + qDebug() << "pkexec stderr:" << process.readAllStandardError(); - // 检查 pkexec 是否成功 - // pkexec 成功执行通常返回 0,或者如果用户取消授权,会返回非零值。 - // 但是,如果 pkexec 本身执行失败(例如找不到命令),exitStatus 会是 CrashOrKilled - // 这里我们认为只要pkexec成功启动了,即使授权失败,也算pkexec命令执行成功了, - // 因为这表示pkexec本身是可用的。 - // 只有当pkexec命令无法执行时,才认为是pkexec的问题 - bool pkexecCommandExecuted = (exitStatus == QProcess::NormalExit && exitCode == 0); - - delete process; - return pkexecCommandExecuted; + return (exitStatus == QProcess::NormalExit && exitCode == 0); } int main(int argc, char *argv[]) From 35d1e8b198b085075745c7a0cba9ce5685547b4a Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 23 Jun 2025 12:21:58 +0800 Subject: [PATCH 085/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0debian?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian/changelog | 2 +- debian/control | 2 +- debian/install | 2 +- debian/momodesk-mind.desktop | 9 --------- .../spark-update-tool.desktop | 0 5 files changed, 3 insertions(+), 12 deletions(-) delete mode 100644 debian/momodesk-mind.desktop rename spark-update-tool.desktop => debian/spark-update-tool.desktop (100%) diff --git a/debian/changelog b/debian/changelog index de17899..b414fea 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -spark-update-tool (1.0.0-1) unstable; urgency=low +spark-update-tool (1.0.0) unstable; urgency=low * Initial release. diff --git a/debian/control b/debian/control index 35ec34c..b250b67 100644 --- a/debian/control +++ b/debian/control @@ -9,4 +9,4 @@ Homepage: https://gitee.com/spark-store-project/Spark-Update-Tool Package: spark-update-tool Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} -Description: A Qt-based application for managing tasks and reminders. +Description: 星火应用商店更新插件 diff --git a/debian/install b/debian/install index 62266e3..fdab2a3 100644 --- a/debian/install +++ b/debian/install @@ -1,2 +1,2 @@ build/spark-update-tool /usr/bin/ -spark-update-tool.desktop /usr/share/applications +debian/spark-update-tool.desktop /usr/share/applications diff --git a/debian/momodesk-mind.desktop b/debian/momodesk-mind.desktop deleted file mode 100644 index 2ef7139..0000000 --- a/debian/momodesk-mind.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Version=1.0 -Name=spark-update-tool -Comment=A Qt-based application for managing tasks and reminders -Exec=/usr/bin/spark-update-tool -Icon=/usr/share/spark-update-tool/img/momen.jpg -Terminal=false -Type=Application -Categories=Utility; \ No newline at end of file diff --git a/spark-update-tool.desktop b/debian/spark-update-tool.desktop similarity index 100% rename from spark-update-tool.desktop rename to debian/spark-update-tool.desktop From f300dd1f4deb2da9692942c26a20c0d33849c958 Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 23 Jun 2025 12:33:01 +0800 Subject: [PATCH 086/126] =?UTF-8?q?chore:=E7=A7=BB=E5=8A=A8=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E6=96=87=E4=BB=B6=E5=88=B0=E6=A0=B9=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {src/resources => resources}/default_icon.svg | 0 {src/resources => resources}/down_arrow.svg | 0 src/icons.qrc | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename {src/resources => resources}/default_icon.svg (100%) rename {src/resources => resources}/down_arrow.svg (100%) diff --git a/src/resources/default_icon.svg b/resources/default_icon.svg similarity index 100% rename from src/resources/default_icon.svg rename to resources/default_icon.svg diff --git a/src/resources/down_arrow.svg b/resources/down_arrow.svg similarity index 100% rename from src/resources/down_arrow.svg rename to resources/down_arrow.svg diff --git a/src/icons.qrc b/src/icons.qrc index cce5944..9f749df 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -1,6 +1,6 @@ - resources/down_arrow.svg - resources/default_icon.svg + ../resources/down_arrow.svg + ../resources/default_icon.svg From c1923ad6afbbf84a1f3b527ed36c77d37476c354 Mon Sep 17 00:00:00 2001 From: MeowVing Date: Mon, 23 Jun 2025 05:24:38 +0000 Subject: [PATCH 087/126] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MeowVing --- resources/spark-update-tool.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 resources/spark-update-tool.svg diff --git a/resources/spark-update-tool.svg b/resources/spark-update-tool.svg new file mode 100644 index 0000000..3d21d81 --- /dev/null +++ b/resources/spark-update-tool.svg @@ -0,0 +1 @@ + \ No newline at end of file From cc087030fd8369b16dafa7740756d5b5ab83e98c Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 23 Jun 2025 13:14:50 +0800 Subject: [PATCH 088/126] =?UTF-8?q?chore:=E5=8E=BB=E9=99=A4=E5=85=A8?= =?UTF-8?q?=E9=80=89=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 1feb345..2a58287 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -49,7 +49,7 @@ - Qt::Orientation::Horizontal + Qt::Horizontal @@ -144,7 +144,7 @@ - Qt::Orientation::Horizontal + Qt::Horizontal @@ -190,14 +190,13 @@ - - + - Qt::LayoutDirection::LeftToRight + Qt::LeftToRight - QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow + QComboBox::AdjustToContentsOnFirstShow @@ -223,31 +222,11 @@ - - - - - 0 - 20 - - - - - - 0 - 0 - 96 - 24 - - - - 全选 - - - - + + true + 0 From c36492cd0b1b50d91a26a5fa7465a66144e16dac Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 23 Jun 2025 14:09:02 +0800 Subject: [PATCH 089/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0control?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian/control | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index b250b67..19c9d62 100644 --- a/debian/control +++ b/debian/control @@ -2,11 +2,12 @@ Source: spark-update-tool Section: utils Priority: optional Maintainer: momen -Build-Depends: debhelper (>= 9),qt6-base-dev +Build-Depends: debhelper (>= 9) Standards-Version: 3.9.6 Homepage: https://gitee.com/spark-store-project/Spark-Update-Tool Package: spark-update-tool Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} -Description: 星火应用商店更新插件 +Description: Spark Update Tool + 星火应用商店更新组件。This package provides the Spark Update Tool. It includes features for checking for updates, downloading, and applying them seamlessly. \ No newline at end of file From 27e41f35f0c89514d5a08c9249bdde5353d0e533 Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 23 Jun 2025 21:56:59 +0800 Subject: [PATCH 090/126] =?UTF-8?q?chore:=E8=AE=BE=E7=BD=AE=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian/install | 1 + src/icons.qrc | 1 + 2 files changed, 2 insertions(+) diff --git a/debian/install b/debian/install index fdab2a3..ac99078 100644 --- a/debian/install +++ b/debian/install @@ -1,2 +1,3 @@ build/spark-update-tool /usr/bin/ debian/spark-update-tool.desktop /usr/share/applications +resources/spark-update-tool.svg /usr/share/icons/hicolor/scalable/apps/spark-update-tool.svg \ No newline at end of file diff --git a/src/icons.qrc b/src/icons.qrc index 9f749df..0209761 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -2,5 +2,6 @@ ../resources/down_arrow.svg ../resources/default_icon.svg + ../resources/spark-update-tool.svg From 6f89f8b69489f437911b50f192f031f23afda74f Mon Sep 17 00:00:00 2001 From: MeowVing Date: Mon, 23 Jun 2025 15:34:37 +0000 Subject: [PATCH 091/126] =?UTF-8?q?!348=20=E6=9B=B4=E6=96=B0=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E5=9B=BE=E6=A0=87=EF=BC=9B=E6=9B=B4=E6=96=B0=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=AD=E8=8B=B1=E6=96=87=20README=20?= =?UTF-8?q?=E4=B8=8E=20FAQ=20=E6=96=87=E6=A1=A3=E7=9A=84=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E8=A1=A8=E8=BF=B0=20*=20=E6=9B=B4=E6=96=B0=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=20*=20=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20pkg/usr/share/ico?= =?UTF-8?q?ns/hicolor/scalable/apps/open-me-in-terminal.png=20*=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE=E6=A0=87=20*=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=96=87=E4=BB=B6=20pkg/usr/share/icons/hicolor/scalable/apps/?= =?UTF-8?q?spark-store.svg=20*=20=E6=9B=B4=E6=96=B0=E5=9B=BE=E6=A0=87=20*?= =?UTF-8?q?=20=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20pkg/usr/share/icons/h?= =?UTF-8?q?icolor/scalable/apps/spark-store.svg=20*=20update=20README.md.?= =?UTF-8?q?=20*=20update=20README.zh.md.=20*=20update=20FAQ.md.=20*=20upda?= =?UTF-8?q?te=20FAQ.md.=20*=20update=20FAQ.zh.md.=20*=20update=20FAQ.zh.md?= =?UTF-8?q?.=20*=20update=20FAQ.zh.md.=20*=20update=20FAQ.zh.md.=20*=20upd?= =?UTF-8?q?ate=20FAQ.zh.md.=20*=20update=20README.md.=20*=20update=20READM?= =?UTF-8?q?E.md.=20*=20update=20README.zh.md.=20*=20update=20README.zh.md.?= =?UTF-8?q?=20*=20update=20README.zh.md.=20*=20update=20README.zh.md.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FAQ.md | 92 +++---- FAQ.zh.md | 83 +++--- README.md | 170 ++++++------ README.zh.md | 94 +++---- .../scalable/apps/open-me-in-terminal.png | Bin 91331 -> 258729 bytes .../hicolor/scalable/apps/spark-store.svg | 255 +----------------- 6 files changed, 228 insertions(+), 466 deletions(-) diff --git a/FAQ.md b/FAQ.md index 8eb13a8..8488789 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,91 +1,91 @@ # FAQ -## Spark App Store FAQ and Support Guide +## Spark Store FAQ and Support Guide ### Introduction -The Spark App Store is an application store aimed at Linux users and supports multiple Linux distributions. Below are some commonly asked questions and solutions. Please note that this document is primarily intended for ordinary users who are not familiar with Linux and the APT package management system. +The Spark Store is an application store designed for Linux users, supporting various Linux distributions. Below are some frequently asked questions and their solutions. Please note that this document primarily targets ordinary users unfamiliar with Linux and the `apt` package management system. --- ### Communication and Feedback -**Q: Where can I join the community group?** -**A:** You can click [here](https://bbs.spark-app.store/) to enter the main communication platform of the Spark Store. -> ⚠️ If the above link doesn't work, you can also join our QQ groups as an alternative. The group numbers are 872690351 and 865927727. We also offer forums for communication. +**Q: How can I participate in discussions?** +**A:** You can click [this link](https://bbs.spark-app.store/) to enter the Spark Forum for communication. +> 📣 You can also join our QQ Discussion Groups: 872690351 and 865927727. -#### Differences Between arm64 and amd64 +--- -arm64 +### Difference Between arm64 and amd64 -* Full Name: Advanced RISC Machine 64-bit -* Main Use: Primarily used in mobile devices, embedded systems, and some new servers and desktop computers. -* Advantages: Low power consumption, high efficiency, suitable for battery-driven and heat-sensitive devices. -* Main Manufacturers: Apple, Qualcomm, HiSilicon, Phytium, etc. +#### arm64 -amd64 +* Full Name: Advanced RISC Machine 64-bit +* Primary Use: Mainly applied to mobile devices, embedded systems, and some new servers and desktop computers +* Advantages: Low power consumption, high efficiency, suitable for battery-powered and heat-sensitive devices +* Major Manufacturers: Apple, Qualcomm, HiSilicon, Phytium, etc. -* Full Name: AMD 64-bit, also known as x86_64 -* Main Use: Primarily used in desktop computers, laptops, and servers. -* Advantages: High performance, suitable for computation-intensive applications. -* Main Manufacturers: Intel and AMD. -* The main differences between the two are in the instruction sets and application scenarios. amd64 is usually used for high-performance computers and servers, while arm64 is more commonly used in power-sensitive settings. +#### amd64 + +* Full Name: AMD 64-bit, also known as x86_64 +* Primary Use: Mainly applied to desktop computers, laptops, and servers +* Advantages: High performance, suitable for compute-intensive applications +* Major Manufacturers: Intel and AMD + +The main difference between the two lies in their instruction sets and application scenarios. amd64 is typically used for high-performance computers and servers, while arm64 is more commonly used in environments with strict power consumption requirements. #### How to Check: -Linux: - -- Open the terminal. -- Type `uname -m` or `arch` and press Enter. -- You will see your processor architecture (possibly x86_64, aarch64, etc.). +- Open the Linux terminal +- Enter the command `uname -m` or `arch` and press `Enter` +- You will see your computer's processor architecture (e.g., x86_64, aarch64) --- ### Domestic Architecture Support -**Q: I use a domestic chip architecture, how can I get applications?** -**A:** Currently, Spark Store supports **arm** architecture for domestic chips. You can download deb packages suitable for arm64 architecture. -> ⚠️ Please note that this is experimental support. If you encounter any issues, please provide feedback via Spark's communication platform, QQ group, or forums. +**Q: My computer uses a domestic architecture. How can I obtain the Spark Store?** +**A:** Currently, the Spark Store supports **ARM** architecture and **LoongArch New World** architecture. You can download deb packages (software installation packages) for arm64 and loong64 architectures. +> ⚠️ Please note that this support is experimental. If you encounter issues, please report them to us via the Spark Forum or QQ Discussion Group. --- -### Dependency Issues -**Q: I get errors when installing dependencies** -**A:** If you are using UOS or deepin, please do not install the dependency packages. For Kali Linux/Kdeneon etc., temporary support is not available, please compile and install yourself. -> 💡 If errors occur, try running `sudo apt update` before attempting the installation again. If the issue persists, refer to the first question and seek help on the community platform. +### Dependency Installation Issues +**Q: What if dependency package installation fails?** +**A:** If you are using UOS or deepin, do not install dependencies. Only some Linux distributions require dependency packages. For unsupported distributions, you may need to compile manually. See the [Spark Store Introduction](https://gitee.com/spark-store-project/spark-store/blob/dev/README.md). +> 💡 When errors occur, run `sudo apt update` first and try again. If the issue persists, refer to the above and seek help via communication platforms. --- -### Submission and App Updates -**Q: Where can I submit applications?** -**A:** You can find the "Submit Application" option in the upper-right corner menu of the app store interface. +### Software Submission and Updates +**Q: Where can I submit software?** +**A:** You can find the "Submit Application" option in the top-right menu of the app store interface. We provide two submission methods: **Submission Tools** and **Online Platform**. --- ### Support for Non-deepin/UOS Users -**Q: I'm not a deepin/UOS user; can I use the Spark App Store?** -**A:** Yes, you can directly install if you're using Ubuntu 22.04. For Ubuntu 20.04/Debian10/Debian11, please install the dependencies first. +**Q: Can I use Spark Store if I am not a deepin/UOS user?** +**A:** It can be installed normally on Debian-based distributions. For example, if you use Ubuntu 22.04, install directly. For Ubuntu 20.04 / Debian 10 / Debian 11, install dependencies first. Additionally, experience may not be guaranteed if installed on non-Debian Linux distributions. --- ### Installation Methods -**Q: Can I use dpkg -i for installation?** -**A:** No! Use `sudo apt install ./xxxx.deb` to install, or use graphical installers like gdebi. -> ⚠️ Using dpkg directly will not handle software dependencies, which is a common mistake. +**Q: Can I install using `dpkg -i`?** +**A:** We **strongly advise against this**. In the directory where the package is saved, use `sudo apt install ./spark-store*.deb` or a graphical installer like Gdebi. +> ⚠️ Direct use of `dpkg` generally does not handle software dependencies, potentially causing installation failures or damaging the system's dependency environment. --- ### System Updates -**Q: Will the Spark Store affect normal system updates?** -**A:** No, the Spark Store has separated its application sources from system sources and will not affect regular system updates. +**Q: Will Spark Store affect normal system updates?** +**A:** No. The current version of Spark Store has decoupled its application sources from system sources and will not affect normal system updates. --- -### Reporting Issues and Removing Apps -**Q: Some apps are outdated or inactive, and I want them removed** -**A:** You can report issues [here](https://gitee.com/spark-store-project/software_-issue). +### Reporting Issues and Application Removal +**Q: Some applications are outdated or invalid. I want them removed.** +**A:** You can [report issues here](https://gitee.com/spark-store-project/software_-issue). --- -### Installing Spark App Store -If you wish to install the Spark App Store, please visit the [Release page](https://gitee.com/spark-store-project/spark-store/releases), find the latest version, and choose the package suitable for your current system. - -> ⚠️ Special Note: If you are using Debian10/Debian11 or Ubuntu 20.04, you may need to download additional dependency packages. After downloading, unzip multiple times until you see the software user guide. For operating systems later than Ubuntu 22.04, you do not need to install dependency packages. Just follow the steps above to install the main program directly. +### Installing Spark Store +If you need to install Spark Store, visit the [Release Page](https://gitee.com/spark-store-project/spark-store/releases), find the latest version, and download the installer suitable for your system. +> ⚠️ Special Note: If you are using Debian 10 / Debian 11 or Ubuntu 20.04, you need to download the dependency supplementary package additionally. After downloading, extract multiple times until dependency instructions are visible, then follow steps to install dependencies. For Ubuntu 22.04 and later, no dependency package is required. Follow the above instructions to install the main program directly. --- \ No newline at end of file diff --git a/FAQ.zh.md b/FAQ.zh.md index 1c45e07..0032052 100644 --- a/FAQ.zh.md +++ b/FAQ.zh.md @@ -1,90 +1,91 @@ # FAQ -## 星火应用商店FAQ与支持指南 +## 星火应用商店 FAQ 与支持指南 ### 简介 -星火应用商店是一个面向 Linux 用户的应用商店,支持多种不同的 Linux 发行版。以下是一些常见问题及解决方案。请注意,该文档主要面向对 Linux 和 APT 软件包管理系统不熟悉的普通用户。 +星火应用商店是一个面向 Linux 用户的应用商店,支持多种不同的 Linux 发行版。以下是一些常见问题及解决方案。请注意,该文档主要面向对 Linux 和 `apt` 软件包管理系统不熟悉的普通用户。 --- ### 交流与反馈 -**Q: 在哪进交流群?** -**A:** 你可以点击[这里](https://bbs.spark-app.store/)进入星火商店的主交流平台。 -> ⚠️ 如果上面的链接无法使用,你还可以加入我们的 QQ 群作为备选方案,群号是 872690351 和 865927727。我们还提供论坛作为交流平台。 +**Q: 如何参与交流讨论?** +**A:** 您可以点击[此链接](https://bbs.spark-app.store/)进入星火社区论坛进行交流。 +> 📣 您还可以加入我们的 QQ 交流群,群号是 872690351 和 865927727。 -arm64与amd64的区别 +--- -arm64 +### arm64 与 amd64 的区别 -* 全称: Advanced RISC Machine 64-bit -* 主要用途: 主要应用于移动设备、嵌入式系统以及一些新型的服务器和桌面计算机。 -* 优势: 低功耗、高效率,适合用在电池驱动和热敏感的设备。 +#### arm64 + +* 全称: Advanced RISC Machine 64-bit; +* 主要用途: 主要应用于移动设备、嵌入式系统以及一些新型的服务器和桌面计算机; +* 优势: 低功耗、高效率,适合用在电池驱动和热敏感的设备; * 主要生产商: 苹果、高通、海思、飞腾等。 -amd64 +#### amd64 -* 全称: AMD 64-bit, 也被称为 x86_64 -* 主要用途: 主要应用于桌面计算机、笔记本以及服务器。 -* 优势: 高性能,适用于计算密集型应用。 -* 主要生产商: Intel和AMD。 -* 两者最主要的不同在于指令集和应用场景。amd64通常用于高性能需求的计算机和服务器,而arm64则更多应用于功耗要求更严格的场合。 +* 全称: AMD 64-bit, 也被称为 x86_64; +* 主要用途: 主要应用于桌面计算机、笔记本以及服务器; +* 优势: 高性能,适用于计算密集型应用; +* 主要生产商: Intel 和 AMD。 -查看方法: +两者最主要的不同在于指令集和应用场景。amd64 通常用于高性能需求的计算机和服务器,而arm64则更多应用于功耗要求更严格的场合。 -Linux: +#### 查看方法: -- 打开终端。 -- 输入 uname -m 或 arch 并按回车。 -- 你将看到你的处理器架构(可能是 x86_64, aarch64 等)。 +- 打开 Linux 终端; +- 输入命令 `uname -m` 或 `arch` 并按 `Enter` 键执行; +- 您将看到您计算机的处理器架构(可能是 x86_64, aarch64 等)。 --- ### 国产架构支持 -**Q: 我是国产架构,怎么获取应用?** -**A:** 目前,星火商店支持**arm**架构的国产芯片。你可以下载适用于 arm64 架构的deb包(软件安装包)。 -> ⚠️ 请注意,这是实验性的支持。如果遇到问题,请在星火交流平台、QQ群或论坛向我们反馈。 +**Q: 我的计算机属于国产架构,如何获取星火应用商店?** +**A:** 目前,星火商店支持 **ARM** 架构和**龙芯·新世界**架构的国产芯片。您可以下载适用于 arm64 架构和 loong64 架构的 deb 包(软件安装包)。 +> ⚠️ 请注意,这项支持是实验性的。若遇到问题,请在星火社区论坛或 QQ 交流群向我们反馈。 --- ### 安装依赖问题 -**Q: 安装依赖包出现错误** -**A:** 如果你使用UOS或deepin,请不要安装依赖包。对于Kali Linux/Kdeneon等,暂时不支持,请自行编译安装。 -> 💡 出现错误时,尝试运行 `sudo apt update` 后再尝试安装。如果问题仍然存在,请参考第一条,进入交流平台寻求帮助。 +**Q: 安装依赖包出现错误,怎么办?** +**A:** 如果您使用 UOS 或 deepin,请不要安装依赖包。仅有部分 Linux 发行版需要安装依赖包,此外对于不在支持列表中的发行版可能需要您自行编译安装,详见[星火应用商店简介](https://gitee.com/spark-store-project/spark-store/blob/dev/README.zh.md)。 +> 💡 出现错误时,请运行 `sudo apt update` 后再尝试安装。如果问题仍然存在,请参考上文,进入交流平台寻求帮助。 --- ### 投稿与应用更新 -**Q: 在哪里投稿?** -**A:** 你可以在应用商店界面的右上角菜单找到“投递应用”的选项。 +**Q: 在哪里可以投稿软件?** +**A:** 您可以在应用商店界面的右上角菜单找到“投递应用”的选项,我们提供了**投递工具**和**在线平台**两种投稿方案。 --- -### 非deepin/UOS用户支持 -**Q: 我不是deepin/UOS用户,可以使用星火应用商店吗?** -**A:** 可以,如果你是使用 Ubuntu 22.04,请直接安装。对于 Ubuntu 20.04/Debian10/Debian11,请先安装依赖包。 +### 非 deepin/UOS 用户支持 +**Q: 我不是 deepin/UOS 用户,可以使用星火应用商店吗?** +**A:** 在 Debian 系发行版可正常安装使用。例如,如果您使用 Ubuntu 22.04,请直接安装;对于 Ubuntu 20.04 / Debian 10 / Debian 11,请先安装依赖包。此外,如果您在非 Debian 系 Linux 发行版安装使用星火应用商店,软件的体验可能无法得到保证。 --- ### 安装方法 -**Q: 我可以用dpkg -i安装吗?** -**A:** 不可以!使用 `sudo apt install ./xxxx.deb` 来安装,或者使用gdebi等图形化的安装器。 -> ⚠️ 直接使用dpkg是不会处理软件依赖的,这是一个常见的错误。 +**Q: 我可以用 `dpkg -i` 安装吗?** +**A:** 我们**极不建议您这样做**。请在安装包保存目录下,使用 `sudo apt install ./spark-store*.deb` 安装,或使用 Gdebi 等图形化安装器。 +> ⚠️ 直接使用 `dpkg` 一般不会处理软件依赖,可能致使安装失败、破坏系统依赖环境。 --- ### 系统更新 -**Q: 星火商店会影响系统正常更新吗?** -**A:** 不会,星火商店已经将应用源与系统源分开,不会影响系统的正常更新。 +**Q: 星火应用商店会影响系统正常更新吗?** +**A:** 不会,当前版本的星火应用商店已将应用源与系统源解耦合,不会影响系统的正常更新。 --- ### 报告问题和应用下架 -**Q: 有些应用已经过时或者失效了,我想让他下架** -**A:** 你可以前往 [这里](https://gitee.com/spark-store-project/software_-issue) 报告问题。 +**Q: 有些应用已经过时或者失效了,我想让它下架。** +**A:** 您可以[前往这里](https://gitee.com/spark-store-project/software_-issue)报告问题。 --- ### 安装星火应用商店 -如果你想安装星火应用商店,请打开[Release页面](https://gitee.com/spark-store-project/spark-store/releases),找到最新版本,并选择适用于当前系统的安装包下载。 +如果您需要安装星火应用商店,请打开 [Release 页面](https://gitee.com/spark-store-project/spark-store/releases),找到最新版本,并选择适用于当前系统的安装包下载。 -> ⚠️ 特别提示: 如果你在使用 Debian10/Debian11 或 Ubuntu 20.04,你可能额外下载依赖补充包。您需要在下载完依赖包后,解压多次一直到看到内部的软件使用说明,对于 Ubuntu22.04 以后的操作系统版本,您无需安装依赖包,您需要按照上面的操作直接安装本体程序。 +> ⚠️ 特别提示: 如果您在使用 Debian 10 / Debian 11 或 Ubuntu 20.04,则需要额外下载依赖补充包;您需要在下载完依赖包后,解压多次直至内部的依赖包使用说明可见,按照步骤安装依赖。对于 Ubuntu 22.04 以后的操作系统版本,您无需安装依赖包,按照上面的操作直接安装本体程序即可。 --- \ No newline at end of file diff --git a/README.md b/README.md index 655ed3c..822e122 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,52 @@ -# Spark App Store -[![star](https://gitee.com/spark-store-project/spark-store/badge/star.svg?theme=gvp)](https://gitee.com/spark-store-project/spark-store/stargazers) [![fork](https://gitee.com/spark-store-project/spark-store/badge/fork.svg?theme=gvp)](https://gitee.com/spark-store-project/spark-store/members) +# Spark Store +[![star](https://gitee.com/spark-store-project/spark-store/badge/star.svg?theme=gvp)](https://gitee.com/spark-store-project/spark-store/stargazers) [![fork](https://gitee.com/spark-store-project/spark-store/badge/fork.svg?theme=gvp)](https://gitee.com/spark-store-project/spark-store/members) +![star](https://gitcode.com/spark-store-project/spark-store/star/badge.svg) -## Introduction -Welcome to the Spark App Store! This is an app store designed for Linux users, aiming to solve the problem of dispersed and difficult-to-obtain applications in the Linux ecosystem. No matter what type of Linux distribution you are using, you may find suitable software packages here. +## Introduction -The number of Linux applications is relatively limited, and the availability of Wine software is also quite challenging. Excellent development and tool resources are scattered across various communities and forums, making it difficult for the entire ecosystem to improve comprehensively. +Welcome to the **Spark Store**! This is an application store designed for Linux platform users, aiming to address the issues of fragmented and difficult-to-obtain software within the Linux ecosystem. Regardless of your Linux distribution, you may find suitable software packages here. -The construction of the ecosystem is not dependent on the isolated efforts of individuals but requires the participation of the entire community. Only when everyone's "sparks" gather together can they ignite a "prairie fire." +The number of Linux applications is relatively limited, and obtaining Wine software can also be challenging. Excellent development kits and tool resources are scattered across various communities and forums, making it difficult for the entire ecosystem to achieve comprehensive improvement. -To improve this situation, we have launched this app store. We have broadly included various user-demand software packages, gathered high-quality tools, and actively adapted Wine applications, all stored in our software library for easy user access. +Ecosystem construction does not rely on isolated individual efforts but requires full community participation. Only when everyone's "spark" gathers in one place can it ignite a prairie fire. -**Currently supported Linux distributions include:** +To improve this situation, we have launched the Spark Store. The Spark Community extensively collects software packages for various user needs, aggregates high-quality small tools, and proactively adapts Wine applications. All resources are stored in our repository, allowing users to conveniently access these applications. -- **amd64 architecture:** deepin 20 / deepin 23 / Ubuntu 20.04 / Ubuntu 22.04 / UOS Home Edition 20 -- **arm64 architecture:** UOS Professional Edition 1060 / Ubuntu 22.04 / deepin 23 -- **loong64 architecture:** deepin 23 +**Currently supported Linux distributions include (but are not limited to):** -> Special Note: We also support all versions of Ubuntu operating systems higher than Ubuntu 22.04, such as Ubuntu 22.10, 23.04, 23.10, etc. +- **amd64 Architecture:** deepin 20 / deepin 23 / Ubuntu 20.04 / Ubuntu 22.04 / UOS Home Edition 20 / Debian 11+ +- **arm64 Architecture:** UOS Professional Edition 1060 / Ubuntu 22.04 / deepin 23 +- **loong64 Architecture:** deepin 23 -**Important Notice:** This software does not provide any form of warranty. If you plan to use it on UOS Professional Edition, please make sure to understand and enable Developer Mode. Make sure you have basic troubleshooting capabilities. It should be clear that we have not conducted extensive testing on the UOS operating system. Therefore, using the Spark client may lead to a series of issues like failed system updates, data loss, etc., all risks to be borne by the user. +> Special Note: Spark Store also supports all Ubuntu OS versions newer than Ubuntu 22.04, such as Ubuntu 22.10, 23.04, 23.10, etc. Additionally, Spark Store may adapt to other Linux distributions beyond the listed platforms; you may conduct installation tests accordingly. -## Team Collaboration: For detailed documentation related to branch management, please see [this link](https://wiki.spark-app.store/#/Dev/Spark-Store-Git-Repo). +**Important Notice:** This software cannot guarantee continuous availability, uninterrupted operation, or meeting specific performance requirements. The Spark community makes no commitment to the functional completeness, stability, or error-free operation of this software. For example, if you plan to use it on UOS Professional Edition (or other similar specific platforms), please ensure you understand and enable the "Developer Mode" features. Basic troubleshooting capabilities are required. It is important to note that the Spark community cannot conduct extensive testing on some special platforms. Therefore, using the Spark Store client on these platforms may lead to various issues such as system update failures or data loss. Using this software indicates your understanding and acceptance that all risks must be borne by the user. -We warmly welcome you to join our development team. Whether you want to participate in development or submit applications, you can find your place here to jointly promote the development of the Linux application ecosystem. +## About Team Collaboration -You can track our Issue handling status in real-time through the following link: [Issue Board](https://gitee.com/spark-store-project/spark-store/board). +Organization Repository Links: -If you have a software package you'd like to submit, please [click here to submit](https://upload.deepinos.org.cn/index). +https://gitee.com/spark-store-project/spark-store -## Contents +https://gitcode.com/spark-store-project/spark-store -- [Read the copyright](#read-the-copyright) -- [Determine your system architecture](#determine-your-system-architecture) -- [System support and installation guide](#system-support-and-installation-guide) - - [For Deepin / UOS Users](#for-deepin-users) +https://github.com/spark-store-project/spark-store + +Refer to [this link](https://wiki.spark-app.store/#/Dev/Spark-Store-Git-Repo) for detailed documentation on branch management. + +We warmly welcome you to join our development team. Whether you wish to participate in development or plan to submit applications, you can find your place here to jointly promote the development of the Linux application ecosystem. + +You can track our Issue processing status in real-time via the [Issue Board](https://gitee.com/spark-store-project/spark-store/board). + +If you have software packages to submit, please [click here to contribute](https://wiki.spark-app.store/#/Submit/Submit). + +## Table of Contents + +- [Read the Copyright Notice](#read-the-copyright-notice) +- [Determine Your System Architecture](#determine-your-system-architecture) +- [System Support and Installation Instructions](#system-support-and-installation-instructions) + - [For deepin Users](#for-deepin-users) - [For Ubuntu Users](#for-ubuntu-users) - [For Debian Users](#for-debian-users) - [Frequently Asked Questions (FAQ)](#frequently-asked-questions-faq) @@ -42,126 +54,126 @@ If you have a software package you'd like to submit, please [click here to submi --- -## Read [the copyright](LICENSE) +## Read the [Copyright Notice](LICENSE) +## Determine Your System Architecture -## Determine your system architecture +Before installing any software, you need to know which architecture your computer is running on (e.g., x86_64/amd64 or aarch64/arm64), which also applies to the Spark Store. -Before installing any software, you need to know what architecture your computer runs on (such as x86_64/amd64 or aarch64/arm64). +**How to Check:** -**How to check:** +1. Open the Linux terminal. +2. Enter the command `uname -m` or `arch` and press `Enter`. -1. Open a Linux terminal. -2. Type `uname -m` or `arch` and press Enter. +You will see the terminal output result, which helps determine your system architecture. Examples: -You will see an output, and that's your system architecture. +- If you see `x86_64`, your system is amd64 architecture. +- If you see `aarch64`, your system is arm64 architecture. -- If you see `x86_64`, your system is AMD64 architecture. -- If you see `aarch64`, your system is ARM64 architecture. +Based on this result, you need to download the corresponding version of the Spark Store installer to use it properly. --- -## System support and installation guide +## System Support and Installation Instructions -### For Deepin Users +Based on your Linux distribution and system architecture, here are detailed steps to install Spark Store. -#### For Deepin Users +### For deepin Users + +#### For deepin Users 1. **Download and Install** - You can directly search for Spark App Store in the Deepin Store to install, or copy this link to open and install in your browser: + You can directly search for `Spark Store` in the deepin App Store for installation, or copy this link to your browser address bar to jump to the deepin App Store installation page. > appstore://deepin-home-appstore-client?app_detail_info/spark-store - To use the latest version, please visit the [Spark App Store Release page](https://gitee.com/spark-store-project/spark-store/releases) and download the latest version suitable for Deepin. Install to use. + Versions published in the deepin App Store may not be the latest. To use the latest version, visit the Spark Store [Gitee Release Page](https://gitee.com/spark-store-project/spark-store/releases) or [Gitcode Release Page](https://gitcode.com/spark-store-project/spark-store/releases) and download the latest version for deepin, then install it. - Assuming you've downloaded to the `Downloads` folder in your user directory, we recommend you use the APT tool to install: + Assuming you downloaded the installer to the Downloads folder under your home directory, we recommend using the `apt` tool for installation to avoid potential dependency issues: - ```shell - cd ~/Downloads - sudo apt install ./spark-store*.deb - ``` + ```shell + cd ~/Downloads + sudo apt install ./spark-store*.deb + ``` ### For Ubuntu Users #### For Ubuntu 20.04 Users 1. **Download Dependencies** -* Please visit the [Spark App Store Dependency Download page](https://gitee.com/spark-store-project/spark-store-dependencies/releases) and download the latest dependency package. -* Unzip the dependency package multiple times until you see multiple installation packages ending with `.deb`. -* Follow the instructions in the dependency package to install all the dependencies at once. + * Visit the [Spark Store Dependency Package Download Page](https://gitee.com/spark-store-project/spark-store-dependencies/releases) to download the latest dependency package; + * Extract the dependency package multiple times until you see numerous `.deb` installation packages; + * Follow the instructions inside the dependency package to install all dependencies at once. 2. **Download and Install** - Visit the [Spark App Store Release page](https://gitee.com/spark-store-project/spark-store/releases), download the installation package that matches your computer's architecture, and install it. + Visit the Spark Store [Gitee Release Page](https://gitee.com/spark-store-project/spark-store/releases) or [Gitcode Release Page](https://gitcode.com/spark-store-project/spark-store/releases) to download and install the installer matching your computer's architecture. - Assuming you've downloaded to the `Downloads` folder in your user directory, we recommend you use the APT tool to install: + Assuming you downloaded the installer to the Downloads folder under your home directory, we recommend using the `apt` tool for installation to avoid potential dependency issues: - ```shell - cd ~/Downloads - sudo apt install ./spark-store*.deb - ``` + ```shell + cd ~/Downloads + sudo apt install ./spark-store*.deb + ``` -#### For Ubuntu 22.04 and Higher Version Users - -1. **No Need to Install Dependencies** +#### For Ubuntu 22.04 and Newer Ubuntu Versions +1. **No Dependency Package Required** 2. **Download and Install** - Visit the [Spark App Store Release page](https://gitee.com/spark-store-project/spark-store/releases), download the installation package that matches your computer's architecture, and install it. + Visit the Spark Store [Gitee Release Page](https://gitee.com/spark-store-project/spark-store/releases) or [Gitcode Release Page](https://gitcode.com/spark-store-project/spark-store/releases) to download and install the installer matching your computer's architecture. - Assuming you've downloaded to the `Downloads` folder in your user directory, we recommend you use the APT tool to install: + Assuming you downloaded the installer to the Downloads folder under your home directory, we recommend using the `apt` tool for installation to avoid potential dependency issues: - ```shell - cd ~/Downloads - sudo apt install ./spark-store*.deb - ``` + ```shell + cd ~/Downloads + sudo apt install ./spark-store*.deb + ``` ### For Debian Users #### For Debian 11 Users 1. **Download Dependencies** -* Please visit the [Spark App Store Dependency Download page](https://gitee.com/spark-store-project/spark-store-dependencies/releases) and download the latest dependency package. -* Unzip the dependency package multiple times until you see multiple installation packages ending with `.deb`. -* Follow the instructions in the dependency package to install all the dependencies at once. + * Visit the [Spark Store Dependency Package Download Page](https://gitee.com/spark-store-project/spark-store-dependencies/releases) to download the latest dependency package; + * Extract the dependency package multiple times until you see numerous `.deb` installation packages; + * Follow the instructions inside the dependency package to install all dependencies at once. 2. **Download and Install** - Please visit the [Spark App Store Release page](https://gitee.com/spark-store-project/spark-store/releases) and download. Install to use. + Visit the Spark Store [Gitee Release Page](https://gitee.com/spark-store-project/spark-store/releases) or [Gitcode Release Page](https://gitcode.com/spark-store-project/spark-store/releases) to download and install the installer. #### For Debian 12+ Users -1. **No Need to Install Dependencies** +1. **No Dependency Package Required** 2. **Download and Install** - Please visit the [Spark App Store Release page](https://gitee.com/spark-store-project/spark-store/releases), download the installation package that matches your computer's architecture, and install it. + Visit the Spark Store [Gitee Release Page](https://gitee.com/spark-store-project/spark-store/releases) or [Gitcode Release Page](https://gitcode.com/spark-store-project/spark-store/releases) to download and install the installer matching your computer's architecture. - Assuming you've downloaded to the `Downloads` folder in your user directory, we recommend you use the APT tool to install: + Assuming you downloaded the installer to the Downloads folder under your home directory, we recommend using the `apt` tool for installation to avoid potential dependency issues: - ```shell - cd ~/Downloads - sudo apt install ./spark-store*.deb - ``` + ```shell + cd ~/Downloads + sudo apt install ./spark-store*.deb + ``` --- ## Frequently Asked Questions (FAQ) -Please refer to the [Spark App Store FAQ and Support Guide](https://gitee.com/spark-store-project/spark-store/blob/dev/FAQ.md). - -To customly configure aptss, refer to [aptss](https://gitee.com/GXDE-OS/aptss) +Please refer to the [Spark Store FAQ and Support Guide](https://gitee.com/spark-store-project/spark-store/blob/dev/FAQ.md); +For advanced users requiring custom aptss config configuration, please refer to the [aptss Repository](https://gitee.com/GXDE-OS/aptss). --- ## Contact and Feedback -- If you have any questions or suggestions, please submit them via email or on our [Issue Page](https://gitee.com/spark-store-project/spark-store/issues). -- If you want to follow our development progress, you can go to the [Spark App Store Board](https://gitee.com/spark-store-project/spark-store/board) for more information. -- Our [Forum](https://bbs.spark-app.store/) -- Our [QQ Group](https://blog.shenmo.tech/post/%E6%95%85%E9%9A%9C%E5%85%AC%E5%91%8A/) -- For commercial support, please leave your issue~ - +- If you have any questions or suggestions, please submit issues via email or the [Issue Page](https://gitee.com/spark-store-project/spark-store/issues); +- If you want to follow our development progress, visit the [Spark Store Board](https://gitee.com/spark-store-project/spark-store/board) for more information; +- Welcome to visit the [Spark Community Forum](https://bbs.spark-app.store/) to join discussions; +- Our QQ Discussion Group: 872690351 and 865927727; +- If you or your organization requires commercial support, please leave a message for consultation. --- \ No newline at end of file diff --git a/README.zh.md b/README.zh.md index 0875ae8..2f62191 100644 --- a/README.zh.md +++ b/README.zh.md @@ -5,43 +5,43 @@ ## 简介 -欢迎来到星火应用商店!这是一个为Linux用户设计的应用商店,旨在解决Linux生态下应用分散、难以获取的问题。无论你是使用什么类型的Linux发行版,这里都有可能找到适合你的软件包。 +欢迎来到**星火应用商店**!这是一个为 Linux 平台用户设计的应用商店,旨在解决 Linux 生态下应用分散、难以获取的问题。无论您使用什么类型的 Linux 发行版,在这里都有可能找到适合您的软件包。 - -Linux 应用的数量相对有限,Wine 软件的可获得性也颇为困难。优秀的开发和工具资源散布在各大社区和论坛之间,这种分散化让整个生态系统难以得到全面的提升。 +Linux 应用的数量相对有限,Wine 软件的可获取性也颇为困难。优秀的开发套件和工具资源散布在各大社区和论坛之间,这种分散化让整个生态系统难以得到全面的提升。 生态系统的构建并非依赖个体的孤立努力,而需要全社区共同参与。只有当大家的“星火”聚集一处,方可引发“燎原之势”。 -为了改善这一现状,我们推出了这个应用商店。我们广泛地收录了各种用户需求的软件包,汇集了高质量的小工具,并主动对 Wine 应用进行了适配,一切都储存在我们的软件库中,使得用户可以方便地获取这些应用。 +为了改善这一现状,我们推出了星火应用商店。星火社区广泛地收录了各种用户需求的软件包,汇集了高质量的小工具,并主动对 Wine 应用进行了适配,一切都储存在我们的软件库中,使得用户可以方便地获取这些应用。 -**当前支持的Linux发行版包括:** +**当前支持的 Linux 发行版包括(但不限于):** -- **amd64架构:** deepin 20 / deepin 23 / Ubuntu 20.04 / Ubuntu 22.04 / UOS家庭版20 -- **arm64架构:** UOS专业版1060 / Ubuntu 22.04 / deepin 23 -- **loong64架构:** deepin 23 +- **amd64 架构:** deepin 20 / deepin 23 / Ubuntu 20.04 / Ubuntu 22.04 / UOS 家庭版 20 / Debian 11+ +- **arm64 架构:** UOS 专业版 1060 / Ubuntu 22.04 / deepin 23 +- **loong64 架构:** deepin 23 -> 特别说明:我们还支持所有版本高于Ubuntu 22.04的Ubuntu操作系统,例如Ubuntu 22.10、23.04、23.10等。 +> 特别说明:星火应用商店还支持所有版本高于 Ubuntu 22.04 的 Ubuntu 操作系统,例如 Ubuntu 22.10、23.04、23.10 等。此外星火应用商店也可能适配除上述平台的其他 Linux 发行版,您可自行进行安装测试。 -**重要须知:** 本软件不提供任何形式的保证。若您计划在UOS专业版上使用,请务必了解并启用开发者模式。请确保您具备基础的故障排查能力。需要明确的是,我们没有在UOS操作系统上进行广泛的测试。因此,使用星火客户端可能会导致一系列问题,如系统更新失败、数据丢失等,所有风险需由用户自行承担。 +**重要须知:** 本软件无法保证持续可用、无中断运行或满足特定性能要求。星火社区对其功能完整性、稳定性及无错误运行不作任何承诺。例如,若您计划在 UOS 专业版(或其他类似特定平台)上使用,请务必了解并启用“开发者模式”相关功能。请确保您具备基础的故障排查能力。需要明确的是,星火社区无法在部分特殊平台上进行广泛测试。因此,在这些平台上使用星火应用商店客户端可能会导致一系列问题,如系统更新失败、数据丢失等;使用该软件,即代表您理解并同意所有风险需由用户自行承担。 ## 关于团队协作 组织仓库链接: + https://gitee.com/spark-store-project/spark-store https://gitcode.com/spark-store-project/spark-store https://github.com/spark-store-project/spark-store -分支管理相关的详细文档可参见 [此链接](https://wiki.spark-app.store/#/Dev/Spark-Store-Git-Repo)。 +参见[此链接](https://wiki.spark-app.store/#/Dev/Spark-Store-Git-Repo)以获取分支管理相关的详细文档。 -我们热忱欢迎您加入我们的开发团队。无论您是想参与开发,还是想提交应用,都能在这里找到属于您的一席之地,共同推动Linux应用生态的发展。 +我们热忱欢迎您加入我们的开发团队。无论您是希望参与开发,还是计划提交应用,都能在这里找到属于您的一席之地,共同推动 Linux 应用生态的发展。 -您可以通过以下链接,实时跟踪我们的Issue处理状态:[Issue看板](https://gitee.com/spark-store-project/spark-store/board)。 +您可以通过以下链接,实时跟踪我们的 Issue 处理状态:[Issue 看板](https://gitee.com/spark-store-project/spark-store/board)。 -若您有软件包想要提交,敬请 [点击此处进行投稿](https://wiki.spark-app.store/#/Submit/Submit)。 +若您有软件包需要提交,敬请[点击此处进行投稿](https://wiki.spark-app.store/#/Submit/Submit)。 @@ -49,9 +49,9 @@ https://github.com/spark-store-project/spark-store ## 目录 - [阅读版权声明](#阅读版权声明) -- [确定你的系统架构](#确定你的系统架构) +- [确定您的系统架构](#确定你的系统架构) - [系统支持与安装指引](#系统支持与安装指引) - - [对于 Deepin / UOS 用户](#对于deepin用户) + - [对于 deepin / UOS 用户](#对于deepin用户) - [对于 Ubuntu 用户](#对于ubuntu用户) - [对于 Debian 用户](#对于debian用户) - [常见问题(FAQ)](#常见问题faq) @@ -64,38 +64,40 @@ https://github.com/spark-store-project/spark-store ## 确定你的系统架构 -在安装任何软件之前,你需要知道你的计算机运行的是哪种架构(如x86_64/amd64或aarch64/arm64)。 +在安装任何软件之前,您需要知道你的计算机运行在何种架构之上(如 x86_64/amd64 或 aarch64/arm64),对于星火应用商店亦是如此。 **如何检查:** -1. 打开Linux终端。 -2. 输入 `uname -m` 或 `arch` 并按回车。 +1. 打开 Linux 终端。 +2. 输入命令 `uname -m` 或 `arch` 并按 `Enter` 键执行。 -你会看到一个输出,这就是你的系统架构。 +您会看到终端输出结果,借此您可判断计算机的系统架构。举例: -- 如果看到 `x86_64`,你的系统是AMD64架构。 -- 如果看到 `aarch64`,你的系统是ARM64架构。 +- 如果您看到 `x86_64`,说明您的系统为 amd64 架构。 +- 如果您看到 `aarch64`,说明您的系统为 arm64 架构。 + +根据这一结果,您需要下载对应版本的星火应用商店安装包以正常使用。 --- ## 系统支持与安装指引 -根据你的Linux发行版和系统架构,以下是安装星火应用商店的详细步骤。 +根据您的 Linux 发行版和系统架构,以下是安装星火应用商店的详细步骤。 -### 对于 Deepin 用户 +### 对于 deepin 用户 -#### 对于 Deepin 用户 +#### 对于 deepin 用户 1. **下载并安装** - 您可直接在深度商店搜索 星火应用商店 安装,或复制此链接到浏览器打开安装 + 您可直接在深度应用商店搜索 `星火应用商店` 安装,或复制此链接到浏览器地址栏,跳转至深度应用商店安装。 > appstore://deepin-home-appstore-client?app_detail_info/spark-store - 若要使用最新版本,请访问[星火应用商店的Release页面](https://gitee.com/spark-store-project/spark-store/releases)或[Gitcode的Release页面](https://gitcode.com/spark-store-project/spark-store/releases)并下载适用于Deepin的最新版本。安装后即可使用。 + 深度应用商店发布的版本可能不是最新的。若要使用最新版本,请访问星火应用商店 [Gitee 的 Release 页面](https://gitee.com/spark-store-project/spark-store/releases)或 [Gitcode 的 Release 页面](https://gitcode.com/spark-store-project/spark-store/releases)并下载适用于 deepin 的最新版本,安装后即可使用。 - 假设您下载到用户目录下的 Downloads 文件夹, 我们推荐您使用 APT 工具安装: + 假设您将安装包下载至用户目录下的 Downloads 文件夹,我们推荐您使用 `apt` 工具进行安装以避免潜在的依赖问题: ```shell cd ~/Downloads @@ -107,16 +109,16 @@ https://github.com/spark-store-project/spark-store #### 对于 Ubuntu 20.04 用户 1. **下载依赖包** -* 请访问[星火应用商店依赖包下载页面](https://gitee.com/spark-store-project/spark-store-dependencies/releases), 下载最新的依赖包。 -* 请多次解压依赖包,直到你可以看到诸多以 deb 结尾的安装包。 +* 请访问[星火应用商店依赖包下载页面](https://gitee.com/spark-store-project/spark-store-dependencies/releases),下载最新的依赖包; +* 请多次解压依赖包,直至您可以看到诸多以 deb 结尾的安装包; * 依据依赖包内的说明,一次性安装所有的依赖包。 2. **下载并安装** - 请访问[星火应用商店的Release页面](https://gitee.com/spark-store-project/spark-store/releases)或[Gitcode的Release页面](https://gitcode.com/spark-store-project/spark-store/releases),下载和您电脑相同架构的安装包并安装。 + 请访问星火应用商店 [Gitee 的 Release 页面](https://gitee.com/spark-store-project/spark-store/releases)或 [Gitcode 的 Release 页面](https://gitcode.com/spark-store-project/spark-store/releases),下载和您电脑相同架构的安装包并安装。 - 假设您下载到用户目录下的 Downloads 文件夹, 我们推荐您使用 APT 工具安装: + 假设您将安装包下载至用户目录下的 Downloads 文件夹,我们推荐您使用 `apt` 工具进行安装以避免潜在的依赖问题: ```shell cd ~/Downloads @@ -129,9 +131,9 @@ https://github.com/spark-store-project/spark-store 2. **下载并安装** - 请访问[星火应用商店的Release页面](https://gitee.com/spark-store-project/spark-store/releases)或[Gitcode的Release页面](https://gitcode.com/spark-store-project/spark-store/releases),下载和您电脑相同架构的安装包并安装。 + 请访问星火应用商店 [Gitee 的 Release 页面](https://gitee.com/spark-store-project/spark-store/releases)或 [Gitcode 的 Release 页面](https://gitcode.com/spark-store-project/spark-store/releases),下载和您电脑相同架构的安装包并安装。 - 假设您下载到用户目录下的 Downloads 文件夹, 我们推荐您使用 APT 工具安装: + 假设您将安装包下载至用户目录下的 Downloads 文件夹,我们推荐您使用 `apt` 工具进行安装以避免潜在的依赖问题: ```shell cd ~/Downloads @@ -143,13 +145,13 @@ https://github.com/spark-store-project/spark-store #### 对于 Debian 11 用户 1. **下载依赖包** -* 请访问[星火应用商店依赖包下载页面](https://gitee.com/spark-store-project/spark-store-dependencies/releases), 下载最新的依赖包。 -* 请多次解压依赖包,直到你可以看到诸多以 deb 结尾的安装包。 +* 请访问[星火应用商店依赖包下载页面](https://gitee.com/spark-store-project/spark-store-dependencies/releases), 下载最新的依赖包; +* 请多次解压依赖包,直到您可以看到诸多以 deb 结尾的安装包; * 依据依赖包内的说明,一次性安装所有的依赖包。 2. **下载并安装** - 请访问[星火应用商店的Release页面](https://gitee.com/spark-store-project/spark-store/releases)或[Gitcode的Release页面](https://gitcode.com/spark-store-project/spark-store/releases)并下载,安装后即可使用。 + 请访问星火应用商店 [Gitee 的 Release 页面](https://gitee.com/spark-store-project/spark-store/releases)或 [Gitcode 的 Release 页面](https://gitcode.com/spark-store-project/spark-store/releases)并下载,安装后即可使用。 #### 对于 Debian 12+ 用户 @@ -159,9 +161,9 @@ https://github.com/spark-store-project/spark-store 2. **下载并安装** - 请访问[星火应用商店的Release页面](https://gitee.com/spark-store-project/spark-store/releases)或[Gitcode的Release页面](https://gitcode.com/spark-store-project/spark-store/releases),下载和您电脑相同架构的安装包并安装。 + 请访问星火应用商店 [Gitee 的 Release 页面](https://gitee.com/spark-store-project/spark-store/releases)或 [Gitcode 的 Release 页面](https://gitcode.com/spark-store-project/spark-store/releases),下载和您电脑相同架构的安装包并安装。 - 假设您下载到用户目录下的 Downloads 文件夹, 我们推荐您使用 APT 工具安装: + 假设您将安装包下载至用户目录下的 Downloads 文件夹,我们推荐您使用 `apt` 工具进行安装以避免潜在的依赖问题: ```shell cd ~/Downloads @@ -171,9 +173,9 @@ https://github.com/spark-store-project/spark-store ## 常见问题(FAQ) -请参见[星火应用商店FAQ与支持指南](https://gitee.com/spark-store-project/spark-store/blob/dev/FAQ.zh.md)。 +请参见[星火应用商店 FAQ 与支持指南](https://gitee.com/spark-store-project/spark-store/blob/dev/FAQ.zh.md); -自定义配置 aptss config 请参阅 [aptss](https://gitee.com/GXDE-OS/aptss) +对于高级用户,如需自定义配置 aptss config,请参阅 [aptss 软件仓库](https://gitee.com/GXDE-OS/aptss)。 @@ -181,10 +183,10 @@ https://github.com/spark-store-project/spark-store ## 联系与反馈 -- 如果您有任何问题或建议,请通过邮件或在[Issue 页面](https://gitee.com/spark-store-project/spark-store/issues)上提交问题。 -- 如果你想关注我们的开发进度,可以跳转[星火应用商店Board](https://gitee.com/spark-store-project/spark-store/board)获取更多信息。 -- 我们的[论坛](https://bbs.spark-app.store/) -- 我们的QQ群 872690351 -- 商业支持请留言咨询~ +- 如果您有任何问题或建议,请通过邮件或在 [Issue 页面](https://gitee.com/spark-store-project/spark-store/issues)上提交问题; +- 如果你想关注我们的开发进度,可以跳转至[星火应用商店 Board](https://gitee.com/spark-store-project/spark-store/board) 获取更多信息; +- 欢迎访问[星火社区论坛](https://bbs.spark-app.store/)加入讨论; +- 我们的 QQ 交流群号:872690351 和 865927727; +- 若您和您的组织需要寻求商业支持,请留言咨询。 --- diff --git a/pkg/usr/share/icons/hicolor/scalable/apps/open-me-in-terminal.png b/pkg/usr/share/icons/hicolor/scalable/apps/open-me-in-terminal.png index 0ae8e54e76be71fbb1f1228aef7e7ea9a417adac..bdcabb7f5be62ad8eb0ff8152b1d9c5eccc3ab3f 100644 GIT binary patch literal 258729 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

4EIgU8c~(zvCUQ_W{; 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 Ye><$_+0iEBIjFrfY~`|=rHT1}0jy{giU0rr literal 91331 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

  • %$yKI~?DHO(x+7F=qCKG|?-J`~ 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 dS!LVJr|Hoqhc>in@lz$iQ-Q&rx4zqA{3j+O^ub zSOnC;1MFh)i?x$az0434ZzwOTdQ|*}u;X_N?(aQR-@M`SNIk72d}>^e zN#+3v>l>~EOzzm){_W|-4=_N6b7?!co8LyetVV{1m)}L3Zx%vJ>8xU2g?&b%FiPJCs zb%5hzkm^0v9#Q(sb@**FpQQKM>Z{A}$Tx^f9b#^-Kj0=F#rP!$?X)8ZM?W_nLKZKt? zH$?QydLB>?Ky@s*uj=+av&g?--xVRKlc%MmurKOMEZ>npn4o3H1MRCUyYEX5Ujh0P zg=uxf4`Zq#N5C@iB%*ZFPJ{LnLA?mn0$|JMJW5OGBirRu<452AMQG*j2Y02al~XEN zm8^)WW%m-D$gCN|-Sgl%#`RdK6S3i2knbz%Bh(z)EbGBwd*z51y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - + \ No newline at end of file From d5ad1d020485e17dbe7ee1b37cf688ece6d08776 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 24 Jun 2025 11:23:47 +0800 Subject: [PATCH 092/126] =?UTF-8?q?fix:=E8=A7=A3=E5=86=B3=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E6=9D=A1=E4=B8=8D=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3b01b23..e76667c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -154,7 +154,7 @@ void MainWindow::initStyle() this->setStyleSheet("background-color: #F8FAFC;"); // 添加滚动条样式 - listView->setStyleSheet(R"( + this->setStyleSheet(R"( QScrollBar:vertical { background: #F3F4F6; width: 8px; From ea78b37d860151cd76155f2e5b41537cd04b631e Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 25 Jun 2025 22:06:05 +0800 Subject: [PATCH 093/126] =?UTF-8?q?docs:=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ffed210..519a94b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ 欢迎使用星火软件更新器,您可以使用此更新器更新位于您 Linux 计算机的程序。 此版本专为仅有qt5的Linux发行版所使用。 +请在root环境下运行。 #### 当前支持的 Linux 发行版 - [x] GXDE OS - [x] Ubuntu From 63910b64917c87b7bbad467f799f5a01116c350b Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 27 Jun 2025 20:18:25 +0800 Subject: [PATCH 094/126] =?UTF-8?q?update:=E8=AE=BE=E7=BD=AE=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e76667c..a94fedd 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5,13 +5,16 @@ #include #include // 新增 #include // 新增 - +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , m_model(new AppListModel(this)) , m_delegate(new AppDelegate(this)) { + QIcon icon(":/resources/spark-update-tool.svg"); + setWindowIcon(icon); QProgressDialog *progressDialog = new QProgressDialog("正在与服务器通信,获取更新信息中...", QString(), 0, 0, this); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setCancelButton(nullptr); @@ -26,7 +29,8 @@ MainWindow::MainWindow(QWidget *parent) progressDialog->deleteLater(); watcher->deleteLater(); ui->setupUi(this); - + QIcon icon(":/resources/spark-update-tool.svg"); + setWindowIcon(icon); // 创建 QListView 并设置父控件为 ui->appWidget listView = new QListView(ui->appWidget); listView->setModel(m_model); From 6bb3e8dfbf8096891b066821abb75e00141867a4 Mon Sep 17 00:00:00 2001 From: momen Date: Sat, 28 Jun 2025 21:45:22 +0800 Subject: [PATCH 095/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 519a94b..279140c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ #### 当前支持的 Linux 发行版 - [x] GXDE OS - [x] Ubuntu -- [ ] deepin +- [x] deepin - [ ] Kylin #### 功能清单 From 4acd2332bf93ede57d8f9cf7371e2d219805668b Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 29 Jun 2025 10:15:05 +0800 Subject: [PATCH 096/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 279140c..df1ec21 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,6 @@ |应用包大小识别|使用 dkpg 完成| |获取应用 Icon|使用 QDesktopServices 完成| |支持 ACE 兼容环境| | - +|aptss|| #### 联系与反馈 momen@momen.world \ No newline at end of file From 5a84a4d3de15634a49683eb7f8248f3490dd943a Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 30 Jun 2025 10:33:02 +0800 Subject: [PATCH 097/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df1ec21..9dfd17b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,6 @@ |应用包大小识别|使用 dkpg 完成| |获取应用 Icon|使用 QDesktopServices 完成| |支持 ACE 兼容环境| | -|aptss|| +|多线程下载|采用aptss方案| #### 联系与反馈 momen@momen.world \ No newline at end of file From 7cad4937c12672e3e5d044349784ff5b60e6cf76 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 2 Jul 2025 11:14:42 +0800 Subject: [PATCH 098/126] =?UTF-8?q?chore:=E5=8A=A0=E5=85=A5128*128=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/128*128/spark-update-tool.png | Bin 0 -> 9142 bytes src/icons.qrc | 1 + 2 files changed, 1 insertion(+) create mode 100644 resources/128*128/spark-update-tool.png diff --git a/resources/128*128/spark-update-tool.png b/resources/128*128/spark-update-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..138ed83770cf4cce8fdead9c11908a0446e8a135 GIT binary patch literal 9142 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 AL;wH) literal 0 HcmV?d00001 diff --git a/src/icons.qrc b/src/icons.qrc index 0209761..f81e1f8 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -3,5 +3,6 @@ ../resources/down_arrow.svg ../resources/default_icon.svg ../resources/spark-update-tool.svg + ../resources/128*128/spark-update-tool.png From 38a3b4ee4fb3cbb59bbcc46d941677e6b53da820 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 2 Jul 2025 11:20:12 +0800 Subject: [PATCH 099/126] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=A0=8F=E6=B2=A1=E6=9C=89=E6=AD=A3=E7=A1=AE=E6=98=BE?= =?UTF-8?q?=E7=A4=BAlogo=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.cpp | 2 +- src/mainwindow.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c834b96..049e23f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) // QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); QApplication a(argc, argv); - + QApplication::setWindowIcon(QIcon(":/resources/128*128/spark-update-tool.png")); if (!isRoot()) { qDebug() << "Not running as root. Attempting to elevate..."; if (!elevateToRoot()) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a94fedd..e54ce43 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -13,7 +13,7 @@ MainWindow::MainWindow(QWidget *parent) , m_model(new AppListModel(this)) , m_delegate(new AppDelegate(this)) { - QIcon icon(":/resources/spark-update-tool.svg"); + QIcon icon(":/resources/128*128/spark-update-tool.png"); setWindowIcon(icon); QProgressDialog *progressDialog = new QProgressDialog("正在与服务器通信,获取更新信息中...", QString(), 0, 0, this); progressDialog->setWindowModality(Qt::ApplicationModal); @@ -29,7 +29,7 @@ MainWindow::MainWindow(QWidget *parent) progressDialog->deleteLater(); watcher->deleteLater(); ui->setupUi(this); - QIcon icon(":/resources/spark-update-tool.svg"); + QIcon icon(":/resources/128*128/spark-update-tool.png"); setWindowIcon(icon); // 创建 QListView 并设置父控件为 ui->appWidget listView = new QListView(ui->appWidget); From 2a88c996079be97bf5076050002fb2853fec657d Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 2 Jul 2025 11:48:35 +0800 Subject: [PATCH 100/126] =?UTF-8?q?fix:deb=E5=8C=85=E4=B8=A2=E5=A4=B1?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian/install | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/install b/debian/install index ac99078..ed44f2c 100644 --- a/debian/install +++ b/debian/install @@ -1,3 +1,3 @@ build/spark-update-tool /usr/bin/ debian/spark-update-tool.desktop /usr/share/applications -resources/spark-update-tool.svg /usr/share/icons/hicolor/scalable/apps/spark-update-tool.svg \ No newline at end of file +resources/128*128/spark-update-tool.png /usr/share/icons/hicolor/128x128/apps diff --git a/src/main.cpp b/src/main.cpp index 049e23f..a3df5a4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) // QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); QApplication a(argc, argv); - QApplication::setWindowIcon(QIcon(":/resources/128*128/spark-update-tool.png")); + a.setWindowIcon(QIcon(":/resources/128*128/spark-update-tool.png")); if (!isRoot()) { qDebug() << "Not running as root. Attempting to elevate..."; if (!elevateToRoot()) { From 2d53e655f75c5b70dc7e2de209c27b1cd27e5c22 Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 2 Jul 2025 15:32:03 +0800 Subject: [PATCH 101/126] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E4=B8=BAaptss?= =?UTF-8?q?=20ssupdate=E6=9D=A5=E6=9B=B4=E6=96=B0=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e54ce43..dde2a4c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -209,17 +209,17 @@ void MainWindow::runAptssUpgrade() { QProcess process; QStringList args; - args << "aptss" << "upgrade"; + args << "sudo" <<"aptss" << "ssupdate"; process.start("sudo", args); if (!process.waitForStarted(5000)) { - QMessageBox::warning(this, "升级失败", "无法启动 sudo aptss upgrade。"); + QMessageBox::warning(this, "升级失败", "无法启动 sudo aptss ssupdate"); return; } process.write("n\n"); process.closeWriteChannel(); process.waitForFinished(-1); if (process.exitCode() != 0) { - QMessageBox::warning(this, "升级失败", "执行 sudo aptss upgrade 失败,请检查系统环境。"); + QMessageBox::warning(this, "升级失败", "执行 sudo aptss ssupdate 失败,请检查系统环境。"); } } From 9bf218cfeefea2cdabd8a9f5584a1c931c1d6e7a Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 3 Jul 2025 14:31:57 +0800 Subject: [PATCH 102/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9dfd17b..ae2026a 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,29 @@ ### 星火软件更新器 + #### 简介 -欢迎使用星火软件更新器,您可以使用此更新器更新位于您 Linux 计算机的程序。 +欢迎使用星火软件更新器!本工具可帮助您便捷地更新 Linux 计算机上的各类程序。 + +本版本专为仅包含 Qt5 的 Linux 发行版设计。 +**请在 root 权限下运行本程序。** -此版本专为仅有qt5的Linux发行版所使用。 -请在root环境下运行。 #### 当前支持的 Linux 发行版 + - [x] GXDE OS - [x] Ubuntu - [x] deepin - [ ] Kylin #### 功能清单 -|功能模块|描述| -|--|--| -|应用名识别|采用 `ss-do-upgrade.sh` 部分代码| -|应用包大小识别|使用 dkpg 完成| -|获取应用 Icon|使用 QDesktopServices 完成| -|支持 ACE 兼容环境| | -|多线程下载|采用aptss方案| + +| 功能模块 | 描述 | +|------------------|--------------------------------------| +| 应用名识别 | 基于 `ss-do-upgrade.sh` 部分代码实现 | +| 应用包大小识别 | 通过 dpkg 获取包大小信息 | +| 获取应用图标 | 利用 QDesktopServices 实现 | +| 支持 ACE 兼容环境| | +| 多线程下载 | 基于 aptss 方案 | + #### 联系与反馈 -momen@momen.world \ No newline at end of file + +如有问题或建议,欢迎联系:momen@momen.world \ No newline at end of file From f09abddda024786826a253ca663d4a6d0ea9a187 Mon Sep 17 00:00:00 2001 From: shenmo Date: Thu, 3 Jul 2025 14:35:34 +0800 Subject: [PATCH 103/126] aptss support riscv --- tool/aptss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tool/aptss b/tool/aptss index 228f0fc..acee277 100755 --- a/tool/aptss +++ b/tool/aptss @@ -19,6 +19,10 @@ case `arch` in STORE_URL="loong64-store" STORE_LIST_URL="-loong64" ;; + riscv64) + STORE_URL="riscv64-store" + STORE_LIST_URL="-riscv64" + ;; esac SS_APT_FAST="/opt/durapps/spark-store/bin/apt-fast/ss-apt-fast" From ca71fe278761365f0a2b9985b63c5ad9bf511a51 Mon Sep 17 00:00:00 2001 From: shenmo Date: Thu, 3 Jul 2025 14:36:58 +0800 Subject: [PATCH 104/126] update svg --- src/assets/icon/spark-store.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/assets/icon/spark-store.svg diff --git a/src/assets/icon/spark-store.svg b/src/assets/icon/spark-store.svg new file mode 100644 index 0000000..a84891b --- /dev/null +++ b/src/assets/icon/spark-store.svg @@ -0,0 +1 @@ + \ No newline at end of file From 8f708aa8b2364904757079513bf6a3960cdd229c Mon Sep 17 00:00:00 2001 From: shenmo Date: Thu, 3 Jul 2025 15:17:14 +0800 Subject: [PATCH 105/126] cdn-d.spark-app.store --- src/pages/settingspage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settingspage.cpp b/src/pages/settingspage.cpp index 16cf2c9..fad913a 100644 --- a/src/pages/settingspage.cpp +++ b/src/pages/settingspage.cpp @@ -8,7 +8,7 @@ #include #define TMP_PATH "/tmp/spark-store" -#define DEFAULT_SERVER_URL "https://cdn.d.store.deepinos.org.cn/" +#define DEFAULT_SERVER_URL "https://cdn-d.spark-app.store/" bool SettingsPage::needUncompatibleNotification = true; From ce9aa95eacbf1ae3efb6e9261c3a9c5eb696d276 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 3 Jul 2025 19:00:11 +0000 Subject: [PATCH 106/126] add LICENSE. Signed-off-by: momen --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3877ae0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 934062aa3bc51dfbce33673e1ed1387716c63ab7 Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 4 Jul 2025 03:18:54 +0800 Subject: [PATCH 107/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E6=A1=86=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.ui | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 2a58287..a53a4b8 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -49,7 +49,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -120,7 +120,7 @@ 0 - + 200 @@ -144,7 +144,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -193,10 +193,10 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight - QComboBox::AdjustToContentsOnFirstShow + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow From e69deb4b95f6f56276bd1e584c44bf2e4ef1a726 Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 4 Jul 2025 04:08:09 +0800 Subject: [PATCH 108/126] =?UTF-8?q?=E5=B0=86=E6=9B=B4=E6=96=B0=E5=99=A8?= =?UTF-8?q?=E5=90=88=E5=85=A5=E5=88=B0=E6=98=9F=E7=81=AB=E5=95=86=E5=BA=97?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spark-update-tool/.gitignore | 62 +++ spark-update-tool/CMakeLists.txt | 58 +++ spark-update-tool/README.md | 29 ++ spark-update-tool/debian/changelog | 5 + spark-update-tool/debian/compat | 1 + spark-update-tool/debian/control | 13 + spark-update-tool/debian/copyright | 7 + spark-update-tool/debian/install | 3 + spark-update-tool/debian/postrm | 11 + spark-update-tool/debian/rules | 7 + spark-update-tool/debian/source/format | 1 + .../debian/spark-update-tool.desktop | 9 + .../resources/128*128/spark-update-tool.png | Bin 0 -> 9142 bytes spark-update-tool/resources/default_icon.svg | 1 + spark-update-tool/resources/down_arrow.svg | 1 + .../resources/spark-update-tool.svg | 1 + spark-update-tool/src/appdelegate.cpp | 321 ++++++++++++++ spark-update-tool/src/appdelegate.h | 46 ++ spark-update-tool/src/applistmodel.cpp | 62 +++ spark-update-tool/src/applistmodel.h | 26 ++ spark-update-tool/src/aptssupdater.cpp | 398 ++++++++++++++++++ spark-update-tool/src/aptssupdater.h | 28 ++ spark-update-tool/src/downloadmanager.cpp | 117 +++++ spark-update-tool/src/downloadmanager.h | 28 ++ spark-update-tool/src/icons.qrc | 8 + spark-update-tool/src/main.cpp | 86 ++++ spark-update-tool/src/mainwindow.cpp | 229 ++++++++++ spark-update-tool/src/mainwindow.h | 33 ++ spark-update-tool/src/mainwindow.ui | 256 +++++++++++ 29 files changed, 1847 insertions(+) create mode 100644 spark-update-tool/.gitignore create mode 100644 spark-update-tool/CMakeLists.txt create mode 100644 spark-update-tool/README.md create mode 100644 spark-update-tool/debian/changelog create mode 100644 spark-update-tool/debian/compat create mode 100644 spark-update-tool/debian/control create mode 100644 spark-update-tool/debian/copyright create mode 100644 spark-update-tool/debian/install create mode 100644 spark-update-tool/debian/postrm create mode 100755 spark-update-tool/debian/rules create mode 100644 spark-update-tool/debian/source/format create mode 100644 spark-update-tool/debian/spark-update-tool.desktop create mode 100644 spark-update-tool/resources/128*128/spark-update-tool.png create mode 100644 spark-update-tool/resources/default_icon.svg create mode 100644 spark-update-tool/resources/down_arrow.svg create mode 100644 spark-update-tool/resources/spark-update-tool.svg create mode 100644 spark-update-tool/src/appdelegate.cpp create mode 100644 spark-update-tool/src/appdelegate.h create mode 100644 spark-update-tool/src/applistmodel.cpp create mode 100644 spark-update-tool/src/applistmodel.h create mode 100644 spark-update-tool/src/aptssupdater.cpp create mode 100644 spark-update-tool/src/aptssupdater.h create mode 100644 spark-update-tool/src/downloadmanager.cpp create mode 100644 spark-update-tool/src/downloadmanager.h create mode 100644 spark-update-tool/src/icons.qrc create mode 100644 spark-update-tool/src/main.cpp create mode 100644 spark-update-tool/src/mainwindow.cpp create mode 100644 spark-update-tool/src/mainwindow.h create mode 100644 spark-update-tool/src/mainwindow.ui diff --git a/spark-update-tool/.gitignore b/spark-update-tool/.gitignore new file mode 100644 index 0000000..79c9ac7 --- /dev/null +++ b/spark-update-tool/.gitignore @@ -0,0 +1,62 @@ +build +.vscode +.cache +CMakeLists.txt.user +CMakeLists.txt.user.* +obj-x86_64-linux-gnu +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* + +# Qt unit tests +target_wrapper.* + +# Qt qm files +translations/*.qm + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* +build + +# Debian dpkg-buildpackage +debian/*.debhelper* +debian/files +debian/*.substvars +debian/spark-update-tool + +.vscode/* +src/spark-update-tool diff --git a/spark-update-tool/CMakeLists.txt b/spark-update-tool/CMakeLists.txt new file mode 100644 index 0000000..49a4d5e --- /dev/null +++ b/spark-update-tool/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 3.16) + +project(spark-update-tool VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +find_package(Qt5 REQUIRED COMPONENTS Widgets Network Concurrent) + + +find_package(Qt5 REQUIRED COMPONENTS Widgets Network Concurrent Core Gui) + +# 定义所有项目源文件,现在无需条件判断 +set(PROJECT_SOURCES + src/main.cpp + src/mainwindow.cpp + src/mainwindow.h + src/mainwindow.ui + src/aptssupdater.h src/aptssupdater.cpp + src/icons.qrc + src/appdelegate.h src/appdelegate.cpp + src/applistmodel.h src/applistmodel.cpp + src/downloadmanager.h src/downloadmanager.cpp +) + + +add_executable(spark-update-tool + ${PROJECT_SOURCES} +) + +target_link_libraries(spark-update-tool PRIVATE + Qt5::Widgets + Qt5::Network + Qt5::Concurrent + Qt5::Core + Qt5::Gui +) + + +set_target_properties(spark-update-tool PROPERTIES + # ${BUNDLE_ID_OPTION} # 如果上面取消注释,这里也取消注释 + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + +) + +include(GNUInstallDirs) +install(TARGETS spark-update-tool + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + diff --git a/spark-update-tool/README.md b/spark-update-tool/README.md new file mode 100644 index 0000000..ae2026a --- /dev/null +++ b/spark-update-tool/README.md @@ -0,0 +1,29 @@ +### 星火软件更新器 + +#### 简介 + +欢迎使用星火软件更新器!本工具可帮助您便捷地更新 Linux 计算机上的各类程序。 + +本版本专为仅包含 Qt5 的 Linux 发行版设计。 +**请在 root 权限下运行本程序。** + +#### 当前支持的 Linux 发行版 + +- [x] GXDE OS +- [x] Ubuntu +- [x] deepin +- [ ] Kylin + +#### 功能清单 + +| 功能模块 | 描述 | +|------------------|--------------------------------------| +| 应用名识别 | 基于 `ss-do-upgrade.sh` 部分代码实现 | +| 应用包大小识别 | 通过 dpkg 获取包大小信息 | +| 获取应用图标 | 利用 QDesktopServices 实现 | +| 支持 ACE 兼容环境| | +| 多线程下载 | 基于 aptss 方案 | + +#### 联系与反馈 + +如有问题或建议,欢迎联系:momen@momen.world \ No newline at end of file diff --git a/spark-update-tool/debian/changelog b/spark-update-tool/debian/changelog new file mode 100644 index 0000000..b414fea --- /dev/null +++ b/spark-update-tool/debian/changelog @@ -0,0 +1,5 @@ +spark-update-tool (1.0.0) unstable; urgency=low + + * Initial release. + + -- momen Wed, 18 Jun 2025 00:00:00 +0000 \ No newline at end of file diff --git a/spark-update-tool/debian/compat b/spark-update-tool/debian/compat new file mode 100644 index 0000000..f11c82a --- /dev/null +++ b/spark-update-tool/debian/compat @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/spark-update-tool/debian/control b/spark-update-tool/debian/control new file mode 100644 index 0000000..19c9d62 --- /dev/null +++ b/spark-update-tool/debian/control @@ -0,0 +1,13 @@ +Source: spark-update-tool +Section: utils +Priority: optional +Maintainer: momen +Build-Depends: debhelper (>= 9) +Standards-Version: 3.9.6 +Homepage: https://gitee.com/spark-store-project/Spark-Update-Tool + +Package: spark-update-tool +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Spark Update Tool + 星火应用商店更新组件。This package provides the Spark Update Tool. It includes features for checking for updates, downloading, and applying them seamlessly. \ No newline at end of file diff --git a/spark-update-tool/debian/copyright b/spark-update-tool/debian/copyright new file mode 100644 index 0000000..163489e --- /dev/null +++ b/spark-update-tool/debian/copyright @@ -0,0 +1,7 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: spark-update-tool +Source: https://gitee.com/spark-store-project/Spark-Update-Tool + +Files: * +Copyright: 2025, momen +License: GPL-3.0+ \ No newline at end of file diff --git a/spark-update-tool/debian/install b/spark-update-tool/debian/install new file mode 100644 index 0000000..ed44f2c --- /dev/null +++ b/spark-update-tool/debian/install @@ -0,0 +1,3 @@ +build/spark-update-tool /usr/bin/ +debian/spark-update-tool.desktop /usr/share/applications +resources/128*128/spark-update-tool.png /usr/share/icons/hicolor/128x128/apps diff --git a/spark-update-tool/debian/postrm b/spark-update-tool/debian/postrm new file mode 100644 index 0000000..263e9c4 --- /dev/null +++ b/spark-update-tool/debian/postrm @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +case "$1" in + purge) + + rm -rf /usr/share/spark-update-tool + ;; +esac + +exit 0 \ No newline at end of file diff --git a/spark-update-tool/debian/rules b/spark-update-tool/debian/rules new file mode 100755 index 0000000..1159ac4 --- /dev/null +++ b/spark-update-tool/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f + +%: + dh $@ + +override_dh_auto_configure: + dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/usr diff --git a/spark-update-tool/debian/source/format b/spark-update-tool/debian/source/format new file mode 100644 index 0000000..9f67427 --- /dev/null +++ b/spark-update-tool/debian/source/format @@ -0,0 +1 @@ +3.0 (native) \ No newline at end of file diff --git a/spark-update-tool/debian/spark-update-tool.desktop b/spark-update-tool/debian/spark-update-tool.desktop new file mode 100644 index 0000000..688ba0c --- /dev/null +++ b/spark-update-tool/debian/spark-update-tool.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Name=Spark Update Tool +Comment=A Qt-based application for managing and updating software +Exec=spark-update-tool +Icon=spark-update-tool +Terminal=false +Type=Application +Categories=Utility; diff --git a/spark-update-tool/resources/128*128/spark-update-tool.png b/spark-update-tool/resources/128*128/spark-update-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..138ed83770cf4cce8fdead9c11908a0446e8a135 GIT binary patch literal 9142 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 AL;wH) literal 0 HcmV?d00001 diff --git a/spark-update-tool/resources/default_icon.svg b/spark-update-tool/resources/default_icon.svg new file mode 100644 index 0000000..7c9cb42 --- /dev/null +++ b/spark-update-tool/resources/default_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/spark-update-tool/resources/down_arrow.svg b/spark-update-tool/resources/down_arrow.svg new file mode 100644 index 0000000..4989662 --- /dev/null +++ b/spark-update-tool/resources/down_arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/spark-update-tool/resources/spark-update-tool.svg b/spark-update-tool/resources/spark-update-tool.svg new file mode 100644 index 0000000..3d21d81 --- /dev/null +++ b/spark-update-tool/resources/spark-update-tool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/spark-update-tool/src/appdelegate.cpp b/spark-update-tool/src/appdelegate.cpp new file mode 100644 index 0000000..1a1c3e9 --- /dev/null +++ b/spark-update-tool/src/appdelegate.cpp @@ -0,0 +1,321 @@ +#include "appdelegate.h" +#include +#include +#include +#include +#include +#include +#include +#include + +AppDelegate::AppDelegate(QObject *parent) + : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)), m_installProcess(nullptr) { + connect(m_downloadManager, &DownloadManager::downloadFinished, this, + [this](const QString &packageName, bool success) { + if (m_downloads.contains(packageName)) { + m_downloads[packageName].isDownloading = false; + emit updateDisplay(packageName); + qDebug() << (success ? "下载完成:" : "下载失败:") << packageName; + if (success) { + enqueueInstall(packageName); + } + } + }); + + connect(m_downloadManager, &DownloadManager::downloadProgress, this, + [this](const QString &packageName, int progress) { + if (m_downloads.contains(packageName)) { + m_downloads[packageName].progress = progress; + qDebug()<save(); + + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + else + painter->fillRect(option.rect, QColor("#F3F4F6")); + + QFont boldFont = option.font; + boldFont.setBold(true); + QFont normalFont = option.font; + + QString name = index.data(Qt::DisplayRole).toString(); + QString currentVersion = index.data(Qt::UserRole + 2).toString(); + QString newVersion = index.data(Qt::UserRole + 3).toString(); + QString iconPath = index.data(Qt::UserRole + 4).toString(); + QString size = index.data(Qt::UserRole + 5).toString(); + QString description = index.data(Qt::UserRole + 6).toString(); + + QRect rect = option.rect; + int margin = 10, spacing = 6, iconSize = 40; + + QRect iconRect(rect.left() + margin, rect.top() + (rect.height() - iconSize) / 2, iconSize, iconSize); + QIcon(iconPath).paint(painter, iconRect); + + int textX = iconRect.right() + margin; + int textWidth = rect.width() - textX - 100; + + QRect nameRect(textX, rect.top() + margin, textWidth, 20); + painter->setFont(boldFont); + painter->setPen(QColor("#333333")); + painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name); + + QRect versionRect(textX, nameRect.bottom() + spacing, textWidth, 20); + painter->setFont(normalFont); + painter->setPen(QColor("#888888")); + painter->drawText(versionRect, Qt::AlignLeft | Qt::AlignVCenter, + QString("当前版本: %1 → 新版本: %2").arg(currentVersion, newVersion)); + + QRect descRect(textX, versionRect.bottom() + spacing, textWidth, 40); + painter->setFont(normalFont); + painter->setPen(QColor("#AAAAAA")); + painter->drawText(descRect, Qt::TextWordWrap, + QString("包大小:%1 MB").arg(QString::number(size.toDouble() / (1024 * 1024), 'f', 2))); + + QString packageName = index.data(Qt::UserRole + 1).toString(); + bool isDownloading = m_downloads.contains(packageName) && m_downloads[packageName].isDownloading; + int progress = m_downloads.value(packageName, DownloadInfo{0, false}).progress; + bool isInstalled = m_downloads.value(packageName).isInstalled; + bool isInstalling = m_downloads.value(packageName).isInstalling; + + if (isDownloading) { + QRect progressRect(rect.right() - 270, rect.top() + (rect.height() - 20) / 2, 150, 20); + QStyleOptionProgressBar progressBarOption; + progressBarOption.rect = progressRect; + progressBarOption.minimum = 0; + progressBarOption.maximum = 100; + progressBarOption.progress = progress; + progressBarOption.text = QString("%1%").arg(progress); + progressBarOption.textVisible = true; + QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); + + // 修改后的取消按钮绘制代码 + QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#ff4444")); // 红色背景 + painter->drawRoundedRect(buttonRect, 4, 4); // 圆角矩形 + + painter->setPen(Qt::white); // 白色文字 + painter->setFont(option.font); + painter->drawText(buttonRect, Qt::AlignCenter, "取消"); + } else if (isInstalling) { + // 安装中:显示转圈和文字 + QRect spinnerRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 30, 30); + int angle = (m_spinnerTimer.elapsed() / 10) % 360; + QPen pen(QColor("#2563EB"), 3); + painter->setPen(pen); + painter->setRenderHint(QPainter::Antialiasing, true); + QRectF arcRect = spinnerRect.adjusted(3, 3, -3, -3); + painter->drawArc(arcRect, angle * 16, 120 * 16); // 120度弧 + + QRect textRect(option.rect.right() - 120, option.rect.top() + (option.rect.height() - 30) / 2, 110, 30); + painter->setPen(QColor("#2563EB")); + painter->setFont(option.font); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, "正在安装中"); + } else { + QRect buttonRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + if (isInstalled) { + painter->setBrush(QColor("#10B981")); + painter->drawRoundedRect(buttonRect, 4, 4); + painter->setPen(Qt::white); + painter->drawText(buttonRect, Qt::AlignCenter, "已安装"); + } else if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { + // 下载完成,按钮绿色,样式不变 + painter->setBrush(QColor("#10B981")); + painter->drawRoundedRect(buttonRect, 4, 4); + painter->setPen(Qt::white); + painter->drawText(buttonRect, Qt::AlignCenter, "下载完成"); + // 不需要特殊处理样式,交互在 editorEvent 控制 + } else { + painter->setBrush(QColor("#e9effd")); + painter->drawRoundedRect(buttonRect, 4, 4); + painter->setPen(QColor("#2563EB")); + painter->drawText(buttonRect, Qt::AlignCenter, "更新"); + } + } + + painter->restore(); +} + +QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { + return QSize(option.rect.width(), 110); +} + +bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) { + if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + QRect rect = option.rect; + QString packageName = index.data(Qt::UserRole + 1).toString(); + + if (m_downloads.contains(packageName) && m_downloads[packageName].isDownloading) { + QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); + if (cancelButtonRect.contains(mouseEvent->pos())) { + m_downloadManager->cancelDownload(packageName); + m_downloads.remove(packageName); + emit updateDisplay(packageName); + return true; + } + } else { + QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + if (buttonRect.contains(mouseEvent->pos())) { + // 判断是否为“下载完成”状态,如果是则不响应点击 + if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { + // “下载完成”状态,按钮失效,点击无效 + return false; + } + QString downloadUrl = index.data(Qt::UserRole + 7).toString(); + QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); + + m_downloads[packageName] = {0, true}; + m_downloadManager->startDownload(packageName, downloadUrl, outputPath); + emit updateDisplay(packageName); + return true; + } + } + } + + return QStyledItemDelegate::editorEvent(event, model, option, index); +} + +void AppDelegate::startDownloadForAll() { + if (!m_model) return; + for (int row = 0; row < m_model->rowCount(); ++row) { + QModelIndex index = m_model->index(row, 0); + QString packageName = index.data(Qt::UserRole + 1).toString(); + if (m_downloads.contains(packageName) && (m_downloads[packageName].isDownloading || m_downloads[packageName].isInstalled)) + continue; // 跳过正在下载或已安装的 + QString downloadUrl = index.data(Qt::UserRole + 7).toString(); + QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); + m_downloads[packageName] = {0, true, false}; + m_downloadManager->startDownload(packageName, downloadUrl, outputPath); + emit updateDisplay(packageName); + } +} + +// 新增:安装队列相关实现 +void AppDelegate::enqueueInstall(const QString &packageName) { + m_installQueue.enqueue(packageName); + if (!m_isInstalling) { + startNextInstall(); + } +} + +void AppDelegate::startNextInstall() { + if (m_installQueue.isEmpty()) { + m_isInstalling = false; + m_installingPackage.clear(); + return; + } + m_isInstalling = true; + QString packageName = m_installQueue.dequeue(); + m_installingPackage = packageName; + m_downloads[packageName].isInstalling = true; + emit updateDisplay(packageName); + + // 查找 /tmp 下以包名开头的 .deb 文件 + QDir tempDir(QDir::tempPath()); + QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName), QDir::Files); + QString debPath; + if (!debs.isEmpty()) { + debPath = tempDir.absoluteFilePath(debs.first()); + } else { + debs = tempDir.entryList(QStringList() << QString("%1*.deb").arg(packageName), QDir::Files); + if (!debs.isEmpty()) { + debPath = tempDir.absoluteFilePath(debs.first()); + } + } + + if (debPath.isEmpty()) { + qWarning() << "未找到deb文件,包名:" << packageName; + m_downloads[packageName].isInstalling = false; + emit updateDisplay(packageName); + m_installingPackage.clear(); + startNextInstall(); + return; + } + + m_installProcess = new QProcess(this); + + // 新增:准备安装日志文件 + QString logPath = QString("/tmp/%1_install.log").arg(packageName); + QFile *logFile = new QFile(logPath, m_installProcess); + if (logFile->open(QIODevice::Append | QIODevice::Text)) { + // 设置权限为777 + QFile::setPermissions(logPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | + QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | + QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); + connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName, logFile]() { + QByteArray out = m_installProcess->readAllStandardOutput(); + logFile->write(out); + logFile->flush(); + QString text = QString::fromLocal8Bit(out); + qDebug().noquote() << text; + // 检查“软件包已安装”关键字 + if (text.contains(QStringLiteral("软件包已安装"))) { + m_downloads[packageName].isInstalling = false; + m_downloads[packageName].isInstalled = true; + emit updateDisplay(packageName); + } + }); + connect(m_installProcess, &QProcess::readyReadStandardError, this, [this, logFile]() { + QByteArray err = m_installProcess->readAllStandardError(); + logFile->write(err); + logFile->flush(); + qDebug().noquote() << QString::fromLocal8Bit(err); + }); + connect(m_installProcess, QOverload::of(&QProcess::finished), + this, [this, packageName, logFile](int /*exitCode*/, QProcess::ExitStatus /*status*/) { + if (logFile) logFile->close(); + // 若未检测到“软件包已安装”,此处兜底 + if (!m_downloads[packageName].isInstalled) { + m_downloads[packageName].isInstalling = false; + } + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + }); + } else { + // 日志文件无法打开时,仍然要连接原有信号 + connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName]() { + QByteArray out = m_installProcess->readAllStandardOutput(); + QString text = QString::fromLocal8Bit(out); + qDebug().noquote() << text; + if (text.contains(QStringLiteral("软件包已安装"))) { + m_downloads[packageName].isInstalling = false; + m_downloads[packageName].isInstalled = true; + emit updateDisplay(packageName); + } + }); + connect(m_installProcess, &QProcess::readyReadStandardError, this, [this]() { + QByteArray err = m_installProcess->readAllStandardError(); + qDebug().noquote() << QString::fromLocal8Bit(err); + }); + connect(m_installProcess, QOverload::of(&QProcess::finished), + this, [this, packageName](int /*exitCode*/, QProcess::ExitStatus /*status*/) { + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + }); + } + + // 注意参数顺序:deb路径在前,--no-create-desktop-entry在后 + QStringList args; + args << debPath << "--no-create-desktop-entry"; + m_installProcess->start("ssinstall", args); +} diff --git a/spark-update-tool/src/appdelegate.h b/spark-update-tool/src/appdelegate.h new file mode 100644 index 0000000..0545155 --- /dev/null +++ b/spark-update-tool/src/appdelegate.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "downloadmanager.h" + +struct DownloadInfo { + int progress = 0; + bool isDownloading = false; + bool isInstalled = false; + bool isInstalling = false; // 新增:标记是否正在安装 +}; + +class AppDelegate : public QStyledItemDelegate { + Q_OBJECT +public: + explicit AppDelegate(QObject *parent = nullptr); + + void setModel(QAbstractItemModel *model); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) override; + void startDownloadForAll(); + +signals: + void updateDisplay(const QString &packageName); + +private: + DownloadManager *m_downloadManager; + QHash m_downloads; + QAbstractItemModel *m_model = nullptr; + + QQueue m_installQueue; + bool m_isInstalling = false; + QProcess *m_installProcess = nullptr; + QString m_installingPackage; // 当前正在安装的包名 + QElapsedTimer m_spinnerTimer; // 用于转圈动画 + + void enqueueInstall(const QString &packageName); + void startNextInstall(); +}; diff --git a/spark-update-tool/src/applistmodel.cpp b/spark-update-tool/src/applistmodel.cpp new file mode 100644 index 0000000..31e244a --- /dev/null +++ b/spark-update-tool/src/applistmodel.cpp @@ -0,0 +1,62 @@ +#include "applistmodel.h" + +AppListModel::AppListModel(QObject *parent) : QAbstractListModel(parent) {} + +int AppListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return m_data.size(); +} + +QVariant AppListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_data.size()) + return QVariant(); + + const QVariantMap &map = m_data.at(index.row()); // 直接访问 QVariantMap + switch (role) { + case Qt::DisplayRole: + return map.value("name"); + case Qt::UserRole + 1: // 包名 + return map.value("package"); + case Qt::UserRole + 2: // 当前版本 + return map.value("current_version"); + case Qt::UserRole + 3: // 新版本 + return map.value("new_version"); + case Qt::UserRole + 4: // 图标路径 + return map.value("icon"); + case Qt::UserRole + 5: // 文件大小 + return map.value("size"); + case Qt::UserRole + 6: // 描述 + return map.value("description"); + case Qt::UserRole + 7: // 下载 URL + return map.value("download_url"); // 返回下载 URL + default: + return QVariant(); + } +} + +void AppListModel::setUpdateData(const QJsonArray &updateInfo) +{ + beginResetModel(); + m_data.clear(); // 清空 QList + + for (const auto &item : updateInfo) { + QJsonObject obj = item.toObject(); + QVariantMap map; + map["package"] = obj["package"].toString(); + map["name"] = obj["name"].toString(); + map["current_version"] = obj["current_version"].toString(); + map["new_version"] = obj["new_version"].toString(); + map["icon"] = obj["icon"].toString(); + map["size"] = obj["size"].toString(); + map["download_url"] = obj["download_url"].toString(); // 确保设置下载 URL + m_data.append(map); // 添加到 QList + + qDebug() << "设置到模型的包名:" << map["package"].toString(); + qDebug() << "设置到模型的下载 URL:" << map["download_url"].toString(); // 检查设置的数据 + } + + endResetModel(); +} diff --git a/spark-update-tool/src/applistmodel.h b/spark-update-tool/src/applistmodel.h new file mode 100644 index 0000000..2fb36c0 --- /dev/null +++ b/spark-update-tool/src/applistmodel.h @@ -0,0 +1,26 @@ +#ifndef APPLISTMODEL_H +#define APPLISTMODEL_H + +#include +#include +// 添加 QJsonObject 头文件 +#include + +class AppListModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit AppListModel(QObject *parent = nullptr); + + // 重写方法 + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // 设置更新数据 + void setUpdateData(const QJsonArray &data); + +private: + QList m_data; // 修改类型为 QList +}; + +#endif // APPLISTMODEL_H diff --git a/spark-update-tool/src/aptssupdater.cpp b/spark-update-tool/src/aptssupdater.cpp new file mode 100644 index 0000000..c4bd906 --- /dev/null +++ b/spark-update-tool/src/aptssupdater.cpp @@ -0,0 +1,398 @@ +#include "aptssupdater.h" +#include +#include +#include +#include +#include + +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> 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()< +#include +#include +#include +#include +#include +class aptssUpdater : public QWidget +{ + Q_OBJECT +public: + explicit aptssUpdater(QWidget *parent = nullptr); + + QStringList getUpdateablePackages(); // 查询可更新包列表及更新内容 + QStringList getPackageSizes(); // 获取每个包的大小 + QStringList getDesktopAppNames(); // 获取桌面应用名称列表 + QStringList getPackageIcons(); // 获取包图标列表 + QJsonArray getUpdateInfoAsJson(); // 获取更新信息的 JSON 格式 + QString m_tempFilePath; +signals: +private: + bool checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName); + QStringList packageName; +}; + +#endif // APTSSUPDATER_H \ No newline at end of file diff --git a/spark-update-tool/src/downloadmanager.cpp b/spark-update-tool/src/downloadmanager.cpp new file mode 100644 index 0000000..9780324 --- /dev/null +++ b/spark-update-tool/src/downloadmanager.cpp @@ -0,0 +1,117 @@ +#include "downloadmanager.h" +#include +#include +#include +#include +#include + +DownloadManager::DownloadManager(QObject *parent) : QObject(parent) +{ + cleanupTempFiles(); +} + +void DownloadManager::startDownload(const QString &packageName, const QString &url, const QString &outputPath) +{ + if (m_processes.contains(packageName)) { + qWarning() << packageName << " is already downloading."; + return; + } + + QString metalinkUrl = url + ".metalink"; + QFileInfo fileInfo(outputPath); + + QStringList arguments = { + "--enable-rpc=false", + "--console-log-level=warn", + "--summary-interval=1", + "--allow-overwrite=true", + "--dir=" + fileInfo.absolutePath(), + "--out=" + fileInfo.fileName(), + metalinkUrl + }; + + QProcess *process = new QProcess(this); + m_processes.insert(packageName, process); + + // 新增:准备日志文件 + QString logPath = QString("/tmp/%1_download.log").arg(packageName); + QFile *logFile = new QFile(logPath, process); + if (logFile->open(QIODevice::Append | QIODevice::Text)) { + // 设置权限为777 + QFile::setPermissions(logPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | + QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | + QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); + connect(process, &QProcess::readyReadStandardOutput, this, [this, packageName, process, logFile]() { + while (process->canReadLine()) { + QString line = QString::fromUtf8(process->readLine()).trimmed(); + // 写入日志 + logFile->write(line.toUtf8() + '\n'); + logFile->flush(); + QRegularExpression regex(R"(\((\d+)%\))"); + QRegularExpressionMatch match = regex.match(line); + if (match.hasMatch()) { + int progress = match.captured(1).toInt(); + emit downloadProgress(packageName, progress); + } + } + }); + connect(process, &QProcess::readyReadStandardError, this, [process, logFile]() { + QByteArray err = process->readAllStandardError(); + logFile->write(err); + logFile->flush(); + }); + } + + connect(process, QOverload::of(&QProcess::finished), + this, [this, packageName, outputPath, logFile](int exitCode, QProcess::ExitStatus status) { + bool success = (exitCode == 0 && status == QProcess::NormalExit); + if (!success) { + qWarning() << "Download failed for" << packageName << "exit code:" << exitCode; + } + + removeAria2Files(outputPath); // 清理残留 .aria2 + emit downloadFinished(packageName, success); + + if (logFile) logFile->close(); + + QProcess *proc = m_processes.take(packageName); + if (proc) proc->deleteLater(); + }); + + process->start("aria2c", arguments); +} + +void DownloadManager::cancelDownload(const QString &packageName) +{ + if (!m_processes.contains(packageName)) return; + + QProcess *process = m_processes.take(packageName); + if (process) { + process->kill(); // 立即终止 + process->waitForFinished(3000); // 最多等待3秒 + process->deleteLater(); + } + + emit downloadFinished(packageName, false); // 显式通知取消 + +} + +void DownloadManager::removeAria2Files(const QString &filePath) +{ + QString ariaFile = filePath + ".aria2"; + QFile::remove(ariaFile); +} + +bool DownloadManager::isDownloading(const QString &packageName) const +{ + return m_processes.contains(packageName); +} + +void DownloadManager::cleanupTempFiles() +{ + QDir tempDir(QDir::tempPath()); + QStringList leftovers = tempDir.entryList(QStringList() << "*.aria2", QDir::Files); + for (const QString &f : leftovers) { + tempDir.remove(f); + } +} diff --git a/spark-update-tool/src/downloadmanager.h b/spark-update-tool/src/downloadmanager.h new file mode 100644 index 0000000..0614368 --- /dev/null +++ b/spark-update-tool/src/downloadmanager.h @@ -0,0 +1,28 @@ +#ifndef DOWNLOADMANAGER_H +#define DOWNLOADMANAGER_H + +#include +#include +#include + +class DownloadManager : public QObject +{ + Q_OBJECT +public: + explicit DownloadManager(QObject *parent = nullptr); + void startDownload(const QString &packageName, const QString &url, const QString &outputPath); + void cancelDownload(const QString &packageName); + bool isDownloading(const QString &packageName) const; + +signals: + void downloadProgress(const QString &packageName, int progress); + void downloadFinished(const QString &packageName, bool success); + +private: + void cleanupTempFiles(); + void removeAria2Files(const QString &filePath); + + QMap m_processes; +}; + +#endif // DOWNLOADMANAGER_H diff --git a/spark-update-tool/src/icons.qrc b/spark-update-tool/src/icons.qrc new file mode 100644 index 0000000..f81e1f8 --- /dev/null +++ b/spark-update-tool/src/icons.qrc @@ -0,0 +1,8 @@ + + + ../resources/down_arrow.svg + ../resources/default_icon.svg + ../resources/spark-update-tool.svg + ../resources/128*128/spark-update-tool.png + + diff --git a/spark-update-tool/src/main.cpp b/spark-update-tool/src/main.cpp new file mode 100644 index 0000000..a3df5a4 --- /dev/null +++ b/spark-update-tool/src/main.cpp @@ -0,0 +1,86 @@ +#include "mainwindow.h" +#include +#include +#include +#include // for geteuid +#include // for getenv +#include // For debugging output + +bool isRoot() { + return geteuid() == 0; +} + +bool elevateToRoot() { + QString program = QCoreApplication::applicationFilePath(); + qDebug() << "Current application path:" << program; + + QByteArray display = qgetenv("DISPLAY"); + QByteArray xauthority = qgetenv("XAUTHORITY"); + + QStringList args; + args << "env" + << "DISPLAY=" + display + << "XAUTHORITY=" + xauthority + << program; + + QProcess process; + process.setProgram("pkexec"); + process.setArguments(args); + + qDebug() << "Attempting to elevate using pkexec with arguments:" << args; + + process.start(); + if (!process.waitForStarted(5000)) { + qDebug() << "Failed to start pkexec."; + return false; + } + + // 阻塞等待提权进程退出(比如主程序窗口关闭) + if (!process.waitForFinished(-1)) { // 等待直到新进程退出 + qDebug() << "pkexec process waitForFinished failed."; + return false; + } + + int exitCode = process.exitCode(); + QProcess::ExitStatus exitStatus = process.exitStatus(); + + qDebug() << "pkexec exit code:" << exitCode; + qDebug() << "pkexec exit status:" << exitStatus; + qDebug() << "pkexec stderr:" << process.readAllStandardError(); + + return (exitStatus == QProcess::NormalExit && exitCode == 0); +} + +int main(int argc, char *argv[]) +{ + // 必须在 QGuiApplication 实例创建之前调用 + // QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); + + QApplication a(argc, argv); + a.setWindowIcon(QIcon(":/resources/128*128/spark-update-tool.png")); + if (!isRoot()) { + qDebug() << "Not running as root. Attempting to elevate..."; + if (!elevateToRoot()) { + qDebug() << "Elevation failed or pkexec command was not executed successfully."; + QMessageBox::critical(nullptr, + "权限不足", + "提权失败!\n\n您的系统可能不支持 `pkexec` 或 `polkit` 配置不正确," + "或者您取消了授权。\n\n请尝试使用 `sudo` 命令运行此程序:" + "\n\n在终端中输入:\n`sudo " + QCoreApplication::applicationName() + "`"); + return 0; // 提权失败,退出程序 + } else { + // 如果 elevateToRoot 返回 true,说明 pkexec 命令本身执行成功 + // 但这并不意味着原始程序以 root 权限启动了 + // 因为 elevateToRoot 启动的是一个新的进程,当前进程应该退出 + // 否则会并行运行两个程序实例 + qDebug() << "pkexec command executed successfully (new process likely started). Exiting current process."; + return 0; // 当前非root进程退出 + } + } else { + qDebug() << "Running as root."; + } + + MainWindow w; + w.show(); + return a.exec(); +} \ No newline at end of file diff --git a/spark-update-tool/src/mainwindow.cpp b/spark-update-tool/src/mainwindow.cpp new file mode 100644 index 0000000..dde2a4c --- /dev/null +++ b/spark-update-tool/src/mainwindow.cpp @@ -0,0 +1,229 @@ +#include "mainwindow.h" +#include "./ui_mainwindow.h" +#include +#include +#include +#include // 新增 +#include // 新增 +#include +#include +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , m_model(new AppListModel(this)) + , m_delegate(new AppDelegate(this)) +{ + QIcon icon(":/resources/128*128/spark-update-tool.png"); + setWindowIcon(icon); + QProgressDialog *progressDialog = new QProgressDialog("正在与服务器通信,获取更新信息中...", QString(), 0, 0, this); + progressDialog->setWindowModality(Qt::ApplicationModal); + progressDialog->setCancelButton(nullptr); + progressDialog->setWindowTitle("请稍候"); + progressDialog->setMinimumDuration(0); + progressDialog->setWindowFlags(progressDialog->windowFlags() & ~Qt::WindowCloseButtonHint); // 禁用关闭按钮 + progressDialog->show(); + //异步执行runAptssUpgrade + QFutureWatcher *watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, [=]() { + progressDialog->close(); + progressDialog->deleteLater(); + watcher->deleteLater(); + ui->setupUi(this); + QIcon icon(":/resources/128*128/spark-update-tool.png"); + setWindowIcon(icon); + // 创建 QListView 并设置父控件为 ui->appWidget + listView = new QListView(ui->appWidget); + listView->setModel(m_model); + listView->setItemDelegate(m_delegate); + + // 新增:确保 delegate 拥有 model 指针 + m_delegate->setModel(m_model); + + // 设置 QListView 填充 ui->appWidget + QVBoxLayout *layout = new QVBoxLayout(ui->appWidget); + layout->addWidget(listView); + layout->setContentsMargins(0, 0, 0, 0); + connect(m_delegate, &AppDelegate::updateDisplay, this, [=](const QString &packageName) { + for (int i = 0; i < m_model->rowCount(); ++i) { + QModelIndex index = m_model->index(i); + if (index.data(Qt::UserRole + 1).toString() == packageName) { + m_model->dataChanged(index, index); // 刷新该行 + break; + } + } + }); + + // 新增:点击“更新全部”按钮批量下载 + connect(ui->updatePushButton, &QPushButton::clicked, this, [=](){ + qDebug()<<"更新全部按钮被点击"; + m_delegate->startDownloadForAll(); + }); + + checkUpdates(); + initStyle(); + }); + + // 启动异步任务 + watcher->setFuture(QtConcurrent::run([this](){ + runAptssUpgrade(); + })); +} +//初始化控件样式 +void MainWindow::initStyle() +{ + //设置窗口标题 + this->setWindowTitle("软件更新中心"); + + //查询框样式 + ui->plainTextEdit->setStyleSheet(R"( + QPlainTextEdit { + background-color: #FFFFFF; + border: 1px solid #E5E7EB; + border-radius: 4px; + padding-top: 8px; + padding-bottom: 8px; + font-size: 9px; + line-height: 1.4; + color: #9CA3AF; + } + )"); + + ui->plainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->plainTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + //筛选框样式 + ui->FilterComboBox->setStyleSheet(R"( + QComboBox { + background-color: #FFFFFF; + border: 1px solid #E5E7EB; + border-radius: 4px; + color: #4B5563; + padding: 4px 8px; + } + + QComboBox::drop-down { + border: none; + width: 20px; + } + + QComboBox::down-arrow { + image: url(:/resources/down_arrow.svg); + width: 12px; + height: 16px; + } + QComboBox QAbstractItemView { + background-color: #FFFFFF; + border: 1px solid #E5E7EB; + color: #4B5563; + selection-background-color: #F3F4F6; + selection-color: #111827; + } + )"); + + //更新软件按钮样式 + ui->updatePushButton->setStyleSheet(R"( + QPushButton { + background-color: #2563EB; + color: #FFFFFF; + border: none; + border-radius: 4px; + font-size: 14px; + padding: 6px 12px; + text-align: center; + } + + QPushButton:hover { + background-color: #1D4ED8; /* 深一点的 hover 效果,可选 */ + } + + QPushButton:pressed { + background-color: #1E40AF; /* 按下效果,可选 */ + } + + QPushButton:disabled { + background-color: #A5B4FC; + color: #F9FAFB; + } + )"); + + //设置背景填充颜色 + ui->backgroundWidget->setStyleSheet(R"( + QWidget { + background-color: #FFFFFF; + border-radius: 12px; + } + )"); + + //设置主背景颜色 + this->setStyleSheet("background-color: #F8FAFC;"); + + // 添加滚动条样式 + this->setStyleSheet(R"( + QScrollBar:vertical { + background: #F3F4F6; + width: 8px; + margin: 0px; + } + QScrollBar::handle:vertical { + background: #D1D5DB; + border-radius: 4px; + min-height: 30px; + } + QScrollBar::handle:vertical:hover { + background: #9CA3AF; + } + QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + background: none; + height: 0px; + } + + QScrollBar:horizontal { + background: #F3F4F6; + height: 8px; + margin: 0px; + } + QScrollBar::handle:horizontal { + background: #D1D5DB; + border-radius: 4px; + min-width: 30px; + } + QScrollBar::handle:horizontal:hover { + background: #9CA3AF; + } + )"); +} +void MainWindow::checkUpdates() +{ + aptssUpdater updater; + QJsonArray updateInfo = updater.getUpdateInfoAsJson(); + m_model->setUpdateData(updateInfo); + + for (const auto &item : updateInfo) { + QJsonObject obj = item.toObject(); + qDebug() << "模型设置的包名:" << obj["package"].toString(); + qDebug() << "模型设置的下载 URL:" << obj["download_url"].toString(); // 检查模型数据 + } +} + +void MainWindow::runAptssUpgrade() +{ + QProcess process; + QStringList args; + args << "sudo" <<"aptss" << "ssupdate"; + process.start("sudo", args); + if (!process.waitForStarted(5000)) { + QMessageBox::warning(this, "升级失败", "无法启动 sudo aptss ssupdate"); + return; + } + process.write("n\n"); + process.closeWriteChannel(); + process.waitForFinished(-1); + if (process.exitCode() != 0) { + QMessageBox::warning(this, "升级失败", "执行 sudo aptss ssupdate 失败,请检查系统环境。"); + } +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/spark-update-tool/src/mainwindow.h b/spark-update-tool/src/mainwindow.h new file mode 100644 index 0000000..e36db88 --- /dev/null +++ b/spark-update-tool/src/mainwindow.h @@ -0,0 +1,33 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "aptssupdater.h" +#include "applistmodel.h" +#include "appdelegate.h" +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + void checkUpdates(); + void initStyle(); + void runAptssUpgrade(); + AppListModel *m_model; + AppDelegate *m_delegate; + QListView *listView; // 声明 QListView 指针 +}; +#endif // MAINWINDOW_H diff --git a/spark-update-tool/src/mainwindow.ui b/spark-update-tool/src/mainwindow.ui new file mode 100644 index 0000000..2a58287 --- /dev/null +++ b/spark-update-tool/src/mainwindow.ui @@ -0,0 +1,256 @@ + + + MainWindow + + + + 0 + 0 + 1440 + 858 + + + + MainWindow + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Noto Sans Vai + 22 + + + + 软件更新中心 + + + + + + + Qt::Horizontal + + + + 1245 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 320 + 38 + + + + + 320 + 38 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 200 + 38 + + + + + + + 81.000000000000000 + + + 搜索软件... + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 214 + 40 + + + + + 214 + 40 + + + + + + 0 + 0 + 102 + 38 + + + + + 102 + 38 + + + + + 102 + 38 + + + + + + + Qt::LeftToRight + + + QComboBox::AdjustToContentsOnFirstShow + + + + 按名称 + + + + + + + 118 + 0 + 96 + 40 + + + + 更新全部 + + + + + + + + + + + true + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + 1440 + 23 + + + + + + + From 7b6df76276821c30647f8028cc9098eefa813f16 Mon Sep 17 00:00:00 2001 From: momen Date: Sat, 5 Jul 2025 20:26:05 +0800 Subject: [PATCH 109/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E6=A1=86=E7=9A=84=E5=90=8D=E7=A7=B0=E4=B8=BAsearchPla?= =?UTF-8?q?inTextEdit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index dde2a4c..2b45c27 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -75,7 +75,7 @@ void MainWindow::initStyle() this->setWindowTitle("软件更新中心"); //查询框样式 - ui->plainTextEdit->setStyleSheet(R"( + ui->searchPlainTextEdit->setStyleSheet(R"( QPlainTextEdit { background-color: #FFFFFF; border: 1px solid #E5E7EB; @@ -88,8 +88,8 @@ void MainWindow::initStyle() } )"); - ui->plainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui->plainTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->searchPlainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->searchPlainTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); //筛选框样式 ui->FilterComboBox->setStyleSheet(R"( From 1be6e62d5585b617a2935200bca621541d949f3e Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 6 Jul 2025 20:14:51 +0800 Subject: [PATCH 110/126] =?UTF-8?q?update:=E5=AE=8C=E5=96=84=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 27 +++++++++++++++++++++++++++ src/mainwindow.h | 3 +++ 2 files changed, 30 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2b45c27..afd7d9b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -60,6 +60,11 @@ MainWindow::MainWindow(QWidget *parent) }); checkUpdates(); + // 新增:监听搜索框文本变化 + connect(ui->searchPlainTextEdit, &QPlainTextEdit::textChanged, this, [=]() { + QString keyword = ui->searchPlainTextEdit->toPlainText(); + filterAppsByKeyword(keyword); + }); initStyle(); }); @@ -196,6 +201,7 @@ void MainWindow::checkUpdates() { aptssUpdater updater; QJsonArray updateInfo = updater.getUpdateInfoAsJson(); + m_allApps = updateInfo; // 保存所有应用数据 m_model->setUpdateData(updateInfo); for (const auto &item : updateInfo) { @@ -205,6 +211,27 @@ void MainWindow::checkUpdates() } } +// 新增:根据关键字过滤应用 +void MainWindow::filterAppsByKeyword(const QString &keyword) +{ + if (keyword.trimmed().isEmpty()) { + m_model->setUpdateData(m_allApps); + return; + } + QJsonArray filtered; + for (const auto &item : m_allApps) { + QJsonObject obj = item.toObject(); + // 可根据需要匹配更多字段 + QString name = obj.value("name").toString(); + QString package = obj.value("package").toString(); + if (name.contains(keyword, Qt::CaseInsensitive) || + package.contains(keyword, Qt::CaseInsensitive)) { + filtered.append(item); + } + } + m_model->setUpdateData(filtered); +} + void MainWindow::runAptssUpgrade() { QProcess process; diff --git a/src/mainwindow.h b/src/mainwindow.h index e36db88..bc7f9d7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -6,6 +6,7 @@ #include "applistmodel.h" #include "appdelegate.h" #include +#include // 添加头文件 QT_BEGIN_NAMESPACE namespace Ui { @@ -29,5 +30,7 @@ private: AppListModel *m_model; AppDelegate *m_delegate; QListView *listView; // 声明 QListView 指针 + QJsonArray m_allApps; // 新增:保存所有应用数据 + void filterAppsByKeyword(const QString &keyword); // 新增:搜索过滤函数声明 }; #endif // MAINWINDOW_H From b693c27adfccfd1af50077f2ec6aec30b9800ab9 Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 7 Jul 2025 05:29:46 +0800 Subject: [PATCH 111/126] =?UTF-8?q?fix:=E6=90=9C=E7=B4=A2=E6=A1=86?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E6=8F=90=E7=A4=BA=E5=AD=97=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index afd7d9b..54f064c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -66,6 +66,9 @@ MainWindow::MainWindow(QWidget *parent) filterAppsByKeyword(keyword); }); initStyle(); + + // 确保搜索框内容为空,placeholder 能显示 + ui->searchPlainTextEdit->clear(); }); // 启动异步任务 @@ -91,6 +94,9 @@ void MainWindow::initStyle() line-height: 1.4; color: #9CA3AF; } + QPlainTextEdit[placeholderText]:empty { + color: #9CA3AF; + } )"); ui->searchPlainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); From 8e40670f553a36c895b55d26053129260689cc11 Mon Sep 17 00:00:00 2001 From: shenmo Date: Mon, 7 Jul 2025 10:56:38 +0000 Subject: [PATCH 112/126] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=8A=E6=B8=B8?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: shenmo --- src/backend/DataCollectorAndUploader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/DataCollectorAndUploader.cpp b/src/backend/DataCollectorAndUploader.cpp index 695a1ce..e0421e5 100644 --- a/src/backend/DataCollectorAndUploader.cpp +++ b/src/backend/DataCollectorAndUploader.cpp @@ -67,7 +67,7 @@ void DataCollectorAndUploader::collectData() // Initialize a network request QNetworkAccessManager *manager = new QNetworkAccessManager(this); - QUrl url("https://status.deepinos.org.cn/upload"); + QUrl url("https://status.spark-app.store/upload"); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); From e603dc3fce0b8c8614b717e78692c8fd3393c317 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 10 Jul 2025 00:00:52 +0800 Subject: [PATCH 113/126] =?UTF-8?q?chore:=E6=B7=BB=E5=8A=A0spark-update-to?= =?UTF-8?q?ol=E4=BB=A3=E7=A0=81=E8=BF=9B=E5=85=A5pro=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spark-store-project.pro | 7 +++ src/mainwindow-dtk.cpp | 35 ++++++++++++--- src/spark-update-tool/spark-update-tool.pro | 48 +++++++++++++++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 src/spark-update-tool/spark-update-tool.pro diff --git a/spark-store-project.pro b/spark-store-project.pro index bb6b540..b0cbbc7 100644 --- a/spark-store-project.pro +++ b/spark-store-project.pro @@ -79,3 +79,10 @@ INSTALLS += \ fish_completion \ polkit-1 # 暂时不添加 + +SUBDIRS += src/spark-update-tool + +spark-update-tool.subdir = src/spark-update-tool +spark-update-tool.file = src/spark-update-tool/spark-update-tool.pro +spark-update-tool.target = spark-update-tool +spark-update-tool.depends = spark-store diff --git a/src/mainwindow-dtk.cpp b/src/mainwindow-dtk.cpp index 45bc6e2..c457f76 100755 --- a/src/mainwindow-dtk.cpp +++ b/src/mainwindow-dtk.cpp @@ -551,13 +551,34 @@ void MainWindow::on_pushButton_14_clicked() QFile upgradeStatus("/tmp/spark-store/upgradeStatus.txt"); if (!upgradeStatus.exists()) { - QtConcurrent::run([=] - { - auto upgradeP = new QProcess(); - upgradeP->startDetached("/opt/durapps/spark-store/bin/update-upgrade/ss-do-upgrade.sh", QStringList()); - upgradeP->waitForStarted(); - upgradeP->waitForFinished(-1); - upgradeP->deleteLater(); }); + QString appPath; + + // 开发环境路径(构建目录) + #ifdef QT_DEBUG + appPath = QCoreApplication::applicationDirPath() + + "/../spark-update-tool/spark-update-tool"; + #else + // 安装后路径(系统PATH) + appPath = QStandardPaths::findExecutable("spark-update-tool"); + #endif + + if (appPath.isEmpty()) { + qWarning() << "spark-update-tool not found!"; + return; + } + + QProcess *process = new QProcess(this); + process->start(appPath, {"--silent"}); + + connect(process, QOverload::of(&QProcess::finished), + [=](int exitCode) { + if (exitCode == 0) { + qDebug() << "Update check successful"; + } else { + qDebug() << "Update check failed"; + } + process->deleteLater(); + }); } } } diff --git a/src/spark-update-tool/spark-update-tool.pro b/src/spark-update-tool/spark-update-tool.pro new file mode 100644 index 0000000..69dc500 --- /dev/null +++ b/src/spark-update-tool/spark-update-tool.pro @@ -0,0 +1,48 @@ +# spark-update-tool.pro +QT += core gui widgets network concurrent +TARGET = spark-update-tool +TEMPLATE = app + +# Set C++ standard to C++17 +CONFIG += c++17 + +# Enable auto features (uic, moc, rcc) +CONFIG += qt warn_on release +QT_CONFIG += no-pkg-config + +# Version info (replace with your actual version) +VERSION = 0.1.0 +DEFINES += APP_VERSION=\\\"$$VERSION\\\" + +# Source files +SOURCES += \ + src/main.cpp \ + src/mainwindow.cpp \ + src/aptssupdater.cpp \ + src/appdelegate.cpp \ + src/applistmodel.cpp \ + src/downloadmanager.cpp + +HEADERS += \ + src/mainwindow.h \ + src/aptssupdater.h \ + src/appdelegate.h \ + src/applistmodel.h \ + src/downloadmanager.h + +FORMS += \ + src/mainwindow.ui + +RESOURCES += \ + src/icons.qrc + +# macOS bundle properties (optional) +macx { + QMAKE_INFO_PLIST = Info.plist + ICON = resources/spark-update-tool.icns + BUNDLE_IDENTIFIER = org.spark.store.update-tool +} + +# Installation paths (matches CMake install) +target.path = $$[QT_INSTALL_BINS] +INSTALLS += target \ No newline at end of file From 59588c90d592eb3b723123bde308a5729ebd323d Mon Sep 17 00:00:00 2001 From: momen Date: Fri, 11 Jul 2025 00:31:18 +0800 Subject: [PATCH 114/126] =?UTF-8?q?chore:=E7=82=B9=E5=87=BB=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=97=B6=EF=BC=8C=E8=B0=83=E7=94=A8=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow-dtk.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mainwindow-dtk.cpp b/src/mainwindow-dtk.cpp index c457f76..9993c43 100755 --- a/src/mainwindow-dtk.cpp +++ b/src/mainwindow-dtk.cpp @@ -552,11 +552,11 @@ void MainWindow::on_pushButton_14_clicked() if (!upgradeStatus.exists()) { QString appPath; - + // 开发环境路径(构建目录) #ifdef QT_DEBUG - appPath = QCoreApplication::applicationDirPath() + - "/../spark-update-tool/spark-update-tool"; + appPath = QCoreApplication::applicationDirPath() + + "/spark-update-tool/spark-update-tool"; #else // 安装后路径(系统PATH) appPath = QStandardPaths::findExecutable("spark-update-tool"); @@ -569,8 +569,8 @@ void MainWindow::on_pushButton_14_clicked() QProcess *process = new QProcess(this); process->start(appPath, {"--silent"}); - - connect(process, QOverload::of(&QProcess::finished), + + connect(process, QOverload::of(&QProcess::finished), [=](int exitCode) { if (exitCode == 0) { qDebug() << "Update check successful"; From a20e96c63f400fe39ed1dddef11883bf51a6758d Mon Sep 17 00:00:00 2001 From: momen Date: Sat, 12 Jul 2025 12:17:11 +0800 Subject: [PATCH 115/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0readme=E5=AF=B9?= =?UTF-8?q?deepin=E7=94=A8=E6=88=B7=E4=BD=BF=E7=94=A8=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=99=A8=E7=9A=84=E5=8F=99=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.zh.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/README.zh.md b/README.zh.md index 2f62191..7c0f2be 100644 --- a/README.zh.md +++ b/README.zh.md @@ -48,14 +48,23 @@ https://github.com/spark-store-project/spark-store ## 目录 -- [阅读版权声明](#阅读版权声明) -- [确定您的系统架构](#确定你的系统架构) -- [系统支持与安装指引](#系统支持与安装指引) - - [对于 deepin / UOS 用户](#对于deepin用户) - - [对于 Ubuntu 用户](#对于ubuntu用户) - - [对于 Debian 用户](#对于debian用户) -- [常见问题(FAQ)](#常见问题faq) -- [联系与反馈](#联系与反馈) +- [星火应用商店](#星火应用商店) + - [简介](#简介) + - [关于团队协作](#关于团队协作) + - [目录](#目录) + - [阅读版权声明](#阅读版权声明) + - [确定你的系统架构](#确定你的系统架构) + - [系统支持与安装指引](#系统支持与安装指引) + - [对于 deepin 用户](#对于-deepin-用户) + - [对于 deepin 用户](#对于-deepin-用户-1) + - [对于 Ubuntu 用户](#对于-ubuntu-用户) + - [对于 Ubuntu 20.04 用户](#对于-ubuntu-2004-用户) + - [对于 Ubuntu 22.04 及更高版本的 Ubuntu 用户](#对于-ubuntu-2204-及更高版本的-ubuntu-用户) + - [对于 Debian 用户](#对于-debian-用户) + - [对于 Debian 11 用户](#对于-debian-11-用户) + - [对于 Debian 12+ 用户](#对于-debian-12-用户) + - [常见问题(FAQ)](#常见问题faq) + - [联系与反馈](#联系与反馈) --- @@ -103,6 +112,9 @@ https://github.com/spark-store-project/spark-store cd ~/Downloads sudo apt install ./spark-store*.deb ``` +2. **使用更新器** + 您可以在应用商店左下角找到更新按钮,点击并输入密码即可。 + ### 对于 Ubuntu 用户 From 0c3151948c928f14fda79bfe89b502a4b1a41063 Mon Sep 17 00:00:00 2001 From: momen Date: Sun, 13 Jul 2025 16:27:02 +0800 Subject: [PATCH 116/126] =?UTF-8?q?update:=E6=9E=84=E5=BB=BA=E8=BD=AF?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=EF=BC=8C=E5=B0=86=E6=9B=B4=E6=96=B0=E5=99=A8?= =?UTF-8?q?=E6=94=BE=E5=85=A5/usr/bin/=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian/spark-store.postinst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/spark-store.postinst b/debian/spark-store.postinst index 879a976..89b442d 100755 --- a/debian/spark-store.postinst +++ b/debian/spark-store.postinst @@ -41,7 +41,7 @@ case "$1" in ln -s -f /opt/durapps/spark-store/bin/aptss /usr/local/bin/ss-apt-fast ln -s -f /opt/durapps/spark-store/bin/aptss /usr/bin/aptss - + ln -sf /usr/lib/qt5/bin/spark-update-tool /usr/bin/spark-update-tool # Install key From 9bf8ff8c50917c3c45d16bcaf20f7db0ea040147 Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 14 Jul 2025 23:39:23 +0800 Subject: [PATCH 117/126] =?UTF-8?q?chore:=E6=B7=BB=E5=8A=A0=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=99=A8=E7=9A=84policy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actions/store.spark-update-tool.policy | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 pkg/usr/share/polkit-1/actions/store.spark-update-tool.policy diff --git a/pkg/usr/share/polkit-1/actions/store.spark-update-tool.policy b/pkg/usr/share/polkit-1/actions/store.spark-update-tool.policy new file mode 100644 index 0000000..012a66a --- /dev/null +++ b/pkg/usr/share/polkit-1/actions/store.spark-update-tool.policy @@ -0,0 +1,16 @@ + + + + + Run the Spark Update Tool + Authentication is required to update the system + /usr/bin/spark-update-tool + true + + yes + no + + + From 1f6e68ca3fc0759f4b4733dd6d687ac487d8818e Mon Sep 17 00:00:00 2001 From: momen Date: Mon, 14 Jul 2025 23:52:34 +0800 Subject: [PATCH 118/126] =?UTF-8?q?chore:=E5=88=A0=E9=99=A4=E6=97=A7?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=99=A8policy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store.spark-app.ss-do-upgrade.policy | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 pkg/usr/share/polkit-1/actions/store.spark-app.ss-do-upgrade.policy diff --git a/pkg/usr/share/polkit-1/actions/store.spark-app.ss-do-upgrade.policy b/pkg/usr/share/polkit-1/actions/store.spark-app.ss-do-upgrade.policy deleted file mode 100644 index d82d670..0000000 --- a/pkg/usr/share/polkit-1/actions/store.spark-app.ss-do-upgrade.policy +++ /dev/null @@ -1,18 +0,0 @@ - - - - Spark Store - x-package-repository - - 运行ss-do-upgrade需要权限 - 要使用ss-do-upgrade需要权限 - - yes - yes - yes - - /opt/durapps/spark-store/bin/update-upgrade/ss-do-upgrade.sh - true - - From 30acad75166b1303897c736d5213846d558d9727 Mon Sep 17 00:00:00 2001 From: momen Date: Tue, 15 Jul 2025 00:45:42 +0800 Subject: [PATCH 119/126] =?UTF-8?q?chore:=E4=BD=BF=E7=94=A8pkexec=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=9B=B4=E6=96=B0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainwindow-dtk.cpp | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/mainwindow-dtk.cpp b/src/mainwindow-dtk.cpp index 9993c43..5baca0e 100755 --- a/src/mainwindow-dtk.cpp +++ b/src/mainwindow-dtk.cpp @@ -345,7 +345,7 @@ void MainWindow::refreshTheme(bool isDarkMode) ui->applistpage->setTheme(isDarkMode); ui->applistpage_1->setTheme(isDarkMode); ui->appintopage->setTheme(isDarkMode); - ui->settingspage->setTheme(isDarkMode); + ui->settingspage->setTheme(isDarkMode); } void MainWindow::initConnections() @@ -551,16 +551,15 @@ void MainWindow::on_pushButton_14_clicked() QFile upgradeStatus("/tmp/spark-store/upgradeStatus.txt"); if (!upgradeStatus.exists()) { - QString appPath; + QString appPath; - // 开发环境路径(构建目录) - #ifdef QT_DEBUG - appPath = QCoreApplication::applicationDirPath() + - "/spark-update-tool/spark-update-tool"; - #else - // 安装后路径(系统PATH) - appPath = QStandardPaths::findExecutable("spark-update-tool"); - #endif + // 判断路径:开发环境 vs 安装后 + #ifdef QT_DEBUG + appPath = QCoreApplication::applicationDirPath() + + "/spark-update-tool/spark-update-tool"; + #else + appPath = QStandardPaths::findExecutable("spark-update-tool"); + #endif if (appPath.isEmpty()) { qWarning() << "spark-update-tool not found!"; @@ -568,17 +567,27 @@ void MainWindow::on_pushButton_14_clicked() } QProcess *process = new QProcess(this); - process->start(appPath, {"--silent"}); - connect(process, QOverload::of(&QProcess::finished), - [=](int exitCode) { + #ifdef QT_DEBUG + // 开发模式:直接运行本地构建的更新器 + process->start(appPath, {"--silent"}); + #else + // 安装模式:使用 pkexec 提权运行系统路径下的 spark-update-tool + QString program = "pkexec"; + QStringList arguments; + arguments << appPath << "--silent"; + process->start(program, arguments); + #endif + + QObject::connect(process, QOverload::of(&QProcess::finished), + [process](int exitCode) { if (exitCode == 0) { qDebug() << "Update check successful"; } else { - qDebug() << "Update check failed"; + qWarning() << "Update check failed with exit code:" << exitCode; } process->deleteLater(); }); - } - } + } + } } From 4c93f0e389cfdb49037b5d6033351d37cd57706e Mon Sep 17 00:00:00 2001 From: momen Date: Wed, 16 Jul 2025 14:45:26 +0800 Subject: [PATCH 120/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 84b1509..7799257 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +spark-store (4.8.1~test1) UNRELEASED; urgency=medium + + * 添加全新的更新器 + * 替换了新版更新器的polkit policy + -- momen Tue, 15 Jul 2025 01:03:08 +0800 + spark-store (4.8.0) UNRELEASED; urgency=medium * ssinstall重写,支持安装到ACE和自动安装到ACE,支持只允许安装到本地 From b704ee0093b371c042fe5ed7e4a71861fd0745bf Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 17 Jul 2025 21:29:52 +0800 Subject: [PATCH 121/126] =?UTF-8?q?chore:=E6=9B=B4=E6=96=B0readme=E5=85=B3?= =?UTF-8?q?=E4=BA=8E=E6=9B=B4=E6=96=B0=E5=99=A8=E7=9A=84=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.zh.md b/README.zh.md index 7c0f2be..8472861 100644 --- a/README.zh.md +++ b/README.zh.md @@ -113,7 +113,7 @@ https://github.com/spark-store-project/spark-store sudo apt install ./spark-store*.deb ``` 2. **使用更新器** - 您可以在应用商店左下角找到更新按钮,点击并输入密码即可。 + 您可以在应用商店左下角找到更新按钮,点击即可查看更新列表。 ### 对于 Ubuntu 用户 From b69dca63e3154054d71d3bfe07086c4884b22602 Mon Sep 17 00:00:00 2001 From: shenmo Date: Fri, 25 Jul 2025 05:32:02 +0000 Subject: [PATCH 122/126] update debian/changelog. Signed-off-by: shenmo --- debian/changelog | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/changelog b/debian/changelog index 7799257..0d5f275 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,6 +2,7 @@ spark-store (4.8.1~test1) UNRELEASED; urgency=medium * 添加全新的更新器 * 替换了新版更新器的polkit policy + -- momen Tue, 15 Jul 2025 01:03:08 +0800 spark-store (4.8.0) UNRELEASED; urgency=medium From fb9385fccd93f35492207df217d0790bd0b6f558 Mon Sep 17 00:00:00 2001 From: shenmo Date: Sat, 23 Aug 2025 07:05:45 +0000 Subject: [PATCH 123/126] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=83=85=E5=86=B5=E4=B8=8B=E6=97=A0=E6=B3=95=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E5=AE=89=E8=A3=85ACE=E7=8E=AF=E5=A2=83=E7=9A=84=E6=83=85?= =?UTF-8?q?=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: shenmo --- tool/ssaudit | 5 ++--- tool/ssinstall | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tool/ssaudit b/tool/ssaudit index 4909876..c6f0a58 100755 --- a/tool/ssaudit +++ b/tool/ssaudit @@ -204,14 +204,13 @@ export -f ensure_aptss_exist function ensure_ace_env() { local ace_env_pkg="${1}" - if ! dpkg -l "$ace_env_pkg" &>/dev/null; then - echo "ACE环境$ace_env_pkg未安装,正在尝试安装..." + + echo "ACE环境$ace_env_pkg正在尝试安装..." zenity --info --text="首次使用$ace_env_pkg环境,重启或注销桌面后才能在启动器中展示,不影响应用启动。安装将在后台继续。" --title="ACE环境安装" & if ! aptss install -y "$ace_env_pkg"; then echo "安装$ace_env_pkg失败" return 1 fi - fi return 0 } export user=$(who | awk '{print $1}' | head -n 1) diff --git a/tool/ssinstall b/tool/ssinstall index e84a186..1617d9f 100755 --- a/tool/ssinstall +++ b/tool/ssinstall @@ -204,14 +204,12 @@ export -f ensure_aptss_exist function ensure_ace_env() { local ace_env_pkg="${1}" - if ! dpkg -l "$ace_env_pkg" &>/dev/null; then - echo "ACE环境$ace_env_pkg未安装,正在尝试安装..." + echo "ACE环境$ace_env_pkg正在尝试安装..." zenity --info --text="首次使用$ace_env_pkg环境,重启或注销桌面后才能在启动器中展示,不影响应用启动。安装将在后台继续。" --title="ACE环境安装" & if ! aptss install -y "$ace_env_pkg"; then echo "安装$ace_env_pkg失败" return 1 fi - fi return 0 } export user=$(who | awk '{print $1}' | head -n 1) From 37ef14e975ad84b29502a65623af5df45edd313e Mon Sep 17 00:00:00 2001 From: shenmo Date: Wed, 27 Aug 2025 11:20:58 +0000 Subject: [PATCH 124/126] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=AF=8F=E6=AC=A1?= =?UTF-8?q?=E9=83=BD=E6=8F=90=E7=A4=BA=E9=A6=96=E6=AC=A1=E5=AE=89=E8=A3=85?= =?UTF-8?q?ACE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: shenmo --- tool/ssaudit | 36 +++++++++++++++++++++++++++++------- tool/ssinstall | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/tool/ssaudit b/tool/ssaudit index c6f0a58..2afffe6 100755 --- a/tool/ssaudit +++ b/tool/ssaudit @@ -200,19 +200,41 @@ function ensure_aptss_exist() { } export -f ensure_aptss_exist +# 确保ACE环境存在 # 确保ACE环境存在 function ensure_ace_env() { local ace_env_pkg="${1}" - + local ace_cmd="" - echo "ACE环境$ace_env_pkg正在尝试安装..." - zenity --info --text="首次使用$ace_env_pkg环境,重启或注销桌面后才能在启动器中展示,不影响应用启动。安装将在后台继续。" --title="ACE环境安装" & - if ! aptss install -y "$ace_env_pkg"; then - echo "安装$ace_env_pkg失败" - return 1 + # 根据配置找到对应命令 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + if [ "${ace_entry#*:}" = "$ace_env_pkg" ]; then + ace_cmd="${ace_entry%%:*}" + break fi - return 0 + done + + # 如果命令存在,则说明环境已可用 + if command -v "$ace_cmd" &>/dev/null; then + return 0 + fi + + echo "ACE环境 $ace_env_pkg 未安装,正在尝试安装..." + zenity --info --text="首次使用 $ace_env_pkg 环境,重启或注销桌面后才能在启动器中展示,不影响应用启动。安装将在后台继续。" --title="ACE环境安装" & + if ! aptss install -y "$ace_env_pkg"; then + echo "安装 $ace_env_pkg 失败" + return 1 + fi + + # 再次确认命令 + if command -v "$ace_cmd" &>/dev/null; then + return 0 + else + echo "ACE环境 $ace_env_pkg 安装后未检测到命令 $ace_cmd" + return 1 + fi } + export user=$(who | awk '{print $1}' | head -n 1) # 在桌面创建快捷方式 function create_desktop_file() { diff --git a/tool/ssinstall b/tool/ssinstall index 1617d9f..e7be005 100755 --- a/tool/ssinstall +++ b/tool/ssinstall @@ -200,18 +200,41 @@ function ensure_aptss_exist() { } export -f ensure_aptss_exist +# 确保ACE环境存在 # 确保ACE环境存在 function ensure_ace_env() { local ace_env_pkg="${1}" - - echo "ACE环境$ace_env_pkg正在尝试安装..." - zenity --info --text="首次使用$ace_env_pkg环境,重启或注销桌面后才能在启动器中展示,不影响应用启动。安装将在后台继续。" --title="ACE环境安装" & - if ! aptss install -y "$ace_env_pkg"; then - echo "安装$ace_env_pkg失败" - return 1 + local ace_cmd="" + + # 根据配置找到对应命令 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + if [ "${ace_entry#*:}" = "$ace_env_pkg" ]; then + ace_cmd="${ace_entry%%:*}" + break fi - return 0 + done + + # 如果命令存在,则说明环境已可用 + if command -v "$ace_cmd" &>/dev/null; then + return 0 + fi + + echo "ACE环境 $ace_env_pkg 未安装,正在尝试安装..." + zenity --info --text="首次使用 $ace_env_pkg 环境,重启或注销桌面后才能在启动器中展示,不影响应用启动。安装将在后台继续。" --title="ACE环境安装" & + if ! aptss install -y "$ace_env_pkg"; then + echo "安装 $ace_env_pkg 失败" + return 1 + fi + + # 再次确认命令 + if command -v "$ace_cmd" &>/dev/null; then + return 0 + else + echo "ACE环境 $ace_env_pkg 安装后未检测到命令 $ace_cmd" + return 1 + fi } + export user=$(who | awk '{print $1}' | head -n 1) # 在桌面创建快捷方式 function create_desktop_file() { From 85564bf8ac74d884642cc290fcac8c6b91467321 Mon Sep 17 00:00:00 2001 From: momen Date: Thu, 28 Aug 2025 13:32:11 +0000 Subject: [PATCH 125/126] =?UTF-8?q?!353=20update:=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E4=B8=BA4.8.1~test2,=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=9B=B4=E6=96=B0=E5=99=A8=E5=AE=89=E8=A3=85=E8=BD=AF?= =?UTF-8?q?=E4=BB=B6=E5=AE=8C=E6=88=90=E5=90=8E=E4=BB=8D=E7=84=B6=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E4=B8=8B=E8=BD=BD=E5=AE=8C=E6=88=90=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20Merge=20pull=20request=20!353=20from=20momen/dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debian/changelog | 6 + spark-update-tool/.gitignore | 62 --- spark-update-tool/CMakeLists.txt | 58 --- spark-update-tool/debian/changelog | 5 - spark-update-tool/debian/compat | 1 - spark-update-tool/debian/control | 13 - spark-update-tool/debian/copyright | 7 - spark-update-tool/debian/install | 3 - spark-update-tool/debian/postrm | 11 - spark-update-tool/debian/rules | 7 - spark-update-tool/debian/source/format | 1 - .../debian/spark-update-tool.desktop | 9 - .../resources/128*128/spark-update-tool.png | Bin 9142 -> 0 bytes spark-update-tool/resources/default_icon.svg | 1 - spark-update-tool/resources/down_arrow.svg | 1 - .../resources/spark-update-tool.svg | 1 - spark-update-tool/src/appdelegate.cpp | 321 -------------- spark-update-tool/src/appdelegate.h | 46 -- spark-update-tool/src/applistmodel.cpp | 62 --- spark-update-tool/src/applistmodel.h | 26 -- spark-update-tool/src/aptssupdater.cpp | 398 ------------------ spark-update-tool/src/aptssupdater.h | 28 -- spark-update-tool/src/downloadmanager.cpp | 117 ----- spark-update-tool/src/downloadmanager.h | 28 -- spark-update-tool/src/icons.qrc | 8 - spark-update-tool/src/main.cpp | 86 ---- spark-update-tool/src/mainwindow.cpp | 229 ---------- spark-update-tool/src/mainwindow.h | 33 -- spark-update-tool/src/mainwindow.ui | 256 ----------- src/spark-update-tool/README.md | 26 +- .../spark-update-tool/README.zh.md | 1 + src/spark-update-tool/src/appdelegate.cpp | 41 +- 32 files changed, 37 insertions(+), 1855 deletions(-) delete mode 100644 spark-update-tool/.gitignore delete mode 100644 spark-update-tool/CMakeLists.txt delete mode 100644 spark-update-tool/debian/changelog delete mode 100644 spark-update-tool/debian/compat delete mode 100644 spark-update-tool/debian/control delete mode 100644 spark-update-tool/debian/copyright delete mode 100644 spark-update-tool/debian/install delete mode 100644 spark-update-tool/debian/postrm delete mode 100755 spark-update-tool/debian/rules delete mode 100644 spark-update-tool/debian/source/format delete mode 100644 spark-update-tool/debian/spark-update-tool.desktop delete mode 100644 spark-update-tool/resources/128*128/spark-update-tool.png delete mode 100644 spark-update-tool/resources/default_icon.svg delete mode 100644 spark-update-tool/resources/down_arrow.svg delete mode 100644 spark-update-tool/resources/spark-update-tool.svg delete mode 100644 spark-update-tool/src/appdelegate.cpp delete mode 100644 spark-update-tool/src/appdelegate.h delete mode 100644 spark-update-tool/src/applistmodel.cpp delete mode 100644 spark-update-tool/src/applistmodel.h delete mode 100644 spark-update-tool/src/aptssupdater.cpp delete mode 100644 spark-update-tool/src/aptssupdater.h delete mode 100644 spark-update-tool/src/downloadmanager.cpp delete mode 100644 spark-update-tool/src/downloadmanager.h delete mode 100644 spark-update-tool/src/icons.qrc delete mode 100644 spark-update-tool/src/main.cpp delete mode 100644 spark-update-tool/src/mainwindow.cpp delete mode 100644 spark-update-tool/src/mainwindow.h delete mode 100644 spark-update-tool/src/mainwindow.ui rename spark-update-tool/README.md => src/spark-update-tool/README.zh.md (88%) diff --git a/debian/changelog b/debian/changelog index 0d5f275..7100157 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +spark-store (4.8.1~test2) UNRELEASED; urgency=medium + + * 修复更新器安装软件完成后仍然显示下载完成的问题 + + -- momen Tue, 28 Aug 2025 01:03:08 +0800 + spark-store (4.8.1~test1) UNRELEASED; urgency=medium * 添加全新的更新器 diff --git a/spark-update-tool/.gitignore b/spark-update-tool/.gitignore deleted file mode 100644 index 79c9ac7..0000000 --- a/spark-update-tool/.gitignore +++ /dev/null @@ -1,62 +0,0 @@ -build -.vscode -.cache -CMakeLists.txt.user -CMakeLists.txt.user.* -obj-x86_64-linux-gnu -# C++ objects and libs -*.slo -*.lo -*.o -*.a -*.la -*.lai -*.so -*.dll -*.dylib - -# Qt-es -object_script.*.Release -object_script.*.Debug -*_plugin_import.cpp -/.qmake.cache -/.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* -*.moc -moc_*.cpp -moc_*.h -qrc_*.cpp -ui_*.h -*.qmlc -*.jsc -Makefile* -*build-* - -# Qt unit tests -target_wrapper.* - -# Qt qm files -translations/*.qm - -# QtCreator -*.autosave - -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* - -# QtCreator CMake -CMakeLists.txt.user* -build - -# Debian dpkg-buildpackage -debian/*.debhelper* -debian/files -debian/*.substvars -debian/spark-update-tool - -.vscode/* -src/spark-update-tool diff --git a/spark-update-tool/CMakeLists.txt b/spark-update-tool/CMakeLists.txt deleted file mode 100644 index 49a4d5e..0000000 --- a/spark-update-tool/CMakeLists.txt +++ /dev/null @@ -1,58 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -project(spark-update-tool VERSION 0.1 LANGUAGES CXX) - -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - - -find_package(Qt5 REQUIRED COMPONENTS Widgets Network Concurrent) - - -find_package(Qt5 REQUIRED COMPONENTS Widgets Network Concurrent Core Gui) - -# 定义所有项目源文件,现在无需条件判断 -set(PROJECT_SOURCES - src/main.cpp - src/mainwindow.cpp - src/mainwindow.h - src/mainwindow.ui - src/aptssupdater.h src/aptssupdater.cpp - src/icons.qrc - src/appdelegate.h src/appdelegate.cpp - src/applistmodel.h src/applistmodel.cpp - src/downloadmanager.h src/downloadmanager.cpp -) - - -add_executable(spark-update-tool - ${PROJECT_SOURCES} -) - -target_link_libraries(spark-update-tool PRIVATE - Qt5::Widgets - Qt5::Network - Qt5::Concurrent - Qt5::Core - Qt5::Gui -) - - -set_target_properties(spark-update-tool PROPERTIES - # ${BUNDLE_ID_OPTION} # 如果上面取消注释,这里也取消注释 - MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - -) - -include(GNUInstallDirs) -install(TARGETS spark-update-tool - BUNDLE DESTINATION . - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) - diff --git a/spark-update-tool/debian/changelog b/spark-update-tool/debian/changelog deleted file mode 100644 index b414fea..0000000 --- a/spark-update-tool/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -spark-update-tool (1.0.0) unstable; urgency=low - - * Initial release. - - -- momen Wed, 18 Jun 2025 00:00:00 +0000 \ No newline at end of file diff --git a/spark-update-tool/debian/compat b/spark-update-tool/debian/compat deleted file mode 100644 index f11c82a..0000000 --- a/spark-update-tool/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 \ No newline at end of file diff --git a/spark-update-tool/debian/control b/spark-update-tool/debian/control deleted file mode 100644 index 19c9d62..0000000 --- a/spark-update-tool/debian/control +++ /dev/null @@ -1,13 +0,0 @@ -Source: spark-update-tool -Section: utils -Priority: optional -Maintainer: momen -Build-Depends: debhelper (>= 9) -Standards-Version: 3.9.6 -Homepage: https://gitee.com/spark-store-project/Spark-Update-Tool - -Package: spark-update-tool -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Description: Spark Update Tool - 星火应用商店更新组件。This package provides the Spark Update Tool. It includes features for checking for updates, downloading, and applying them seamlessly. \ No newline at end of file diff --git a/spark-update-tool/debian/copyright b/spark-update-tool/debian/copyright deleted file mode 100644 index 163489e..0000000 --- a/spark-update-tool/debian/copyright +++ /dev/null @@ -1,7 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: spark-update-tool -Source: https://gitee.com/spark-store-project/Spark-Update-Tool - -Files: * -Copyright: 2025, momen -License: GPL-3.0+ \ No newline at end of file diff --git a/spark-update-tool/debian/install b/spark-update-tool/debian/install deleted file mode 100644 index ed44f2c..0000000 --- a/spark-update-tool/debian/install +++ /dev/null @@ -1,3 +0,0 @@ -build/spark-update-tool /usr/bin/ -debian/spark-update-tool.desktop /usr/share/applications -resources/128*128/spark-update-tool.png /usr/share/icons/hicolor/128x128/apps diff --git a/spark-update-tool/debian/postrm b/spark-update-tool/debian/postrm deleted file mode 100644 index 263e9c4..0000000 --- a/spark-update-tool/debian/postrm +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -set -e - -case "$1" in - purge) - - rm -rf /usr/share/spark-update-tool - ;; -esac - -exit 0 \ No newline at end of file diff --git a/spark-update-tool/debian/rules b/spark-update-tool/debian/rules deleted file mode 100755 index 1159ac4..0000000 --- a/spark-update-tool/debian/rules +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/make -f - -%: - dh $@ - -override_dh_auto_configure: - dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/usr diff --git a/spark-update-tool/debian/source/format b/spark-update-tool/debian/source/format deleted file mode 100644 index 9f67427..0000000 --- a/spark-update-tool/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) \ No newline at end of file diff --git a/spark-update-tool/debian/spark-update-tool.desktop b/spark-update-tool/debian/spark-update-tool.desktop deleted file mode 100644 index 688ba0c..0000000 --- a/spark-update-tool/debian/spark-update-tool.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Version=1.0 -Name=Spark Update Tool -Comment=A Qt-based application for managing and updating software -Exec=spark-update-tool -Icon=spark-update-tool -Terminal=false -Type=Application -Categories=Utility; diff --git a/spark-update-tool/resources/128*128/spark-update-tool.png b/spark-update-tool/resources/128*128/spark-update-tool.png deleted file mode 100644 index 138ed83770cf4cce8fdead9c11908a0446e8a135..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9142 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 AL;wH) diff --git a/spark-update-tool/resources/default_icon.svg b/spark-update-tool/resources/default_icon.svg deleted file mode 100644 index 7c9cb42..0000000 --- a/spark-update-tool/resources/default_icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/spark-update-tool/resources/down_arrow.svg b/spark-update-tool/resources/down_arrow.svg deleted file mode 100644 index 4989662..0000000 --- a/spark-update-tool/resources/down_arrow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/spark-update-tool/resources/spark-update-tool.svg b/spark-update-tool/resources/spark-update-tool.svg deleted file mode 100644 index 3d21d81..0000000 --- a/spark-update-tool/resources/spark-update-tool.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/spark-update-tool/src/appdelegate.cpp b/spark-update-tool/src/appdelegate.cpp deleted file mode 100644 index 1a1c3e9..0000000 --- a/spark-update-tool/src/appdelegate.cpp +++ /dev/null @@ -1,321 +0,0 @@ -#include "appdelegate.h" -#include -#include -#include -#include -#include -#include -#include -#include - -AppDelegate::AppDelegate(QObject *parent) - : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)), m_installProcess(nullptr) { - connect(m_downloadManager, &DownloadManager::downloadFinished, this, - [this](const QString &packageName, bool success) { - if (m_downloads.contains(packageName)) { - m_downloads[packageName].isDownloading = false; - emit updateDisplay(packageName); - qDebug() << (success ? "下载完成:" : "下载失败:") << packageName; - if (success) { - enqueueInstall(packageName); - } - } - }); - - connect(m_downloadManager, &DownloadManager::downloadProgress, this, - [this](const QString &packageName, int progress) { - if (m_downloads.contains(packageName)) { - m_downloads[packageName].progress = progress; - qDebug()<save(); - - if (option.state & QStyle::State_Selected) - painter->fillRect(option.rect, option.palette.highlight()); - else - painter->fillRect(option.rect, QColor("#F3F4F6")); - - QFont boldFont = option.font; - boldFont.setBold(true); - QFont normalFont = option.font; - - QString name = index.data(Qt::DisplayRole).toString(); - QString currentVersion = index.data(Qt::UserRole + 2).toString(); - QString newVersion = index.data(Qt::UserRole + 3).toString(); - QString iconPath = index.data(Qt::UserRole + 4).toString(); - QString size = index.data(Qt::UserRole + 5).toString(); - QString description = index.data(Qt::UserRole + 6).toString(); - - QRect rect = option.rect; - int margin = 10, spacing = 6, iconSize = 40; - - QRect iconRect(rect.left() + margin, rect.top() + (rect.height() - iconSize) / 2, iconSize, iconSize); - QIcon(iconPath).paint(painter, iconRect); - - int textX = iconRect.right() + margin; - int textWidth = rect.width() - textX - 100; - - QRect nameRect(textX, rect.top() + margin, textWidth, 20); - painter->setFont(boldFont); - painter->setPen(QColor("#333333")); - painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name); - - QRect versionRect(textX, nameRect.bottom() + spacing, textWidth, 20); - painter->setFont(normalFont); - painter->setPen(QColor("#888888")); - painter->drawText(versionRect, Qt::AlignLeft | Qt::AlignVCenter, - QString("当前版本: %1 → 新版本: %2").arg(currentVersion, newVersion)); - - QRect descRect(textX, versionRect.bottom() + spacing, textWidth, 40); - painter->setFont(normalFont); - painter->setPen(QColor("#AAAAAA")); - painter->drawText(descRect, Qt::TextWordWrap, - QString("包大小:%1 MB").arg(QString::number(size.toDouble() / (1024 * 1024), 'f', 2))); - - QString packageName = index.data(Qt::UserRole + 1).toString(); - bool isDownloading = m_downloads.contains(packageName) && m_downloads[packageName].isDownloading; - int progress = m_downloads.value(packageName, DownloadInfo{0, false}).progress; - bool isInstalled = m_downloads.value(packageName).isInstalled; - bool isInstalling = m_downloads.value(packageName).isInstalling; - - if (isDownloading) { - QRect progressRect(rect.right() - 270, rect.top() + (rect.height() - 20) / 2, 150, 20); - QStyleOptionProgressBar progressBarOption; - progressBarOption.rect = progressRect; - progressBarOption.minimum = 0; - progressBarOption.maximum = 100; - progressBarOption.progress = progress; - progressBarOption.text = QString("%1%").arg(progress); - progressBarOption.textVisible = true; - QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); - - // 修改后的取消按钮绘制代码 - QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); - painter->setPen(Qt::NoPen); - painter->setBrush(QColor("#ff4444")); // 红色背景 - painter->drawRoundedRect(buttonRect, 4, 4); // 圆角矩形 - - painter->setPen(Qt::white); // 白色文字 - painter->setFont(option.font); - painter->drawText(buttonRect, Qt::AlignCenter, "取消"); - } else if (isInstalling) { - // 安装中:显示转圈和文字 - QRect spinnerRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 30, 30); - int angle = (m_spinnerTimer.elapsed() / 10) % 360; - QPen pen(QColor("#2563EB"), 3); - painter->setPen(pen); - painter->setRenderHint(QPainter::Antialiasing, true); - QRectF arcRect = spinnerRect.adjusted(3, 3, -3, -3); - painter->drawArc(arcRect, angle * 16, 120 * 16); // 120度弧 - - QRect textRect(option.rect.right() - 120, option.rect.top() + (option.rect.height() - 30) / 2, 110, 30); - painter->setPen(QColor("#2563EB")); - painter->setFont(option.font); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, "正在安装中"); - } else { - QRect buttonRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30); - painter->setPen(Qt::NoPen); - if (isInstalled) { - painter->setBrush(QColor("#10B981")); - painter->drawRoundedRect(buttonRect, 4, 4); - painter->setPen(Qt::white); - painter->drawText(buttonRect, Qt::AlignCenter, "已安装"); - } else if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { - // 下载完成,按钮绿色,样式不变 - painter->setBrush(QColor("#10B981")); - painter->drawRoundedRect(buttonRect, 4, 4); - painter->setPen(Qt::white); - painter->drawText(buttonRect, Qt::AlignCenter, "下载完成"); - // 不需要特殊处理样式,交互在 editorEvent 控制 - } else { - painter->setBrush(QColor("#e9effd")); - painter->drawRoundedRect(buttonRect, 4, 4); - painter->setPen(QColor("#2563EB")); - painter->drawText(buttonRect, Qt::AlignCenter, "更新"); - } - } - - painter->restore(); -} - -QSize AppDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - return QSize(option.rect.width(), 110); -} - -bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) { - if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEvent = static_cast(event); - QRect rect = option.rect; - QString packageName = index.data(Qt::UserRole + 1).toString(); - - if (m_downloads.contains(packageName) && m_downloads[packageName].isDownloading) { - QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20); - if (cancelButtonRect.contains(mouseEvent->pos())) { - m_downloadManager->cancelDownload(packageName); - m_downloads.remove(packageName); - emit updateDisplay(packageName); - return true; - } - } else { - QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); - if (buttonRect.contains(mouseEvent->pos())) { - // 判断是否为“下载完成”状态,如果是则不响应点击 - if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { - // “下载完成”状态,按钮失效,点击无效 - return false; - } - QString downloadUrl = index.data(Qt::UserRole + 7).toString(); - QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); - - m_downloads[packageName] = {0, true}; - m_downloadManager->startDownload(packageName, downloadUrl, outputPath); - emit updateDisplay(packageName); - return true; - } - } - } - - return QStyledItemDelegate::editorEvent(event, model, option, index); -} - -void AppDelegate::startDownloadForAll() { - if (!m_model) return; - for (int row = 0; row < m_model->rowCount(); ++row) { - QModelIndex index = m_model->index(row, 0); - QString packageName = index.data(Qt::UserRole + 1).toString(); - if (m_downloads.contains(packageName) && (m_downloads[packageName].isDownloading || m_downloads[packageName].isInstalled)) - continue; // 跳过正在下载或已安装的 - QString downloadUrl = index.data(Qt::UserRole + 7).toString(); - QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); - m_downloads[packageName] = {0, true, false}; - m_downloadManager->startDownload(packageName, downloadUrl, outputPath); - emit updateDisplay(packageName); - } -} - -// 新增:安装队列相关实现 -void AppDelegate::enqueueInstall(const QString &packageName) { - m_installQueue.enqueue(packageName); - if (!m_isInstalling) { - startNextInstall(); - } -} - -void AppDelegate::startNextInstall() { - if (m_installQueue.isEmpty()) { - m_isInstalling = false; - m_installingPackage.clear(); - return; - } - m_isInstalling = true; - QString packageName = m_installQueue.dequeue(); - m_installingPackage = packageName; - m_downloads[packageName].isInstalling = true; - emit updateDisplay(packageName); - - // 查找 /tmp 下以包名开头的 .deb 文件 - QDir tempDir(QDir::tempPath()); - QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName), QDir::Files); - QString debPath; - if (!debs.isEmpty()) { - debPath = tempDir.absoluteFilePath(debs.first()); - } else { - debs = tempDir.entryList(QStringList() << QString("%1*.deb").arg(packageName), QDir::Files); - if (!debs.isEmpty()) { - debPath = tempDir.absoluteFilePath(debs.first()); - } - } - - if (debPath.isEmpty()) { - qWarning() << "未找到deb文件,包名:" << packageName; - m_downloads[packageName].isInstalling = false; - emit updateDisplay(packageName); - m_installingPackage.clear(); - startNextInstall(); - return; - } - - m_installProcess = new QProcess(this); - - // 新增:准备安装日志文件 - QString logPath = QString("/tmp/%1_install.log").arg(packageName); - QFile *logFile = new QFile(logPath, m_installProcess); - if (logFile->open(QIODevice::Append | QIODevice::Text)) { - // 设置权限为777 - QFile::setPermissions(logPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | - QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | - QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); - connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName, logFile]() { - QByteArray out = m_installProcess->readAllStandardOutput(); - logFile->write(out); - logFile->flush(); - QString text = QString::fromLocal8Bit(out); - qDebug().noquote() << text; - // 检查“软件包已安装”关键字 - if (text.contains(QStringLiteral("软件包已安装"))) { - m_downloads[packageName].isInstalling = false; - m_downloads[packageName].isInstalled = true; - emit updateDisplay(packageName); - } - }); - connect(m_installProcess, &QProcess::readyReadStandardError, this, [this, logFile]() { - QByteArray err = m_installProcess->readAllStandardError(); - logFile->write(err); - logFile->flush(); - qDebug().noquote() << QString::fromLocal8Bit(err); - }); - connect(m_installProcess, QOverload::of(&QProcess::finished), - this, [this, packageName, logFile](int /*exitCode*/, QProcess::ExitStatus /*status*/) { - if (logFile) logFile->close(); - // 若未检测到“软件包已安装”,此处兜底 - if (!m_downloads[packageName].isInstalled) { - m_downloads[packageName].isInstalling = false; - } - emit updateDisplay(packageName); - m_installProcess->deleteLater(); - m_installProcess = nullptr; - m_installingPackage.clear(); - startNextInstall(); - }); - } else { - // 日志文件无法打开时,仍然要连接原有信号 - connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName]() { - QByteArray out = m_installProcess->readAllStandardOutput(); - QString text = QString::fromLocal8Bit(out); - qDebug().noquote() << text; - if (text.contains(QStringLiteral("软件包已安装"))) { - m_downloads[packageName].isInstalling = false; - m_downloads[packageName].isInstalled = true; - emit updateDisplay(packageName); - } - }); - connect(m_installProcess, &QProcess::readyReadStandardError, this, [this]() { - QByteArray err = m_installProcess->readAllStandardError(); - qDebug().noquote() << QString::fromLocal8Bit(err); - }); - connect(m_installProcess, QOverload::of(&QProcess::finished), - this, [this, packageName](int /*exitCode*/, QProcess::ExitStatus /*status*/) { - emit updateDisplay(packageName); - m_installProcess->deleteLater(); - m_installProcess = nullptr; - m_installingPackage.clear(); - startNextInstall(); - }); - } - - // 注意参数顺序:deb路径在前,--no-create-desktop-entry在后 - QStringList args; - args << debPath << "--no-create-desktop-entry"; - m_installProcess->start("ssinstall", args); -} diff --git a/spark-update-tool/src/appdelegate.h b/spark-update-tool/src/appdelegate.h deleted file mode 100644 index 0545155..0000000 --- a/spark-update-tool/src/appdelegate.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "downloadmanager.h" - -struct DownloadInfo { - int progress = 0; - bool isDownloading = false; - bool isInstalled = false; - bool isInstalling = false; // 新增:标记是否正在安装 -}; - -class AppDelegate : public QStyledItemDelegate { - Q_OBJECT -public: - explicit AppDelegate(QObject *parent = nullptr); - - void setModel(QAbstractItemModel *model); - - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - bool editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) override; - void startDownloadForAll(); - -signals: - void updateDisplay(const QString &packageName); - -private: - DownloadManager *m_downloadManager; - QHash m_downloads; - QAbstractItemModel *m_model = nullptr; - - QQueue m_installQueue; - bool m_isInstalling = false; - QProcess *m_installProcess = nullptr; - QString m_installingPackage; // 当前正在安装的包名 - QElapsedTimer m_spinnerTimer; // 用于转圈动画 - - void enqueueInstall(const QString &packageName); - void startNextInstall(); -}; diff --git a/spark-update-tool/src/applistmodel.cpp b/spark-update-tool/src/applistmodel.cpp deleted file mode 100644 index 31e244a..0000000 --- a/spark-update-tool/src/applistmodel.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "applistmodel.h" - -AppListModel::AppListModel(QObject *parent) : QAbstractListModel(parent) {} - -int AppListModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - return m_data.size(); -} - -QVariant AppListModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() >= m_data.size()) - return QVariant(); - - const QVariantMap &map = m_data.at(index.row()); // 直接访问 QVariantMap - switch (role) { - case Qt::DisplayRole: - return map.value("name"); - case Qt::UserRole + 1: // 包名 - return map.value("package"); - case Qt::UserRole + 2: // 当前版本 - return map.value("current_version"); - case Qt::UserRole + 3: // 新版本 - return map.value("new_version"); - case Qt::UserRole + 4: // 图标路径 - return map.value("icon"); - case Qt::UserRole + 5: // 文件大小 - return map.value("size"); - case Qt::UserRole + 6: // 描述 - return map.value("description"); - case Qt::UserRole + 7: // 下载 URL - return map.value("download_url"); // 返回下载 URL - default: - return QVariant(); - } -} - -void AppListModel::setUpdateData(const QJsonArray &updateInfo) -{ - beginResetModel(); - m_data.clear(); // 清空 QList - - for (const auto &item : updateInfo) { - QJsonObject obj = item.toObject(); - QVariantMap map; - map["package"] = obj["package"].toString(); - map["name"] = obj["name"].toString(); - map["current_version"] = obj["current_version"].toString(); - map["new_version"] = obj["new_version"].toString(); - map["icon"] = obj["icon"].toString(); - map["size"] = obj["size"].toString(); - map["download_url"] = obj["download_url"].toString(); // 确保设置下载 URL - m_data.append(map); // 添加到 QList - - qDebug() << "设置到模型的包名:" << map["package"].toString(); - qDebug() << "设置到模型的下载 URL:" << map["download_url"].toString(); // 检查设置的数据 - } - - endResetModel(); -} diff --git a/spark-update-tool/src/applistmodel.h b/spark-update-tool/src/applistmodel.h deleted file mode 100644 index 2fb36c0..0000000 --- a/spark-update-tool/src/applistmodel.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef APPLISTMODEL_H -#define APPLISTMODEL_H - -#include -#include -// 添加 QJsonObject 头文件 -#include - -class AppListModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit AppListModel(QObject *parent = nullptr); - - // 重写方法 - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - // 设置更新数据 - void setUpdateData(const QJsonArray &data); - -private: - QList m_data; // 修改类型为 QList -}; - -#endif // APPLISTMODEL_H diff --git a/spark-update-tool/src/aptssupdater.cpp b/spark-update-tool/src/aptssupdater.cpp deleted file mode 100644 index c4bd906..0000000 --- a/spark-update-tool/src/aptssupdater.cpp +++ /dev/null @@ -1,398 +0,0 @@ -#include "aptssupdater.h" -#include -#include -#include -#include -#include - -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> 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()< -#include -#include -#include -#include -#include -class aptssUpdater : public QWidget -{ - Q_OBJECT -public: - explicit aptssUpdater(QWidget *parent = nullptr); - - QStringList getUpdateablePackages(); // 查询可更新包列表及更新内容 - QStringList getPackageSizes(); // 获取每个包的大小 - QStringList getDesktopAppNames(); // 获取桌面应用名称列表 - QStringList getPackageIcons(); // 获取包图标列表 - QJsonArray getUpdateInfoAsJson(); // 获取更新信息的 JSON 格式 - QString m_tempFilePath; -signals: -private: - bool checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName); - QStringList packageName; -}; - -#endif // APTSSUPDATER_H \ No newline at end of file diff --git a/spark-update-tool/src/downloadmanager.cpp b/spark-update-tool/src/downloadmanager.cpp deleted file mode 100644 index 9780324..0000000 --- a/spark-update-tool/src/downloadmanager.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "downloadmanager.h" -#include -#include -#include -#include -#include - -DownloadManager::DownloadManager(QObject *parent) : QObject(parent) -{ - cleanupTempFiles(); -} - -void DownloadManager::startDownload(const QString &packageName, const QString &url, const QString &outputPath) -{ - if (m_processes.contains(packageName)) { - qWarning() << packageName << " is already downloading."; - return; - } - - QString metalinkUrl = url + ".metalink"; - QFileInfo fileInfo(outputPath); - - QStringList arguments = { - "--enable-rpc=false", - "--console-log-level=warn", - "--summary-interval=1", - "--allow-overwrite=true", - "--dir=" + fileInfo.absolutePath(), - "--out=" + fileInfo.fileName(), - metalinkUrl - }; - - QProcess *process = new QProcess(this); - m_processes.insert(packageName, process); - - // 新增:准备日志文件 - QString logPath = QString("/tmp/%1_download.log").arg(packageName); - QFile *logFile = new QFile(logPath, process); - if (logFile->open(QIODevice::Append | QIODevice::Text)) { - // 设置权限为777 - QFile::setPermissions(logPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | - QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | - QFile::ReadOther | QFile::WriteOther | QFile::ExeOther); - connect(process, &QProcess::readyReadStandardOutput, this, [this, packageName, process, logFile]() { - while (process->canReadLine()) { - QString line = QString::fromUtf8(process->readLine()).trimmed(); - // 写入日志 - logFile->write(line.toUtf8() + '\n'); - logFile->flush(); - QRegularExpression regex(R"(\((\d+)%\))"); - QRegularExpressionMatch match = regex.match(line); - if (match.hasMatch()) { - int progress = match.captured(1).toInt(); - emit downloadProgress(packageName, progress); - } - } - }); - connect(process, &QProcess::readyReadStandardError, this, [process, logFile]() { - QByteArray err = process->readAllStandardError(); - logFile->write(err); - logFile->flush(); - }); - } - - connect(process, QOverload::of(&QProcess::finished), - this, [this, packageName, outputPath, logFile](int exitCode, QProcess::ExitStatus status) { - bool success = (exitCode == 0 && status == QProcess::NormalExit); - if (!success) { - qWarning() << "Download failed for" << packageName << "exit code:" << exitCode; - } - - removeAria2Files(outputPath); // 清理残留 .aria2 - emit downloadFinished(packageName, success); - - if (logFile) logFile->close(); - - QProcess *proc = m_processes.take(packageName); - if (proc) proc->deleteLater(); - }); - - process->start("aria2c", arguments); -} - -void DownloadManager::cancelDownload(const QString &packageName) -{ - if (!m_processes.contains(packageName)) return; - - QProcess *process = m_processes.take(packageName); - if (process) { - process->kill(); // 立即终止 - process->waitForFinished(3000); // 最多等待3秒 - process->deleteLater(); - } - - emit downloadFinished(packageName, false); // 显式通知取消 - -} - -void DownloadManager::removeAria2Files(const QString &filePath) -{ - QString ariaFile = filePath + ".aria2"; - QFile::remove(ariaFile); -} - -bool DownloadManager::isDownloading(const QString &packageName) const -{ - return m_processes.contains(packageName); -} - -void DownloadManager::cleanupTempFiles() -{ - QDir tempDir(QDir::tempPath()); - QStringList leftovers = tempDir.entryList(QStringList() << "*.aria2", QDir::Files); - for (const QString &f : leftovers) { - tempDir.remove(f); - } -} diff --git a/spark-update-tool/src/downloadmanager.h b/spark-update-tool/src/downloadmanager.h deleted file mode 100644 index 0614368..0000000 --- a/spark-update-tool/src/downloadmanager.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef DOWNLOADMANAGER_H -#define DOWNLOADMANAGER_H - -#include -#include -#include - -class DownloadManager : public QObject -{ - Q_OBJECT -public: - explicit DownloadManager(QObject *parent = nullptr); - void startDownload(const QString &packageName, const QString &url, const QString &outputPath); - void cancelDownload(const QString &packageName); - bool isDownloading(const QString &packageName) const; - -signals: - void downloadProgress(const QString &packageName, int progress); - void downloadFinished(const QString &packageName, bool success); - -private: - void cleanupTempFiles(); - void removeAria2Files(const QString &filePath); - - QMap m_processes; -}; - -#endif // DOWNLOADMANAGER_H diff --git a/spark-update-tool/src/icons.qrc b/spark-update-tool/src/icons.qrc deleted file mode 100644 index f81e1f8..0000000 --- a/spark-update-tool/src/icons.qrc +++ /dev/null @@ -1,8 +0,0 @@ - - - ../resources/down_arrow.svg - ../resources/default_icon.svg - ../resources/spark-update-tool.svg - ../resources/128*128/spark-update-tool.png - - diff --git a/spark-update-tool/src/main.cpp b/spark-update-tool/src/main.cpp deleted file mode 100644 index a3df5a4..0000000 --- a/spark-update-tool/src/main.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "mainwindow.h" -#include -#include -#include -#include // for geteuid -#include // for getenv -#include // For debugging output - -bool isRoot() { - return geteuid() == 0; -} - -bool elevateToRoot() { - QString program = QCoreApplication::applicationFilePath(); - qDebug() << "Current application path:" << program; - - QByteArray display = qgetenv("DISPLAY"); - QByteArray xauthority = qgetenv("XAUTHORITY"); - - QStringList args; - args << "env" - << "DISPLAY=" + display - << "XAUTHORITY=" + xauthority - << program; - - QProcess process; - process.setProgram("pkexec"); - process.setArguments(args); - - qDebug() << "Attempting to elevate using pkexec with arguments:" << args; - - process.start(); - if (!process.waitForStarted(5000)) { - qDebug() << "Failed to start pkexec."; - return false; - } - - // 阻塞等待提权进程退出(比如主程序窗口关闭) - if (!process.waitForFinished(-1)) { // 等待直到新进程退出 - qDebug() << "pkexec process waitForFinished failed."; - return false; - } - - int exitCode = process.exitCode(); - QProcess::ExitStatus exitStatus = process.exitStatus(); - - qDebug() << "pkexec exit code:" << exitCode; - qDebug() << "pkexec exit status:" << exitStatus; - qDebug() << "pkexec stderr:" << process.readAllStandardError(); - - return (exitStatus == QProcess::NormalExit && exitCode == 0); -} - -int main(int argc, char *argv[]) -{ - // 必须在 QGuiApplication 实例创建之前调用 - // QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); - - QApplication a(argc, argv); - a.setWindowIcon(QIcon(":/resources/128*128/spark-update-tool.png")); - if (!isRoot()) { - qDebug() << "Not running as root. Attempting to elevate..."; - if (!elevateToRoot()) { - qDebug() << "Elevation failed or pkexec command was not executed successfully."; - QMessageBox::critical(nullptr, - "权限不足", - "提权失败!\n\n您的系统可能不支持 `pkexec` 或 `polkit` 配置不正确," - "或者您取消了授权。\n\n请尝试使用 `sudo` 命令运行此程序:" - "\n\n在终端中输入:\n`sudo " + QCoreApplication::applicationName() + "`"); - return 0; // 提权失败,退出程序 - } else { - // 如果 elevateToRoot 返回 true,说明 pkexec 命令本身执行成功 - // 但这并不意味着原始程序以 root 权限启动了 - // 因为 elevateToRoot 启动的是一个新的进程,当前进程应该退出 - // 否则会并行运行两个程序实例 - qDebug() << "pkexec command executed successfully (new process likely started). Exiting current process."; - return 0; // 当前非root进程退出 - } - } else { - qDebug() << "Running as root."; - } - - MainWindow w; - w.show(); - return a.exec(); -} \ No newline at end of file diff --git a/spark-update-tool/src/mainwindow.cpp b/spark-update-tool/src/mainwindow.cpp deleted file mode 100644 index dde2a4c..0000000 --- a/spark-update-tool/src/mainwindow.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include "mainwindow.h" -#include "./ui_mainwindow.h" -#include -#include -#include -#include // 新增 -#include // 新增 -#include -#include -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::MainWindow) - , m_model(new AppListModel(this)) - , m_delegate(new AppDelegate(this)) -{ - QIcon icon(":/resources/128*128/spark-update-tool.png"); - setWindowIcon(icon); - QProgressDialog *progressDialog = new QProgressDialog("正在与服务器通信,获取更新信息中...", QString(), 0, 0, this); - progressDialog->setWindowModality(Qt::ApplicationModal); - progressDialog->setCancelButton(nullptr); - progressDialog->setWindowTitle("请稍候"); - progressDialog->setMinimumDuration(0); - progressDialog->setWindowFlags(progressDialog->windowFlags() & ~Qt::WindowCloseButtonHint); // 禁用关闭按钮 - progressDialog->show(); - //异步执行runAptssUpgrade - QFutureWatcher *watcher = new QFutureWatcher(this); - connect(watcher, &QFutureWatcher::finished, this, [=]() { - progressDialog->close(); - progressDialog->deleteLater(); - watcher->deleteLater(); - ui->setupUi(this); - QIcon icon(":/resources/128*128/spark-update-tool.png"); - setWindowIcon(icon); - // 创建 QListView 并设置父控件为 ui->appWidget - listView = new QListView(ui->appWidget); - listView->setModel(m_model); - listView->setItemDelegate(m_delegate); - - // 新增:确保 delegate 拥有 model 指针 - m_delegate->setModel(m_model); - - // 设置 QListView 填充 ui->appWidget - QVBoxLayout *layout = new QVBoxLayout(ui->appWidget); - layout->addWidget(listView); - layout->setContentsMargins(0, 0, 0, 0); - connect(m_delegate, &AppDelegate::updateDisplay, this, [=](const QString &packageName) { - for (int i = 0; i < m_model->rowCount(); ++i) { - QModelIndex index = m_model->index(i); - if (index.data(Qt::UserRole + 1).toString() == packageName) { - m_model->dataChanged(index, index); // 刷新该行 - break; - } - } - }); - - // 新增:点击“更新全部”按钮批量下载 - connect(ui->updatePushButton, &QPushButton::clicked, this, [=](){ - qDebug()<<"更新全部按钮被点击"; - m_delegate->startDownloadForAll(); - }); - - checkUpdates(); - initStyle(); - }); - - // 启动异步任务 - watcher->setFuture(QtConcurrent::run([this](){ - runAptssUpgrade(); - })); -} -//初始化控件样式 -void MainWindow::initStyle() -{ - //设置窗口标题 - this->setWindowTitle("软件更新中心"); - - //查询框样式 - ui->plainTextEdit->setStyleSheet(R"( - QPlainTextEdit { - background-color: #FFFFFF; - border: 1px solid #E5E7EB; - border-radius: 4px; - padding-top: 8px; - padding-bottom: 8px; - font-size: 9px; - line-height: 1.4; - color: #9CA3AF; - } - )"); - - ui->plainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui->plainTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - //筛选框样式 - ui->FilterComboBox->setStyleSheet(R"( - QComboBox { - background-color: #FFFFFF; - border: 1px solid #E5E7EB; - border-radius: 4px; - color: #4B5563; - padding: 4px 8px; - } - - QComboBox::drop-down { - border: none; - width: 20px; - } - - QComboBox::down-arrow { - image: url(:/resources/down_arrow.svg); - width: 12px; - height: 16px; - } - QComboBox QAbstractItemView { - background-color: #FFFFFF; - border: 1px solid #E5E7EB; - color: #4B5563; - selection-background-color: #F3F4F6; - selection-color: #111827; - } - )"); - - //更新软件按钮样式 - ui->updatePushButton->setStyleSheet(R"( - QPushButton { - background-color: #2563EB; - color: #FFFFFF; - border: none; - border-radius: 4px; - font-size: 14px; - padding: 6px 12px; - text-align: center; - } - - QPushButton:hover { - background-color: #1D4ED8; /* 深一点的 hover 效果,可选 */ - } - - QPushButton:pressed { - background-color: #1E40AF; /* 按下效果,可选 */ - } - - QPushButton:disabled { - background-color: #A5B4FC; - color: #F9FAFB; - } - )"); - - //设置背景填充颜色 - ui->backgroundWidget->setStyleSheet(R"( - QWidget { - background-color: #FFFFFF; - border-radius: 12px; - } - )"); - - //设置主背景颜色 - this->setStyleSheet("background-color: #F8FAFC;"); - - // 添加滚动条样式 - this->setStyleSheet(R"( - QScrollBar:vertical { - background: #F3F4F6; - width: 8px; - margin: 0px; - } - QScrollBar::handle:vertical { - background: #D1D5DB; - border-radius: 4px; - min-height: 30px; - } - QScrollBar::handle:vertical:hover { - background: #9CA3AF; - } - QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - background: none; - height: 0px; - } - - QScrollBar:horizontal { - background: #F3F4F6; - height: 8px; - margin: 0px; - } - QScrollBar::handle:horizontal { - background: #D1D5DB; - border-radius: 4px; - min-width: 30px; - } - QScrollBar::handle:horizontal:hover { - background: #9CA3AF; - } - )"); -} -void MainWindow::checkUpdates() -{ - aptssUpdater updater; - QJsonArray updateInfo = updater.getUpdateInfoAsJson(); - m_model->setUpdateData(updateInfo); - - for (const auto &item : updateInfo) { - QJsonObject obj = item.toObject(); - qDebug() << "模型设置的包名:" << obj["package"].toString(); - qDebug() << "模型设置的下载 URL:" << obj["download_url"].toString(); // 检查模型数据 - } -} - -void MainWindow::runAptssUpgrade() -{ - QProcess process; - QStringList args; - args << "sudo" <<"aptss" << "ssupdate"; - process.start("sudo", args); - if (!process.waitForStarted(5000)) { - QMessageBox::warning(this, "升级失败", "无法启动 sudo aptss ssupdate"); - return; - } - process.write("n\n"); - process.closeWriteChannel(); - process.waitForFinished(-1); - if (process.exitCode() != 0) { - QMessageBox::warning(this, "升级失败", "执行 sudo aptss ssupdate 失败,请检查系统环境。"); - } -} - -MainWindow::~MainWindow() -{ - delete ui; -} diff --git a/spark-update-tool/src/mainwindow.h b/spark-update-tool/src/mainwindow.h deleted file mode 100644 index e36db88..0000000 --- a/spark-update-tool/src/mainwindow.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include "aptssupdater.h" -#include "applistmodel.h" -#include "appdelegate.h" -#include - -QT_BEGIN_NAMESPACE -namespace Ui { -class MainWindow; -} -QT_END_NAMESPACE - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - -private: - Ui::MainWindow *ui; - void checkUpdates(); - void initStyle(); - void runAptssUpgrade(); - AppListModel *m_model; - AppDelegate *m_delegate; - QListView *listView; // 声明 QListView 指针 -}; -#endif // MAINWINDOW_H diff --git a/spark-update-tool/src/mainwindow.ui b/spark-update-tool/src/mainwindow.ui deleted file mode 100644 index 2a58287..0000000 --- a/spark-update-tool/src/mainwindow.ui +++ /dev/null @@ -1,256 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1440 - 858 - - - - MainWindow - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - Noto Sans Vai - 22 - - - - 软件更新中心 - - - - - - - Qt::Horizontal - - - - 1245 - 20 - - - - - - - - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 320 - 38 - - - - - 320 - 38 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 200 - 38 - - - - - - - 81.000000000000000 - - - 搜索软件... - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 214 - 40 - - - - - 214 - 40 - - - - - - 0 - 0 - 102 - 38 - - - - - 102 - 38 - - - - - 102 - 38 - - - - - - - Qt::LeftToRight - - - QComboBox::AdjustToContentsOnFirstShow - - - - 按名称 - - - - - - - 118 - 0 - 96 - 40 - - - - 更新全部 - - - - - - - - - - - true - - - - 0 - 0 - - - - - - - - - - - - - 0 - 0 - 1440 - 23 - - - - - - - diff --git a/src/spark-update-tool/README.md b/src/spark-update-tool/README.md index ae2026a..e572880 100644 --- a/src/spark-update-tool/README.md +++ b/src/spark-update-tool/README.md @@ -1,29 +1,21 @@ -### 星火软件更新器 +### Spark Software Updater -#### 简介 +#### Introduction -欢迎使用星火软件更新器!本工具可帮助您便捷地更新 Linux 计算机上的各类程序。 +Welcome to Spark Software Updater! This tool helps you conveniently update various applications on your Linux system. -本版本专为仅包含 Qt5 的 Linux 发行版设计。 -**请在 root 权限下运行本程序。** +This version is specifically designed for Linux distributions with Qt5 support. +**Please run with root privileges (recommended to use `sudo`).** -#### 当前支持的 Linux 发行版 +#### Currently Supported Linux Distributions - [x] GXDE OS - [x] Ubuntu - [x] deepin - [ ] Kylin -#### 功能清单 +// ... existing Chinese feature table ... -| 功能模块 | 描述 | -|------------------|--------------------------------------| -| 应用名识别 | 基于 `ss-do-upgrade.sh` 部分代码实现 | -| 应用包大小识别 | 通过 dpkg 获取包大小信息 | -| 获取应用图标 | 利用 QDesktopServices 实现 | -| 支持 ACE 兼容环境| | -| 多线程下载 | 基于 aptss 方案 | +#### Contact & Feedback -#### 联系与反馈 - -如有问题或建议,欢迎联系:momen@momen.world \ No newline at end of file +For any issues or suggestions, please contact: momen@momen.world diff --git a/spark-update-tool/README.md b/src/spark-update-tool/README.zh.md similarity index 88% rename from spark-update-tool/README.md rename to src/spark-update-tool/README.zh.md index ae2026a..3e30de3 100644 --- a/spark-update-tool/README.md +++ b/src/spark-update-tool/README.zh.md @@ -24,6 +24,7 @@ | 支持 ACE 兼容环境| | | 多线程下载 | 基于 aptss 方案 | +如您已安装星火应用商店,则会附带本程序。 #### 联系与反馈 如有问题或建议,欢迎联系:momen@momen.world \ No newline at end of file diff --git a/src/spark-update-tool/src/appdelegate.cpp b/src/spark-update-tool/src/appdelegate.cpp index 1a1c3e9..4efbc46 100644 --- a/src/spark-update-tool/src/appdelegate.cpp +++ b/src/spark-update-tool/src/appdelegate.cpp @@ -12,14 +12,14 @@ AppDelegate::AppDelegate(QObject *parent) : QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)), m_installProcess(nullptr) { connect(m_downloadManager, &DownloadManager::downloadFinished, this, [this](const QString &packageName, bool success) { - if (m_downloads.contains(packageName)) { - m_downloads[packageName].isDownloading = false; - emit updateDisplay(packageName); - qDebug() << (success ? "下载完成:" : "下载失败:") << packageName; - if (success) { - enqueueInstall(packageName); - } - } + if (m_downloads.contains(packageName)) { + m_downloads[packageName].isDownloading = false; + emit updateDisplay(packageName); + qDebug() << (success ? "下载完成:" : "下载失败:") << packageName; + if (success) { + enqueueInstall(packageName); + } + } }); connect(m_downloadManager, &DownloadManager::downloadProgress, this, @@ -276,18 +276,19 @@ void AppDelegate::startNextInstall() { qDebug().noquote() << QString::fromLocal8Bit(err); }); connect(m_installProcess, QOverload::of(&QProcess::finished), - this, [this, packageName, logFile](int /*exitCode*/, QProcess::ExitStatus /*status*/) { - if (logFile) logFile->close(); - // 若未检测到“软件包已安装”,此处兜底 - if (!m_downloads[packageName].isInstalled) { - m_downloads[packageName].isInstalling = false; - } - emit updateDisplay(packageName); - m_installProcess->deleteLater(); - m_installProcess = nullptr; - m_installingPackage.clear(); - startNextInstall(); - }); + this, [this, packageName, logFile](int exitCode, QProcess::ExitStatus status) { + if (logFile) logFile->close(); + m_downloads[packageName].isInstalling = false; + if (exitCode == 0) { + m_downloads[packageName].isInstalled = true; // 安装成功 + } + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + }); + } else { // 日志文件无法打开时,仍然要连接原有信号 connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName]() { From 3dc2c3ab42186cceb4c305b4df9115d66a5b158b Mon Sep 17 00:00:00 2001 From: shenmo Date: Fri, 5 Sep 2025 13:26:30 +0000 Subject: [PATCH 126/126] Release 4.8.1 Signed-off-by: shenmo --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 7100157..9e1bb8d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -spark-store (4.8.1~test2) UNRELEASED; urgency=medium +spark-store (4.8.1) UNRELEASED; urgency=medium * 修复更新器安装软件完成后仍然显示下载完成的问题