diff --git a/pkg/etc/apt/preferences.d/sparkstore b/pkg/etc/apt/preferences.d/sparkstore new file mode 100644 index 00000000..8c9cac3f --- /dev/null +++ b/pkg/etc/apt/preferences.d/sparkstore @@ -0,0 +1,3 @@ +Package: * +Pin: origin *.deepinos.org.cn +Pin-Priority: 400 diff --git a/pkg/etc/apt/sources.list.d/sparkstore.list b/pkg/etc/apt/sources.list.d/sparkstore.list new file mode 100644 index 00000000..05637729 --- /dev/null +++ b/pkg/etc/apt/sources.list.d/sparkstore.list @@ -0,0 +1 @@ +deb [by-hash=force] https://d.store.deepinos.org.cn / diff --git a/pkg/lib/systemd/system/spark-update-notifier.timer b/pkg/lib/systemd/system/spark-update-notifier.timer new file mode 100644 index 00000000..b8a48186 --- /dev/null +++ b/pkg/lib/systemd/system/spark-update-notifier.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Timer for Spark Update Notifier + +[Timer] +# 开机后第一次执行 +OnBootSec=1min +# 每天执行一次 +OnUnitActiveSec=1d + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/pkg/tmp/spark-store-install/feedback.sh b/pkg/tmp/spark-store-install/feedback.sh new file mode 100755 index 00000000..007f9761 --- /dev/null +++ b/pkg/tmp/spark-store-install/feedback.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +echo "From:sparkstorefeedback@163.com +To:sparkstorefeedback@163.com +Subject: spark-store_3.0.2: $(lsb_release -a | grep "Description" | sed -e "s#\t#@#" | cut -d "@" -f 2) + +$(uname -a)" | tee /tmp/spark-store-install/feedback.txt > /dev/null + +curl -s --url "smtp://smtp.163.com" --mail-from "${MAIL_FEEDBACK}" --mail-rcpt "${MAIL_FEEDBACK}" --upload-file /tmp/spark-store-install/feedback.txt --user "${MAIL_FEEDBACK}:${M}AIL_AUTH" diff --git a/pkg/usr/lib/systemd/system/spark-update-notifier.service b/pkg/usr/lib/systemd/system/spark-update-notifier.service new file mode 100644 index 00000000..62263a13 --- /dev/null +++ b/pkg/usr/lib/systemd/system/spark-update-notifier.service @@ -0,0 +1,15 @@ +[Unit] +Description=Spark Store update notifier +After=apt-daily.service network.target network-online.target systemd-networkd.service NetworkManager.service connman.service + +[Service] +Type=simple +RemainAfterExit=yes +ExecStart=/opt/durapps/spark-store/bin/update-upgrade/ss-update-notifier.sh +Restart=on-failure +RestartSec=15 # 可以设置为更长的重试间隔,比如 15 秒或 30 秒 +StartLimitIntervalSec=1h # 设置为 1 小时的时间窗口 +StartLimitBurst=3 # 最大允许失败次数为 3 次 + +[Install] +WantedBy=multi-user.target diff --git a/pkg/usr/share/applications/open-me-in-terminal.desktop b/pkg/usr/share/applications/open-me-in-terminal.desktop new file mode 100644 index 00000000..8502cbe5 --- /dev/null +++ b/pkg/usr/share/applications/open-me-in-terminal.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Categories=Development; +Encoding=UTF-8 +Exec=/opt/durapps/spark-store/bin/open-in-terminal/open-in-terminal %U +Icon=open-me-in-terminal +MimeType=application/x-desktop +Name=Open me in terminal +Name[zh_CN]=在终端中打开 +NoDisplay=true +StartupWMClass=在终端中打开 +Terminal=true +Type=Application diff --git a/pkg/usr/share/applications/spark-store.desktop b/pkg/usr/share/applications/spark-store.desktop new file mode 100644 index 00000000..d2090f55 --- /dev/null +++ b/pkg/usr/share/applications/spark-store.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Categories=System; +Exec=spark-store %u +Icon=spark-store +Name=Spark Store +Name[zh_CN]=星火应用商店 +Keywords=appstore; +Terminal=false +StartupNotify=true +StartupWMClass=spark-store +MimeType=x-scheme-handler/spk diff --git a/pkg/usr/share/aptss/transhell/aptss_en_US.transhell b/pkg/usr/share/aptss/transhell/aptss_en_US.transhell new file mode 100644 index 00000000..7d3cb08c --- /dev/null +++ b/pkg/usr/share/aptss/transhell/aptss_en_US.transhell @@ -0,0 +1,5 @@ +#!/bin/bash +TRANSHELL_CONTENT_RUNNING_IN_NOT_ROOT_USER="INFO: Running in non-Root mode! If error occurs, please try running the command with Root privileges." +TRANSHELL_CONTENT_INFO_SOURCES_LIST_D_IS_EMPTY="INFO: The sources.list.d directory is empty. Synchronization will not be attempted." +TRANSHELL_CONTENT_GETTING_SERVER_CONFIG_AND_MIRROR_LIST="Fetching configuration and mirror list from the server..." +TRANSHELL_CONTENT_PLEASE_USE_APTSS_INSTEAD_OF_APT="NOTE: Although the error message may suggest using apt (e.g. apt install --fix-broken) to fix the issue, please use aptss instead when troubleshooting (for example, use: aptss install --fix-broken)." \ No newline at end of file diff --git a/pkg/usr/share/aptss/transhell/aptss_zh_CN.transhell b/pkg/usr/share/aptss/transhell/aptss_zh_CN.transhell new file mode 100644 index 00000000..51ec95fb --- /dev/null +++ b/pkg/usr/share/aptss/transhell/aptss_zh_CN.transhell @@ -0,0 +1,5 @@ +#!/bin/bash +TRANSHELL_CONTENT_RUNNING_IN_NOT_ROOT_USER="信息:正在使用非 Root 权限模式启动!若出现问题,请尝试使用 Root 权限执行命令。" +TRANSHELL_CONTENT_INFO_SOURCES_LIST_D_IS_EMPTY="信息:sources.list.d 文件夹是空的,将不会尝试同步。" +TRANSHELL_CONTENT_GETTING_SERVER_CONFIG_AND_MIRROR_LIST="正在从服务器获取配置和镜像列表……" +TRANSHELL_CONTENT_PLEASE_USE_APTSS_INSTEAD_OF_APT="注意:尽管报错信息提示使用 apt(如 apt install --fix-broken)修复问题,但请在排查错误时使用 aptss 进行替代(对于例子,请改为使用 aptss install --fix-broken)。" \ No newline at end of file diff --git a/pkg/usr/share/bash-completion/completions/aptss b/pkg/usr/share/bash-completion/completions/aptss new file mode 100644 index 00000000..91e12ef6 --- /dev/null +++ b/pkg/usr/share/bash-completion/completions/aptss @@ -0,0 +1,228 @@ +# Debian apt(8) completion -*- shell-script -*- + +_aptss() +{ + local sourcesdir="/etc/apt/sources.list.d" + local cur prev words cword + _init_completion || return + + local GENERIC_APT_GET_OPTIONS=' + -d --download-only + -y --assume-yes + --assume-no + -u --show-upgraded + -m --ignore-missing + -t --target-release + --download + --fix-missing + --ignore-hold + --upgrade + --only-upgrade + --allow-change-held-packages + --allow-remove-essential + --allow-downgrades + --print-uris + --trivial-only + --remove + --arch-only + --allow-unauthenticated + --allow-insecure-repositories + --install-recommends + --install-suggests + --no-install-recommends + --no-install-suggests + --fix-policy + ' + + # see if the user selected a command already + local COMMANDS=( + "ssupdate" + "list" + "search" + "show" "showsrc" + "install" "remove" "purge" "autoremove" "autopurge" + "update" + "upgrade" "full-upgrade" "dist-upgrade" + "edit-sources" + "help" + "source" "build-dep" + "clean" "autoclean" + "download" "changelog" + "moo" + "depends" "rdepends" + "policy") + + local command i + for (( i=0; i < ${#words[@]}-1; i++ )); do + if [[ ${COMMANDS[@]} =~ ${words[i]} ]]; then + command=${words[i]} + break + fi + done + + # Complete a -t + case $prev in + -t|--target-release) + COMPREPLY=( $( compgen -W "$( apt-cache policy -o Dir::Cache="/var/lib/aptss/" | egrep -o 'a=[^,]*|n=[^,]*' | cut -f2- -d= | sort -u)" -- "$cur" ) ) + return 0 + ;; + esac + + # supported options per command + if [[ "$cur" == -* ]]; then + case $command in + install|remove|purge|upgrade|dist-upgrade|full-upgrade|autoremove) + COMPREPLY=( $( compgen -W '--show-progress + --fix-broken --purge --verbose-versions --auto-remove + -s --simulate --dry-run + --download + --fix-missing + --fix-policy + --ignore-hold + --force-yes + --trivial-only + --reinstall --solver + -t --target-release'"$GENERIC_APT_GET_OPTIONS" -- "$cur" ) ) + return 0 + ;; + update) + COMPREPLY=( $( compgen -W '--list-cleanup + --print-uris + --allow-insecure-repositories + ' -- "$cur" ) ) + return 0 + ;; + list) + COMPREPLY=( $( compgen -W '--installed --upgradable + --manual-installed + -v --verbose + -a --all-versions + -t --target-release + ' -- "$cur" ) ) + return 0 + ;; + show) + COMPREPLY=( $( compgen -W '-a --all-versions + ' -- "$cur" ) ) + return 0 + ;; + depends|rdepends) + COMPREPLY=( $( compgen -W '-i + --important + --installed + --pre-depends + --depends + --recommends + --suggests + --replaces + --breaks + --conflicts + --enhances + --recurse + --implicit' -- "$cur" ) ) + return 0 + ;; + search) + COMPREPLY=( $( compgen -W ' + -n --names-only + -f --full' -- "$cur" ) ) + return 0 + ;; + showsrc) + COMPREPLY=( $( compgen -W ' + --only-source' -- "$cur" ) ) + return 0 + ;; + source) + COMPREPLY=( $( compgen -W ' + -s --simulate --dry-run + -b --compile --build + -P --build-profiles + --diff-only --debian-only + --tar-only + --dsc-only + -t --target-release + '"$GENERIC_APT_GET_OPTIONS" -- "$cur" ) ) + return 0 + ;; + build-dep) + COMPREPLY=( $( compgen -W ' + -a --host-architecture + -s --simulate --dry-run + -P --build-profiles + -t --target-release + --purge --solver + '"$GENERIC_APT_GET_OPTIONS" -- "$cur" ) ) + return 0 + ;; + moo) + COMPREPLY=( $( compgen -W ' + --color + ' -- "$cur" ) ) + return 0 + ;; + clean|autoclean) + COMPREPLY=( $( compgen -W ' + -s --simulate --dry-run + ' -- "$cur" ) ) + return 0 + ;; + esac + fi + + # specific command arguments + if [[ -n $command ]]; then + case $command in + remove|purge|autoremove) + if [[ -f /etc/debian_version ]]; then + # Debian system + COMPREPLY=( $( \ + _xfunc dpkg _comp_dpkg_installed_packages $cur ) ) + else + # assume RPM based + _xfunc rpm _rpm_installed_packages + fi + return 0 + ;; + show|list|download|changelog|depends|rdepends) + COMPREPLY=( $( apt-cache --no-generate pkgnames "$cur" -o Dir::Cache="/var/lib/aptss/" \ + 2> /dev/null ) ) + return 0 + ;; + install) + COMPREPLY=( $( apt-cache --no-generate pkgnames "$cur" -o Dir::Cache="/var/lib/aptss/" \ + 2> /dev/null ) ) + if [[ "$cur" == ./* || "$cur" == /* ]]; then + _filedir "deb" + fi + return 0 + ;; + source|build-dep|showsrc|policy) + COMPREPLY=( $( apt-cache --no-generate pkgnames "$cur" -o Dir::Cache="/var/lib/aptss/" \ + 2> /dev/null ) $( apt-cache dumpavail -o Dir::Cache="/var/lib/aptss/" | \ + command grep "^Source: $cur" | sort -u | cut -f2 -d" " ) ) + return 0 + ;; + edit-sources) + COMPREPLY=( $( compgen -W '$( command ls $sourcesdir )' \ + -- "$cur" ) ) + return 0 + ;; + moo) + COMPREPLY=( $( compgen -W 'moo' \ + -- "$cur" ) ) + return 0 + ;; + esac + fi + + # no command yet, show what commands we have + if [ "$command" = "" ]; then + COMPREPLY=( $( compgen -W '${COMMANDS[@]}' -- "$cur" ) ) + fi + + return 0 +} && +complete -F _aptss aptss + +# ex: ts=4 sw=4 et filetype=sh diff --git a/pkg/usr/share/dsg/org.deepin.dtkwidget.feature-display.json b/pkg/usr/share/dsg/org.deepin.dtkwidget.feature-display.json new file mode 100644 index 00000000..8d1bbeef --- /dev/null +++ b/pkg/usr/share/dsg/org.deepin.dtkwidget.feature-display.json @@ -0,0 +1,26 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "featureUpdated": { + "value": false, + "serial": 0, + "flags": [], + "name": "Whether the application has new feature updates", + "name[zh_CN]": "配置应用的更新状态", + "description": "Configure the update status of the application", + "permissions": "readwrite", + "visibility": "public" + }, + "autoDisplayFeature": { + "value": false, + "serial": 0, + "flags": [], + "name": "The application automatically display new features once", + "name[zh_CN]": "配置应用是否自动展示一次新特性", + "description": "The application automatically display updated contents once", + "permissions": "readwrite", + "visibility": "public" + } + } +} \ No newline at end of file diff --git a/pkg/usr/share/fish/completions/aptss.fish b/pkg/usr/share/fish/completions/aptss.fish new file mode 100644 index 00000000..0bb08fa5 --- /dev/null +++ b/pkg/usr/share/fish/completions/aptss.fish @@ -0,0 +1,242 @@ +# 清除已有的 aptss 补全(如果有的话) +complete -c aptss -e + +# 禁用默认的文件补全(避免显示当前目录文件) +complete -c aptss -f + +######################################################################## +# aptss Fish 补全脚本(中文说明版,软件包补全显示简介) +# +# 说明: +# 1. 子命令和选项的说明采用中文显示。 +# 2. 软件包补全部分不再调用 apt-cache,而是解析 aptss 自有的软件源索引文件, +# 从 /var/lib/aptss/lists/*Packages(或 *Sources)中提取软件包名称及简介信息。 +# +# 注意:如果你的 aptss 软件源索引文件位置或格式有变化,请相应修改下面的 awk 命令。 +######################################################################## + +### 辅助函数 + +# 解析 /var/lib/aptss/lists/*Packages 文件,输出符合当前输入前缀的“软件包简介” +function __fish_aptss_print_packages + set cur (commandline -ct) + # 将所有匹配的 Packages 文件拼接后,用 awk 分段解析(RS="" 表示以空行为分段) + awk -v cur="$cur" ' + BEGIN { RS=""; FS="\n" } + { + pkg = ""; desc = ""; + for(i=1; i<=NF; i++){ + if($i ~ /^Package: /) { pkg = substr($i, 10) } # “Package: ”共9个字符 + else if($i ~ /^Description: /) { desc = substr($i, 14) } # “Description: ”共13个字符 + } + if(pkg != "" && (cur == "" || pkg ~ ("^" cur))) { + print pkg "\t" desc + } + } + ' /var/lib/aptss/lists/*Packages 2>/dev/null +end + +# 解析已安装软件包(这里仍使用 dpkg-query,如果需要使用 aptss 数据,可另外构造) +function __fish_aptss_print_installed_packages + set cur (commandline -ct) + dpkg-query -W -f='${Package}\t${Description}\n' 2>/dev/null | grep -i "^$cur" +end + +# 解析 /var/lib/aptss/lists/*Sources 文件,输出源代码包信息(如果存在) +function __fish_aptss_print_source_packages + set cur (commandline -ct) + awk -v cur="$cur" ' + BEGIN { RS=""; FS="\n" } + { + pkg = ""; desc = ""; + for(i=1; i<=NF; i++){ + if($i ~ /^Package: /) { pkg = substr($i, 10) } + else if($i ~ /^Description: /) { desc = substr($i, 14) } + } + if(pkg != "" && (cur == "" || pkg ~ ("^" cur))) { + print pkg "\t" desc + } + } + ' /var/lib/aptss/lists/*Sources 2>/dev/null +end + +# 翻译子命令为中文说明(用于补全时显示在括号内) +function __fish_translate_aptss_cmd + switch $argv[1] + case ssupdate + echo "更新软件源" + case list + echo "列出软件包" + case search + echo "搜索软件包" + case show + echo "显示软件包信息" + case showsrc + echo "显示源包信息" + case install + echo "安装软件包" + case remove + echo "移除软件包" + case purge + echo "彻底移除软件包" + case autoremove + echo "自动移除不必要的软件包" + case update + echo "更新软件包列表" + case upgrade + echo "升级软件包" + case full-upgrade + echo "完全升级(可能移除其他软件包)" + case dist-upgrade + echo "发行版升级" + case edit-sources + echo "编辑软件源列表" + case help + echo "显示帮助信息" + case source + echo "下载源代码包" + case build-dep + echo "安装构建依赖" + case clean + echo "清除软件包缓存" + case autoclean + echo "自动清理旧缓存" + case download + echo "下载软件包" + case changelog + echo "显示更新日志" + case moo + echo "彩蛋" + case depends + echo "显示软件包依赖" + case rdepends + echo "显示软件包逆向依赖" + case policy + echo "显示软件包策略" + case '*' + echo $argv[1] + end +end + +### 定义各类子命令组 + +# 所有子命令列表 +set -g __aptss_commands ssupdate list search show showsrc install remove purge autoremove update upgrade full-upgrade dist-upgrade edit-sources help source build-dep clean autoclean download changelog moo depends rdepends policy + +# 需要补全二进制软件包名称的子命令(例如 install、show、search、download、changelog、depends、rdepends) +set -l __aptss_pkg_subcmds install show search download changelog depends rdepends + +# 需要补全已安装软件包的子命令(例如 remove、purge、autoremove) +set -l __aptss_installed_pkg_subcmds remove purge autoremove + +# 需要补全源代码包的子命令(例如 source、build-dep、showsrc、policy) +set -l __aptss_src_pkg_subcmds source build-dep showsrc policy + +### 子命令补全 +# 未输入子命令时,显示所有候选子命令,并在括号中显示中文说明 +for cmd in $__aptss_commands + set desc (__fish_translate_aptss_cmd $cmd) + complete -c aptss -a $cmd -d "$desc" -n "not __fish_seen_subcommand_from $__aptss_commands" +end + +### 公共选项(适用于一组子命令) +set -l group1 "install remove purge upgrade dist-upgrade full-upgrade autoremove" + +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l show-progress -d '显示进度' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l fix-broken -d '修复损坏的依赖' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l purge -d '清除配置文件' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l verbose-versions -d '显示详细版本' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l auto-remove -d '自动移除依赖' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -s s -l simulate -d '模拟/试运行' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l download -d '下载软件包' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l fix-missing -d '修复丢失文件' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l fix-policy -d '修复策略' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l ignore-hold -d '忽略锁定' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l force-yes -d '强制确认' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l trivial-only -d '仅处理简单情况' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l reinstall -d '重新安装' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l solver -d '使用求解器' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -s t -l target-release -d '目标版本' + +# 附加的 GENERIC 选项 +complete -c aptss -n "__fish_seen_subcommand_from $group1" -s d -l download-only -d '仅下载' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -s y -l assume-yes -d '默认确认' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -l assume-no -d '默认否定' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -s u -l show-upgraded -d '显示升级情况' +complete -c aptss -n "__fish_seen_subcommand_from $group1" -s m -l ignore-missing -d '忽略缺失' + +### 针对各个子命令的专用选项 + +# update 命令 +complete -c aptss -n "__fish_seen_subcommand_from update" -l list-cleanup -d '清理列表' +complete -c aptss -n "__fish_seen_subcommand_from update" -l print-uris -d '显示 URI' +complete -c aptss -n "__fish_seen_subcommand_from update" -l allow-insecure-repositories -d '允许不安全的仓库' + +# list 命令 +complete -c aptss -n "__fish_seen_subcommand_from list" -l installed -d '已安装的软件包' +complete -c aptss -n "__fish_seen_subcommand_from list" -l upgradable -d '可升级的软件包' +complete -c aptss -n "__fish_seen_subcommand_from list" -l manual-installed -d '手动安装的软件包' +complete -c aptss -n "__fish_seen_subcommand_from list" -s v -l verbose -d '详细模式' +complete -c aptss -n "__fish_seen_subcommand_from list" -s a -l all-versions -d '显示所有版本' +complete -c aptss -n "__fish_seen_subcommand_from list" -s t -l target-release -d '目标版本' + +# show 命令 +complete -c aptss -n "__fish_seen_subcommand_from show" -s a -l all-versions -d '显示所有版本' + +# depends 和 rdepends 命令(逐项添加各选项) +for opt in i important installed pre-depends depends recommends suggests replaces breaks conflicts enhances recurse implicit + complete -c aptss -n "__fish_seen_subcommand_from depends rdepends" -l $opt -d $opt +end +complete -c aptss -n "__fish_seen_subcommand_from depends rdepends" -s i -d '选项 -i' + +# search 命令 +complete -c aptss -n "__fish_seen_subcommand_from search" -s n -l names-only -d '仅匹配名称' +complete -c aptss -n "__fish_seen_subcommand_from search" -s f -l full -d '全文搜索' + +# showsrc 命令 +complete -c aptss -n "__fish_seen_subcommand_from showsrc" -l only-source -d '仅显示源代码' + +# source 命令 +complete -c aptss -n "__fish_seen_subcommand_from source" -s s -l simulate -d '模拟' +complete -c aptss -n "__fish_seen_subcommand_from source" -s b -l compile -d '编译/构建' +complete -c aptss -n "__fish_seen_subcommand_from source" -s P -l build-profiles -d '构建配置' +complete -c aptss -n "__fish_seen_subcommand_from source" -l diff-only -d '仅显示差异' +complete -c aptss -n "__fish_seen_subcommand_from source" -l debian-only -d '仅限 Debian' +complete -c aptss -n "__fish_seen_subcommand_from source" -l tar-only -d '仅打包 tar' +complete -c aptss -n "__fish_seen_subcommand_from source" -l dsc-only -d '仅下载 DSC' +complete -c aptss -n "__fish_seen_subcommand_from source" -s t -l target-release -d '目标版本' + +# build-dep 命令 +complete -c aptss -n "__fish_seen_subcommand_from build-dep" -s a -l host-architecture -d '主机架构' +complete -c aptss -n "__fish_seen_subcommand_from build-dep" -s s -l simulate -d '模拟' +complete -c aptss -n "__fish_seen_subcommand_from build-dep" -s P -l build-profiles -d '构建配置' +complete -c aptss -n "__fish_seen_subcommand_from build-dep" -s t -l target-release -d '目标版本' +complete -c aptss -n "__fish_seen_subcommand_from build-dep" -l purge -d '清除' +complete -c aptss -n "__fish_seen_subcommand_from build-dep" -l solver -d '求解依赖' + +# moo 命令 +complete -c aptss -n "__fish_seen_subcommand_from moo" -l color -d '彩蛋模式' + +# clean 和 autoclean 命令 +complete -c aptss -n "__fish_seen_subcommand_from clean autoclean" -s s -l simulate -d '模拟' + +### 针对 -t/--target-release 的特殊补全 +complete -c aptss -n ' + begin + set -l prev (commandline -poc | string trim) + test "$prev" = "-t" -o "$prev" = "--target-release" + end +' -a '(__fish_aptss_target_release)' -d '目标版本' + +### 软件包补全 +# 对于需要二进制软件包名称的子命令,调用 __fish_aptss_print_packages, +# 输出的每一行格式为 "包名简介",Fish 会将 TAB 后内容显示为注释。 +complete -c aptss -n "__fish_seen_subcommand_from $__aptss_pkg_subcmds" -a '(__fish_aptss_print_packages)' + +# 对于 remove、purge、autoremove 命令,补全已安装的软件包(使用 dpkg-query 输出) +complete -c aptss -n "__fish_seen_subcommand_from $__aptss_installed_pkg_subcmds" -a '(__fish_aptss_print_installed_packages)' -d '已安装软件包' + +# 对于 source、build-dep、showsrc、policy 命令,补全源代码包, +# 如果存在对应的 Sources 索引文件,则调用 __fish_aptss_print_source_packages, +# 否则可考虑默认使用二进制包的索引。 +complete -c aptss -n "__fish_seen_subcommand_from $__aptss_src_pkg_subcmds" -a '(__fish_aptss_print_source_packages)' -d '源代码包' 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 new file mode 100644 index 00000000..bdcabb7f Binary files /dev/null and b/pkg/usr/share/icons/hicolor/scalable/apps/open-me-in-terminal.png differ diff --git a/pkg/usr/share/icons/hicolor/scalable/apps/spark-store.svg b/pkg/usr/share/icons/hicolor/scalable/apps/spark-store.svg new file mode 100644 index 00000000..a1f93964 --- /dev/null +++ b/pkg/usr/share/icons/hicolor/scalable/apps/spark-store.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkg/usr/share/polkit-1/actions/store.spark-app.ssinstall.policy b/pkg/usr/share/polkit-1/actions/store.spark-app.ssinstall.policy new file mode 100644 index 00000000..8b2111b7 --- /dev/null +++ b/pkg/usr/share/polkit-1/actions/store.spark-app.ssinstall.policy @@ -0,0 +1,18 @@ + + + + Spark Store + x-package-repository + + 运行ssinstall需要权限 + 要使用ssinstall需要权限 + + yes + yes + yes + + /usr/local/bin/ssinstall + true + + 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 00000000..012a66ab --- /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 + + + diff --git a/pkg/usr/share/ssinstall-local/transhell/ssinstall-local_en_US.transhell b/pkg/usr/share/ssinstall-local/transhell/ssinstall-local_en_US.transhell new file mode 100644 index 00000000..76f6cccf --- /dev/null +++ b/pkg/usr/share/ssinstall-local/transhell/ssinstall-local_en_US.transhell @@ -0,0 +1,6 @@ +#!/bin/bash +TRANSHELL_CONTENT_HASH_CHECK_FAILED="An unexpected error caused the package verification to fail, and the installation process has been terminated.\n\nPossible causes include:\nNetwork or storage issues resulting in a corrupted package; The Spark Store repository has not yet completed synchronization; In extreme cases, malware may be attempting to tamper with the installation package to compromise the system.\n\nFor normal users: please run the following command in the terminal and then try installing again:\nsudo aptss update\n\nIf the problem persists, click 'App Feedback' button in the application information page to submit the issue.\n\nFor auditors: please use ssaudit instead of ssinstall for auditing, as ssinstall is now reserved for password-free installation.\nThe ssaudit command can also be used to attempt installation of applications that have been removed from the Spark Store." +TRANSHELL_CONTENT_PLEASE_RUN_AS_ROOT="Please run the ssinstall command with Root privileges." +TRANSHELL_CONTENT_FILE_NOT_EXIST="The specified file does not exist." +TRANSHELL_CONTENT_WILL_NOT_DELETE_DEB="No --delete-after-install option specified or installation failed. The Deb package will not be deleted." +TRANSHELL_CONTENT_DEB_IS_DELETED="The --delete-after-install option was used and the installation succeeded. The Deb package has been deleted." \ No newline at end of file diff --git a/pkg/usr/share/ssinstall-local/transhell/ssinstall-local_zh_CN.transhell b/pkg/usr/share/ssinstall-local/transhell/ssinstall-local_zh_CN.transhell new file mode 100644 index 00000000..940396ea --- /dev/null +++ b/pkg/usr/share/ssinstall-local/transhell/ssinstall-local_zh_CN.transhell @@ -0,0 +1,6 @@ +#!/bin/bash +TRANSHELL_CONTENT_HASH_CHECK_FAILED="意外错误导致软件包校验失败,安装进程已终止。\n\n可能导致此错误的原因:\n网络或存储问题导致软件包损坏;星火应用商店软件仓库未完成同步;极端情况下,恶意软件尝试篡改安装包进行入侵。\n\n对于普通用户,请在终端执行 sudo aptss update 后再次尝试安装;如仍然遇到此错误,请在应用信息界面点击“应用反馈”提交问题。\n对于审核人员,请使用 ssaudit 替代 ssinstall 执行,现在 ssinstall 已被用于免密安装;此替代命令也可用于尝试安装已于星火应用商店下架的应用。" +TRANSHELL_CONTENT_PLEASE_RUN_AS_ROOT="请使用 Root 权限运行 ssinstall 命令。" +TRANSHELL_CONTENT_FILE_NOT_EXIST="指定的文件不存在。" +TRANSHELL_CONTENT_WILL_NOT_DELETE_DEB="未指定安装后删除软件包或安装出错,不删除 Deb 包。" +TRANSHELL_CONTENT_DEB_IS_DELETED="使用了 --delete-after-install 选项且安装未出错,删除 Deb 包。" \ No newline at end of file diff --git a/pkg/usr/share/ssinstall/transhell/ssinstall_en_US.transhell b/pkg/usr/share/ssinstall/transhell/ssinstall_en_US.transhell new file mode 100644 index 00000000..76f6cccf --- /dev/null +++ b/pkg/usr/share/ssinstall/transhell/ssinstall_en_US.transhell @@ -0,0 +1,6 @@ +#!/bin/bash +TRANSHELL_CONTENT_HASH_CHECK_FAILED="An unexpected error caused the package verification to fail, and the installation process has been terminated.\n\nPossible causes include:\nNetwork or storage issues resulting in a corrupted package; The Spark Store repository has not yet completed synchronization; In extreme cases, malware may be attempting to tamper with the installation package to compromise the system.\n\nFor normal users: please run the following command in the terminal and then try installing again:\nsudo aptss update\n\nIf the problem persists, click 'App Feedback' button in the application information page to submit the issue.\n\nFor auditors: please use ssaudit instead of ssinstall for auditing, as ssinstall is now reserved for password-free installation.\nThe ssaudit command can also be used to attempt installation of applications that have been removed from the Spark Store." +TRANSHELL_CONTENT_PLEASE_RUN_AS_ROOT="Please run the ssinstall command with Root privileges." +TRANSHELL_CONTENT_FILE_NOT_EXIST="The specified file does not exist." +TRANSHELL_CONTENT_WILL_NOT_DELETE_DEB="No --delete-after-install option specified or installation failed. The Deb package will not be deleted." +TRANSHELL_CONTENT_DEB_IS_DELETED="The --delete-after-install option was used and the installation succeeded. The Deb package has been deleted." \ No newline at end of file diff --git a/pkg/usr/share/ssinstall/transhell/ssinstall_zh_CN.transhell b/pkg/usr/share/ssinstall/transhell/ssinstall_zh_CN.transhell new file mode 100644 index 00000000..940396ea --- /dev/null +++ b/pkg/usr/share/ssinstall/transhell/ssinstall_zh_CN.transhell @@ -0,0 +1,6 @@ +#!/bin/bash +TRANSHELL_CONTENT_HASH_CHECK_FAILED="意外错误导致软件包校验失败,安装进程已终止。\n\n可能导致此错误的原因:\n网络或存储问题导致软件包损坏;星火应用商店软件仓库未完成同步;极端情况下,恶意软件尝试篡改安装包进行入侵。\n\n对于普通用户,请在终端执行 sudo aptss update 后再次尝试安装;如仍然遇到此错误,请在应用信息界面点击“应用反馈”提交问题。\n对于审核人员,请使用 ssaudit 替代 ssinstall 执行,现在 ssinstall 已被用于免密安装;此替代命令也可用于尝试安装已于星火应用商店下架的应用。" +TRANSHELL_CONTENT_PLEASE_RUN_AS_ROOT="请使用 Root 权限运行 ssinstall 命令。" +TRANSHELL_CONTENT_FILE_NOT_EXIST="指定的文件不存在。" +TRANSHELL_CONTENT_WILL_NOT_DELETE_DEB="未指定安装后删除软件包或安装出错,不删除 Deb 包。" +TRANSHELL_CONTENT_DEB_IS_DELETED="使用了 --delete-after-install 选项且安装未出错,删除 Deb 包。" \ No newline at end of file diff --git a/spark-update-tool/.gitignore b/spark-update-tool/.gitignore new file mode 100644 index 00000000..79c9ac74 --- /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 00000000..498ea212 --- /dev/null +++ b/spark-update-tool/CMakeLists.txt @@ -0,0 +1,99 @@ +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 Network Concurrent) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Concurrent) + +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 + src/ignoreconfig.h + src/ignoreconfig.cpp +) + +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${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) +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 +) + +# 安装配置 +# 对于Linux,安装到/usr/bin目录 +if(UNIX AND NOT APPLE) + # 直接指定安装路径为 /usr/bin + install(TARGETS spark-update-tool + RUNTIME DESTINATION /usr/bin + ) + + # 可选:安装桌面文件和应用图标 + # install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/spark-update-tool.desktop + # DESTINATION /usr/share/applications + # ) + # install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/icons/spark-update-tool.png + # DESTINATION /usr/share/icons/hicolor/256x256/apps + # ) +else() + # 对于其他系统,使用GNU标准安装路径 + include(GNUInstallDirs) + install(TARGETS spark-update-tool + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) +endif() + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(spark-update-tool) +endif() \ No newline at end of file diff --git a/spark-update-tool/LICENSE b/spark-update-tool/LICENSE new file mode 100644 index 00000000..3877ae0a --- /dev/null +++ b/spark-update-tool/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 +. diff --git a/spark-update-tool/README.md b/spark-update-tool/README.md new file mode 100644 index 00000000..778029c2 --- /dev/null +++ b/spark-update-tool/README.md @@ -0,0 +1,15 @@ +### Spark Update Tool +#### Introduction + +Welcome to Spark Software Updater. Use this tool to update applications on your Linux system. +This version is specifically designed for Linux distributions with Qt6 support. +Please run under root privileges (recommended: use `sudo`). +#### Currently Supported Linux Distributions +- [x] GXDE OS +- [x] Ubuntu +- [x] deepin +- [ ] Kylin + + +#### Contact & Feedback +momen@momen.world diff --git a/spark-update-tool/README.zh.md b/spark-update-tool/README.zh.md new file mode 100644 index 00000000..c597ce44 --- /dev/null +++ b/spark-update-tool/README.zh.md @@ -0,0 +1,25 @@ +### 星火软件更新器 +#### 简介 + +欢迎使用星火软件更新器,您可以使用此更新器更新位于您 Linux 计算机的程序。 +此版本专为有qt6的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 00000000..c3eda0fb --- /dev/null +++ b/spark-update-tool/debian/changelog @@ -0,0 +1,27 @@ +spark-update-tool (1.0.4) unstable; urgency=low + + * 修复点击更新全部按钮后,会更新被忽略应用的问题。 + +spark-update-tool (1.0.3) unstable; urgency=low + + * 修复默认图标加载失败的问题 + * 修复更新器在安装阶段强制关闭窗口后再次更新无法安装软件包的问题。 + + -- momen Fri, 17 Oct 2025 00:00:00 +0000 +spark-update-tool (1.0.2) unstable; urgency=low + + * 添加复选框,选择多个包更新 + * 修复缩放问题 + * 添加忽略应用功能 + + -- momen Mon, 29 Sep 2025 00:00:00 +0000 +spark-update-tool (1.0.1) unstable; urgency=low + + * 修复窗口调整大小时的错误 + + -- momen Wed, 18 Jun 2025 00:00:00 +0000 +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 00000000..ca7bf83a --- /dev/null +++ b/spark-update-tool/debian/compat @@ -0,0 +1 @@ +13 \ 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 00000000..19c9d624 --- /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 00000000..163489ed --- /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 00000000..ed44f2cf --- /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 00000000..263e9c42 --- /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 00000000..d9cedc76 --- /dev/null +++ b/spark-update-tool/debian/rules @@ -0,0 +1,27 @@ +#!/usr/bin/make -f + +# 声明兼容性级别 +export DH_VERBOSE=1 + +%: + dh $@ --buildsystem=cmake + +# 确保使用CMake进行配置 +override_dh_auto_configure: + dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/usr + +# 确保使用CMake进行构建 +override_dh_auto_build: + dh_auto_build + +# 确保使用CMake进行安装 +override_dh_auto_install: + dh_auto_install + +# 确保使用CMake进行清理 +override_dh_auto_clean: + dh_auto_clean + +# 确保使用CMake进行依赖解析 +override_dh_shlibdeps: + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info \ No newline at end of file diff --git a/spark-update-tool/debian/source/format b/spark-update-tool/debian/source/format new file mode 100644 index 00000000..9f674278 --- /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 00000000..688ba0cd --- /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 00000000..138ed837 Binary files /dev/null and b/spark-update-tool/resources/128*128/spark-update-tool.png differ diff --git a/spark-update-tool/resources/default_icon.png b/spark-update-tool/resources/default_icon.png new file mode 100644 index 00000000..bffa3792 Binary files /dev/null and b/spark-update-tool/resources/default_icon.png differ diff --git a/spark-update-tool/resources/default_icon.svg b/spark-update-tool/resources/default_icon.svg new file mode 100644 index 00000000..7c9cb42d --- /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 00000000..4989662c --- /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 00000000..3d21d816 --- /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/spark-update-tool b/spark-update-tool/spark-update-tool new file mode 100755 index 00000000..dbfb8803 Binary files /dev/null and b/spark-update-tool/spark-update-tool differ diff --git a/spark-update-tool/spark-update-tool.pro b/spark-update-tool/spark-update-tool.pro new file mode 100644 index 00000000..7493059d --- /dev/null +++ b/spark-update-tool/spark-update-tool.pro @@ -0,0 +1,54 @@ +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 + +# Version info +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 \ + src/ignoreconfig.cpp + +HEADERS += \ + src/mainwindow.h \ + src/aptssupdater.h \ + src/appdelegate.h \ + src/applistmodel.h \ + src/downloadmanager.h \ + src/ignoreconfig.h + +FORMS += \ + src/mainwindow.ui + +RESOURCES += \ + src/icons.qrc + +# Linux-specific settings +unix:!macx { + # 安装到 /usr/bin 目录 + target.path = /usr/bin + INSTALLS += target + + # 如果需要安装其他文件(如桌面文件、图标等),可以添加 + # desktop.path = /usr/share/applications + # desktop.files = spark-update-tool.desktop + # INSTALLS += desktop + + # Additional Linux specific configurations if needed + QMAKE_CXXFLAGS += -Wall -Wextra +} + +# Remove Windows and macOS specific sections since we're focusing on Linux \ 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 00000000..b0a987c6 --- /dev/null +++ b/spark-update-tool/src/appdelegate.cpp @@ -0,0 +1,892 @@ +#include "appdelegate.h" +#include +#include +#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; + // 不要提前设置 isInstalled + emit updateDisplay(packageName); + qDebug() << (success ? "下载完成:" : "下载失败:") << packageName; + if (success) { + enqueueInstall(packageName); // 安装完成后再设置 isInstalled + } else { + // 下载失败,删除已存在的deb包并重新下载 + QDir tempDir(QDir::tempPath()); + QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName) << QString("%1*.deb").arg(packageName), QDir::Files); + for (const QString &deb : debs) { + QString debPath = tempDir.absoluteFilePath(deb); + if (QFile::exists(debPath)) { + if (QFile::remove(debPath)) { + qDebug() << "已删除下载失败的软件包:" << debPath; + } else { + qWarning() << "删除下载失败的软件包失败:" << debPath; + } + } + } + + // 重新开始下载 + if (m_model) { + for (int row = 0; row < m_model->rowCount(); ++row) { + QModelIndex index = m_model->index(row, 0); + if (index.data(Qt::UserRole + 1).toString() == packageName) { + 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); + break; + } + } + } + } + } + }); + + connect(m_downloadManager, &DownloadManager::downloadProgress, this, + [this](const QString &packageName, int progress) { + if (m_downloads.contains(packageName)) { + m_downloads[packageName].progress = progress; + qDebug()<state() != QProcess::NotRunning) { + m_installProcess->kill(); + m_installProcess->waitForFinished(3000); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + } +} + +void AppDelegate::setModel(QAbstractItemModel *model) { + m_model = model; +} + +void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + painter->save(); + + // 检查是否为忽略状态 + bool isIgnored = index.data(Qt::UserRole + 8).toBool(); + + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + else + painter->fillRect(option.rect, QColor("#F3F4F6")); + + // 绘制复选框 + QString packageName = index.data(Qt::UserRole + 1).toString(); + bool isSelected = m_selectedPackages.contains(packageName); + + QRect checkboxRect(option.rect.left() + 10, option.rect.top() + (option.rect.height() - 20) / 2, 20, 20); + + // 绘制复选框边框 + QColor checkboxColor = isIgnored ? QColor("#CCCCCC") : QColor("#888888"); + painter->setPen(checkboxColor); + painter->setBrush(Qt::NoBrush); + painter->drawRect(checkboxRect); + + // 如果选中,绘制勾选标记 + if (isSelected && !isIgnored) { + painter->setPen(QPen(QColor("#2563EB"), 2)); + painter->setBrush(QColor("#2563EB")); + painter->drawRect(checkboxRect.adjusted(4, 4, -4, -4)); + } + + 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 source = index.data(Qt::UserRole + 9).toString(); + + QRect rect = option.rect; + int margin = 10, spacing = 6, iconSize = 40; + + // 调整图标位置,为复选框留出空间 + QRect iconRect(rect.left() + 40, rect.top() + (rect.height() - iconSize) / 2, iconSize, iconSize); + + + // 如果是忽略状态,绘制灰色图标 + if (isIgnored) { + // 创建灰度效果 + QPixmap originalPixmap = QIcon(iconPath).pixmap(iconSize, iconSize); + QPixmap grayPixmap(originalPixmap.size()); + grayPixmap.fill(Qt::transparent); + QPainter grayPainter(&grayPixmap); + grayPainter.setOpacity(0.3); // 设置透明度使其变灰 + grayPainter.drawPixmap(0, 0, originalPixmap); + grayPainter.end(); + painter->drawPixmap(iconRect, grayPixmap); + } else { + 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); + QColor nameColor = isIgnored ? QColor("#999999") : QColor("#333333"); + painter->setPen(nameColor); + + // 计算名称宽度 + QFontMetrics fontMetrics(boldFont); + int nameWidth = fontMetrics.horizontalAdvance(name); + + // 绘制名称 + painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name); + + // 绘制来源Tag + if (!source.isEmpty()) { + int tagX = textX + nameWidth + 10; + QString tagText; + QColor bgColor; + QColor textColor; + + if (source == "apm") { + tagText = "APM"; + bgColor = QColor("#3B82F6"); // 蓝色 + textColor = QColor("#FFFFFF"); + } else { + tagText = "传统deb"; + bgColor = QColor("#F97316"); // 橙色 + textColor = QColor("#FFFFFF"); + } + + int tagWidth = fontMetrics.horizontalAdvance(tagText) + 12; + int tagHeight = 18; + + QRect tagRect(tagX, rect.top() + margin + 1, tagWidth, tagHeight); + + // 绘制Tag背景 + painter->setBrush(bgColor); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(tagRect, 9, 9); + + // 绘制Tag文本 + painter->setFont(normalFont); + painter->setPen(textColor); + painter->drawText(tagRect, Qt::AlignCenter, tagText); + } + + QRect versionRect(textX, nameRect.bottom() + spacing, textWidth, 20); + painter->setFont(normalFont); + QColor versionColor = isIgnored ? QColor("#AAAAAA") : QColor("#888888"); + painter->setPen(versionColor); + painter->drawText(versionRect, Qt::AlignLeft | Qt::AlignVCenter, + QString("当前版本: %1 → 新版本: %2").arg(currentVersion, newVersion)); + + QRect descRect(textX, versionRect.bottom() + spacing, textWidth, 40); + painter->setFont(normalFont); + QColor descColor = isIgnored ? QColor("#CCCCCC") : QColor("#AAAAAA"); + painter->setPen(descColor); + painter->drawText(descRect, Qt::TextWordWrap, + QString("包大小:%1 MB").arg(QString::number(size.toDouble() / (1024 * 1024), 'f', 2))); + + 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 (isIgnored) { + QRect ignoredTextRect(rect.right() - 170, rect.top() + (rect.height() - 30) / 2, 80, 30); + painter->setPen(QColor("#999999")); + painter->setFont(option.font); + painter->drawText(ignoredTextRect, Qt::AlignCenter, "已忽略"); + + // 绘制取消忽略按钮 + QRect unignoreButtonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#F3F4F6")); + painter->drawRoundedRect(unignoreButtonRect, 4, 4); + painter->setPen(QColor("#6B7280")); + painter->drawText(unignoreButtonRect, Qt::AlignCenter, "取消忽略"); + } else if (isDownloading) { + // 绘制灰底蓝色进度条 + QRect progressRect(rect.right() - 270, rect.top() + (rect.height() - 20) / 2, 150, 20); + + // 绘制背景 + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#E5E7EB")); + painter->drawRoundedRect(progressRect, 10, 10); + + // 绘制进度 + int progressWidth = progressRect.width() * progress / 100; + QRect progressFillRect(progressRect.left(), progressRect.top(), progressWidth, progressRect.height()); + painter->setBrush(QColor("#3B82F6")); + painter->drawRoundedRect(progressFillRect, 10, 10); + + // 绘制进度文本 + painter->setPen(Qt::white); + painter->setFont(option.font); + painter->drawText(progressRect, Qt::AlignCenter, QString("%1%").arg(progress)); + + QRect cancelButtonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#ff4444")); + painter->drawRoundedRect(cancelButtonRect, 4, 4); + + painter->setPen(Qt::white); + painter->setFont(option.font); + painter->drawText(cancelButtonRect, Qt::AlignCenter, "取消"); + } else if (isInstalling) { + QRect spinnerRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 30, 30); + + // 修改:使用 m_spinnerAngle + QPen pen(QColor("#2563EB"), 3); + painter->setPen(pen); + painter->setRenderHint(QPainter::Antialiasing, true); + QRectF arcRect = spinnerRect.adjusted(3, 3, -3, -3); + painter->drawArc(arcRect, m_spinnerAngle * 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 ignoreButtonRect(option.rect.right() - 160, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30); + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#F3F4F6")); + painter->drawRoundedRect(ignoreButtonRect, 4, 4); + painter->setPen(QColor("#6B7280")); + painter->drawText(ignoreButtonRect, Qt::AlignCenter, "忽略"); + + // 绘制更新按钮 + QRect updateButtonRect(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(updateButtonRect, 4, 4); + painter->setPen(Qt::white); + painter->drawText(updateButtonRect, Qt::AlignCenter, "已安装"); + } else if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { + painter->setBrush(QColor("#10B981")); + painter->drawRoundedRect(updateButtonRect, 4, 4); + painter->setPen(Qt::white); + painter->drawText(updateButtonRect, Qt::AlignCenter, "下载完成"); + } else { + painter->setBrush(QColor("#e9effd")); + painter->drawRoundedRect(updateButtonRect, 4, 4); + painter->setPen(QColor("#2563EB")); + painter->drawText(updateButtonRect, 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(); + + // 检查是否为忽略状态,如果是则只允许取消忽略按钮的交互 + bool isIgnored = index.data(Qt::UserRole + 8).toBool(); + if (isIgnored) { + QRect unignoreButtonRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30); + if (unignoreButtonRect.contains(mouseEvent->pos())) { + // 发送取消忽略信号 + emit unignoreApp(packageName); + return true; + } + return true; // 消耗其他事件,不允许其他交互 + } + + // 检查是否点击了复选框 + QRect checkboxRect(rect.left() + 10, rect.top() + (rect.height() - 20) / 2, 20, 20); + if (checkboxRect.contains(mouseEvent->pos())) { + if (m_selectedPackages.contains(packageName)) { + m_selectedPackages.remove(packageName); + } else { + m_selectedPackages.insert(packageName); + } + emit updateDisplay(packageName); + return true; + } + + 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); + + // 删除未下载完成的软件包 + QDir tempDir(QDir::tempPath()); + QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName) << QString("%1*.deb").arg(packageName), QDir::Files); + for (const QString &deb : debs) { + QString debPath = tempDir.absoluteFilePath(deb); + if (QFile::exists(debPath)) { + if (QFile::remove(debPath)) { + qDebug() << "已删除未下载完成的软件包:" << debPath; + } else { + qWarning() << "删除未下载完成的软件包失败:" << debPath; + } + } + } + + emit updateDisplay(packageName); + return true; + } + } else { + // 检查是否点击了忽略按钮 + QRect ignoreButtonRect(rect.right() - 160, rect.top() + (rect.height() - 30) / 2, 70, 30); + if (ignoreButtonRect.contains(mouseEvent->pos())) { + QString currentVersion = index.data(Qt::UserRole + 2).toString(); + emit ignoreApp(packageName, currentVersion); + return true; + } + + // 检查是否点击了更新按钮 + QRect updateButtonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30); + if (updateButtonRect.contains(mouseEvent->pos())) { + if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) { + return false; + } + + // 检查/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()); + } + } + + // 触发下载流程(无论是否存在deb包,都尝试续传) + 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); + + // 检查应用是否被忽略 + bool isIgnored = index.data(Qt::UserRole + 8).toBool(); + if (isIgnored) { + qDebug() << "跳过被忽略的应用:" << index.data(Qt::UserRole + 1).toString(); + continue; + } + + 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(); + m_spinnerUpdateTimer.stop(); // 新增:停止定时器 + return; + } + m_isInstalling = true; + QString packageName = m_installQueue.dequeue(); + m_installingPackage = packageName; + m_downloads[packageName].isInstalling = true; + m_spinnerUpdateTimer.start(); // 新增:启动定时器 + emit updateDisplay(packageName); + + 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; + } + + // 获取包的来源信息 + QString source = "aptss"; // 默认来源 + if (m_model) { + for (int row = 0; row < m_model->rowCount(); ++row) { + QModelIndex index = m_model->index(row, 0); + if (index.data(Qt::UserRole + 1).toString() == packageName) { + source = index.data(Qt::UserRole + 9).toString(); + break; + } + } + } + + // 如果是APM包,先检查APM中是否存在对应的包,再卸载APTSS版本 + if (source == "apm") { + // 检查APM中是否存在对应的包 + QProcess checkProcess; + QStringList checkArgs; + checkArgs << "list" << packageName; + checkProcess.start("apm", checkArgs); + checkProcess.waitForFinished(30000); // 30秒超时 + + QString checkOutput = checkProcess.readAllStandardOutput(); + if (checkOutput.contains(packageName)) { + // APM中存在对应的包,卸载APTSS版本 + QProcess removeProcess; + QStringList removeArgs; + removeArgs << "remove" << "--purge" << "-y" << packageName; + removeProcess.start("aptss", removeArgs); + removeProcess.waitForFinished(30000); // 30秒超时 + qDebug() << "卸载APTSS版本" << packageName << "退出码:" << removeProcess.exitCode(); + } else { + // APM中不存在对应的包,安装失败 + qWarning() << "APM中不存在对应的包:" << 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)) { + 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, debPath, source](int exitCode, QProcess::ExitStatus status) { + if (logFile) logFile->close(); + m_downloads[packageName].isInstalling = false; + if (exitCode == 0) { + m_downloads[packageName].isInstalled = true; + + // 安装成功后删除deb包 + if (QFile::exists(debPath)) { + if (QFile::remove(debPath)) { + qDebug() << "已删除deb包:" << debPath; + } else { + qWarning() << "删除deb包失败:" << debPath; + } + } + } else { + // 安装失败,尝试从APM安装 + if (source == "aptss") { + qDebug() << "APTSS安装失败,尝试从APM安装:" << packageName; + + // 检查apm命令是否存在 + QProcess whichProcess; + whichProcess.start("which", QStringList() << "apm"); + whichProcess.waitForFinished(5000); + + if (whichProcess.exitCode() != 0) { + // apm命令不存在,先安装apm + qDebug() << "apm命令不存在,先安装apm"; + QProcess installApmProcess; + installApmProcess.start("aptss", QStringList() << "install" << "apm" << "-y"); + installApmProcess.waitForFinished(60000); // 60秒超时 + + if (installApmProcess.exitCode() != 0) { + qWarning() << "安装apm失败:" << packageName; + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + return; + } + qDebug() << "apm安装成功"; + } + + // 检查APM中是否存在对应的包 + QProcess checkProcess; + QStringList checkArgs; + checkArgs << "list" << packageName; + checkProcess.start("apm", checkArgs); + checkProcess.waitForFinished(30000); // 30秒超时 + + QString checkOutput = checkProcess.readAllStandardOutput(); + if (checkOutput.contains(packageName)) { + // APM中存在对应的包,卸载当前版本 + QProcess removeProcess; + QStringList removeArgs; + removeArgs << "remove" << "--purge" << "-y" << packageName; + removeProcess.start("aptss", removeArgs); + removeProcess.waitForFinished(30000); + + // 从APM获取下载URL,使用与aptssupdater相同的方法 + QString downloadUrl; + QProcess process; + QString command = QString("amber-pm-debug /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf download %1 --print-uris").arg(packageName); + + process.start("bash", QStringList() << "-c" << command); + if (process.waitForFinished(30000)) { // 30秒超时 + QString output = process.readAllStandardOutput(); + // 解析输出格式:'URL' 文件名 大小 SHA512:哈希值 + QRegularExpression regex(R"('([^']+)'\s+\S+\s+(\d+)\s+SHA512:([^\s]+))"); + QRegularExpressionMatch match = regex.match(output); + + if (match.hasMatch()) { + downloadUrl = match.captured(1); + } + } + + if (!downloadUrl.isEmpty()) { + // 使用更新器的下载功能下载APM包 + QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); + m_downloads[packageName] = {0, true}; + m_downloadManager->startDownload(packageName, downloadUrl, outputPath); + + // 等待下载完成后再安装 + QEventLoop loop; + connect(m_downloadManager, &DownloadManager::downloadFinished, &loop, [&loop](const QString &, bool) { + loop.quit(); + }); + loop.exec(); + + // 下载完成后,使用APM安装 + QDir tempDir(QDir::tempPath()); + QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName) << QString("%1*.deb").arg(packageName), QDir::Files); + if (!debs.isEmpty()) { + QString apmDebPath = tempDir.absoluteFilePath(debs.first()); + QProcess apmProcess; + QStringList apmArgs; + apmArgs << "ssaudit" << apmDebPath; + apmProcess.start("apm", apmArgs); + apmProcess.waitForFinished(60000); // 60秒超时 + int apmExitCode = apmProcess.exitCode(); + qDebug() << "APM安装" << packageName << "退出码:" << apmExitCode; + + // APM安装成功后设置状态 + if (apmExitCode == 0) { + m_downloads[packageName].isInstalling = false; + m_downloads[packageName].isInstalled = true; + + // 安装成功后删除deb包 + if (QFile::exists(apmDebPath)) { + if (QFile::remove(apmDebPath)) { + qDebug() << "已删除deb包:" << apmDebPath; + } else { + qWarning() << "删除deb包失败:" << apmDebPath; + } + } + } + } + } else { + qWarning() << "无法获取APM包的下载URL:" << packageName; + } + } else { + // APM中不存在对应的包,不卸载aptss包 + qWarning() << "APM中不存在对应的包,安装失败:" << packageName; + } + } + } + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + }); + } else { + connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName, debPath]() { + 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; + + // 安装成功后删除deb包 + if (QFile::exists(debPath)) { + if (QFile::remove(debPath)) { + qDebug() << "已删除deb包:" << debPath; + } else { + qWarning() << "删除deb包失败:" << debPath; + } + } + + 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, debPath, source](int exitCode, QProcess::ExitStatus /*status*/) { + // 如果通过退出码判断安装成功,也删除deb包 + if (exitCode == 0 && QFile::exists(debPath)) { + if (QFile::remove(debPath)) { + qDebug() << "已删除deb包:" << debPath; + } else { + qWarning() << "删除deb包失败:" << debPath; + } + } else { + // 安装失败,尝试从APM安装 + if (source == "aptss") { + qDebug() << "APTSS安装失败,尝试从APM安装:" << packageName; + + // 检查apm命令是否存在 + QProcess whichProcess; + whichProcess.start("which", QStringList() << "apm"); + whichProcess.waitForFinished(5000); + + if (whichProcess.exitCode() != 0) { + // apm命令不存在,先安装apm + qDebug() << "apm命令不存在,先安装apm"; + QProcess installApmProcess; + installApmProcess.start("aptss", QStringList() << "install" << "apm" << "-y"); + installApmProcess.waitForFinished(60000); // 60秒超时 + + if (installApmProcess.exitCode() != 0) { + qWarning() << "安装apm失败:" << packageName; + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + return; + } + qDebug() << "apm安装成功"; + } + + // 检查APM中是否存在对应的包 + QProcess checkProcess; + QStringList checkArgs; + checkArgs << "list" << packageName; + checkProcess.start("apm", checkArgs); + checkProcess.waitForFinished(30000); // 30秒超时 + + QString checkOutput = checkProcess.readAllStandardOutput(); + if (checkOutput.contains(packageName)) { + // APM中存在对应的包,卸载当前版本 + QProcess removeProcess; + QStringList removeArgs; + removeArgs << "remove" << "--purge" << "-y" << packageName; + removeProcess.start("aptss", removeArgs); + removeProcess.waitForFinished(30000); + + // 从APM获取下载URL,使用与aptssupdater相同的方法 + QString downloadUrl; + QProcess process; + QString command = QString("amber-pm-debug /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf download %1 --print-uris").arg(packageName); + + process.start("bash", QStringList() << "-c" << command); + if (process.waitForFinished(30000)) { // 30秒超时 + QString output = process.readAllStandardOutput(); + // 解析输出格式:'URL' 文件名 大小 SHA512:哈希值 + QRegularExpression regex(R"('([^']+)'\s+\S+\s+(\d+)\s+SHA512:([^\s]+))"); + QRegularExpressionMatch match = regex.match(output); + + if (match.hasMatch()) { + downloadUrl = match.captured(1); + } + } + + if (!downloadUrl.isEmpty()) { + // 使用更新器的下载功能下载APM包 + QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName); + m_downloads[packageName] = {0, true}; + m_downloadManager->startDownload(packageName, downloadUrl, outputPath); + + // 等待下载完成后再安装 + QEventLoop loop; + connect(m_downloadManager, &DownloadManager::downloadFinished, &loop, [&loop](const QString &, bool) { + loop.quit(); + }); + loop.exec(); + + // 下载完成后,使用APM安装 + QDir tempDir(QDir::tempPath()); + QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName) << QString("%1*.deb").arg(packageName), QDir::Files); + if (!debs.isEmpty()) { + QString apmDebPath = tempDir.absoluteFilePath(debs.first()); + QProcess apmProcess; + QStringList apmArgs; + apmArgs << "ssaudit" << apmDebPath; + apmProcess.start("apm", apmArgs); + apmProcess.waitForFinished(60000); // 60秒超时 + int apmExitCode = apmProcess.exitCode(); + qDebug() << "APM安装" << packageName << "退出码:" << apmExitCode; + + // APM安装成功后设置状态 + if (apmExitCode == 0) { + m_downloads[packageName].isInstalling = false; + m_downloads[packageName].isInstalled = true; + + // 安装成功后删除deb包 + if (QFile::exists(apmDebPath)) { + if (QFile::remove(apmDebPath)) { + qDebug() << "已删除deb包:" << apmDebPath; + } else { + qWarning() << "删除deb包失败:" << apmDebPath; + } + } + } + } + } else { + qWarning() << "无法获取APM包的下载URL:" << packageName; + } + } else { + // APM中不存在对应的包,不卸载aptss包 + qWarning() << "APM中不存在对应的包,安装失败:" << packageName; + } + } + } + + emit updateDisplay(packageName); + m_installProcess->deleteLater(); + m_installProcess = nullptr; + m_installingPackage.clear(); + startNextInstall(); + }); + } + + QStringList args; + if (source == "apm") { + // APM 包使用 apm ssaudit 安装 + args << "ssaudit" << debPath; + m_installProcess->start("apm", args); + } else { + // APTSS 包使用 ssinstall 安装 + args << debPath << "--no-create-desktop-entry" << "--delete-after-install" << "--native"; + m_installProcess->start("/usr/bin/ssinstall", args); + } +} + +// 新增槽函数,用于更新旋转角度并触发刷新 +void AppDelegate::updateSpinner() { + m_spinnerAngle = (m_spinnerAngle + 10) % 360; // 每次增加10度 + emit updateDisplay(m_installingPackage); // 仅刷新当前正在安装的项 +} + +// 新增:更新选中应用的方法 +void AppDelegate::startDownloadForSelected() { + 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(); + + // 检查应用是否被忽略 + bool isIgnored = index.data(Qt::UserRole + 8).toBool(); + if (isIgnored) { + qDebug() << "跳过被忽略的应用:" << packageName; + continue; + } + + // 只下载选中的应用 + if (m_selectedPackages.contains(packageName)) { + 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::setSelectedPackages(const QSet &selected) { + m_selectedPackages = selected; +} + +QSet AppDelegate::getSelectedPackages() const { + return m_selectedPackages; +} + +void AppDelegate::clearSelection() { + m_selectedPackages.clear(); +} + +// 实现获取下载状态信息的方法 +const QHash& AppDelegate::getDownloads() const { + return m_downloads; +} \ No newline at end of file diff --git a/spark-update-tool/src/appdelegate.h b/spark-update-tool/src/appdelegate.h new file mode 100644 index 00000000..ac3f69ab --- /dev/null +++ b/spark-update-tool/src/appdelegate.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#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); + ~AppDelegate(); + + 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(); + void startDownloadForSelected(); + + // 复选框相关方法 + void setSelectedPackages(const QSet &selected); + QSet getSelectedPackages() const; + void clearSelection(); + + // 获取下载状态信息 + const QHash& getDownloads() const; + + +signals: + void updateDisplay(const QString &packageName); + void updateFinished(bool success); //传递是否完成更新 + void ignoreApp(const QString &packageName, const QString &version); // 新增:忽略应用信号 + void unignoreApp(const QString &packageName); // 新增:取消忽略应用信号 + +private slots: + void updateSpinner(); // 新增槽函数 + +private: + DownloadManager *m_downloadManager; + QHash m_downloads; + QAbstractItemModel *m_model = nullptr; + + // 复选框相关成员变量 + QSet m_selectedPackages; + + QQueue m_installQueue; + bool m_isInstalling = false; + QProcess *m_installProcess = nullptr; + QString m_installingPackage; + QElapsedTimer m_spinnerTimer; + + QTimer m_spinnerUpdateTimer; // 新增定时器 + int m_spinnerAngle = 0; // 新增角度变量 + + void enqueueInstall(const QString &packageName); + void startNextInstall(); +}; \ No newline at end of file diff --git a/spark-update-tool/src/applistmodel.cpp b/spark-update-tool/src/applistmodel.cpp new file mode 100644 index 00000000..ed01f050 --- /dev/null +++ b/spark-update-tool/src/applistmodel.cpp @@ -0,0 +1,77 @@ +#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 + case Qt::UserRole + 8: // 忽略状态 + return map.value("ignored"); + case Qt::UserRole + 9: // 包来源 + return map.value("source"); + 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 + map["ignored"] = obj["ignored"].toBool(); // 设置忽略状态 + map["source"] = obj["source"].toString(); // 设置包来源 + m_data.append(map); // 添加到 QList + + qDebug() << "设置到模型的包名:" << map["package"].toString() << "忽略状态:" << map["ignored"].toBool() << "来源:" << map["source"].toString(); + qDebug() << "设置到模型的下载 URL:" << map["download_url"].toString(); // 检查设置的数据 + } + + endResetModel(); +} + +bool AppListModel::isAppIgnored(const QModelIndex &index) const +{ + if (!index.isValid() || index.row() >= m_data.size()) + return false; + + const QVariantMap &map = m_data.at(index.row()); + return map.value("ignored").toBool(); +} \ No newline at end of file diff --git a/spark-update-tool/src/applistmodel.h b/spark-update-tool/src/applistmodel.h new file mode 100644 index 00000000..d3f304da --- /dev/null +++ b/spark-update-tool/src/applistmodel.h @@ -0,0 +1,29 @@ +#ifndef APPLISTMODEL_H +#define APPLISTMODEL_H + +#include +#include +// 添加 QJsonObject 头文件 +#include +#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); + + // 获取忽略状态 + bool isAppIgnored(const QModelIndex &index) const; + +private: + QList m_data; // 修改类型为 QList +}; + +#endif // APPLISTMODEL_H \ No newline at end of file diff --git a/spark-update-tool/src/aptssupdater.cpp b/spark-update-tool/src/aptssupdater.cpp new file mode 100644 index 00000000..6a352757 --- /dev/null +++ b/spark-update-tool/src/aptssupdater.cpp @@ -0,0 +1,654 @@ +#include "aptssupdater.h" +#include +#include +#include +#include +#include +#include + +aptssUpdater::aptssUpdater(QWidget *parent) + : QWidget(parent) +{ + packageName = getUpdateablePackages(); + apmPackageName = getApmUpdateablePackages(); +} + +QStringList aptssUpdater::getUpdateablePackages() +{ + QStringList packageDetails; + QProcess process; + QString command = R"(env LANGUAGE=en_US /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf list --upgradable -o Dir::Etc::sourcelist="/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list" -o Dir::Etc::sourceparts="/dev/null" -o APT::Get::List-Cleanup="0" | awk 'NR>1')"; + + process.start("bash", QStringList() << "-c" << command); + if (!process.waitForFinished(30000)) { // 30秒超时 + qWarning() << "Process failed to finish within 30 seconds."; + process.kill(); + return packageDetails; + } + + QString output = process.readAllStandardOutput(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList lines = output.split('\n', Qt::SkipEmptyParts); +#else + QStringList lines = output.split('\n', QString::SkipEmptyParts); +#endif + + + // 创建临时文件 + QTemporaryFile tempFile; + tempFile.setAutoRemove(false); + if (tempFile.open()) { + QTextStream stream(&tempFile); + + for (const QString &line : lines) { + QRegularExpression regex(R"(([\w\-\+\.]+)/\S+\s+([^\s]+)\s+\S+\s+\[upgradable from: ([^\]]+)\])"); + QRegularExpressionMatch match = regex.match(line); + if (match.hasMatch()) { + QString name = match.captured(1); + QString newVersion = match.captured(2); + QString oldVersion = match.captured(3); + + // 检查版本是否相同,相同则跳过 + if (newVersion == oldVersion) { + qDebug() << "跳过版本相同的包:" << name << "(" << oldVersion << "→" << newVersion << ")"; + continue; + } + + // 写入内存列表 + packageDetails << QString("%1: %2 → %3").arg(name, oldVersion, newVersion); + + // 写入临时文件(原始数据) + stream << name << "|" << oldVersion << "|" << newVersion << "\n"; + } + } + tempFile.close(); + m_tempFilePath = tempFile.fileName(); + qDebug()<< "临时文件路径:" << m_tempFilePath; + + } else { + qWarning() << "无法创建临时文件"; + } + + return packageDetails; +} + + +QStringList aptssUpdater::getPackageSizes() +{ + QStringList packageDetails; + + // 获取可更新包名列表 + QStringList updateablePackages; + for (const QString &pkgInfo : packageName) { + updateablePackages << pkgInfo.section(":", 0, 0).trimmed(); + } + + foreach (const QString &packageName, updateablePackages) { + QProcess process; // 在循环内部创建新的QProcess实例 + + // 构建新命令(包含包名参数) + QString command = QString("/usr/bin/apt download %1 --print-uris -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf " + "-o Dir::Etc::sourcelist=\"/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list\" " + "-o Dir::Etc::sourceparts=\"/dev/null\"").arg(packageName); + + process.start("bash", QStringList() << "-c" << command); + if (!process.waitForFinished(30000)) { // 30秒超时 + qWarning() << "获取包信息失败:" << packageName << "(超时)"; + process.kill(); + continue; + } + + QString output = process.readAllStandardOutput(); + // 使用正则匹配所有信息 + // 调整正则表达式匹配分组 + QRegularExpression regex(R"('([^']+)'\s+(\S+)\s+(\d+)\s+SHA512:([^\s]+))"); // 分组1:URL 分组2:文件名 分组3:大小 分组4:SHA512 + QRegularExpressionMatch match = regex.match(output); + + if (match.hasMatch()) { + QString url = match.captured(1); + QString fileName = match.captured(2); + QString size = match.captured(3); + QString sha512 = match.captured(4); + + // 调整字段顺序:包名 | 大小 | URL | SHA512 + packageDetails << QString("%1: %2|%3|%4").arg(packageName, size, url, sha512); + } + } + + qDebug() << "完整包信息:" << packageDetails; + return packageDetails; +} + + + + + +QStringList aptssUpdater::getDesktopAppNames() +{ + QStringList appNames; + + // 获取当前系统语言环境 + QString lang = QLocale().name().replace("_", "-"); + + // 遍历所有可更新包(复用已有的临时文件数据) + QStringList packages = packageName; + + foreach (const QString &package, packages) { + QProcess dpkgProcess; // 在循环内部创建新的QProcess实例 + + QString packageName = package.split(":")[0]; + QString finalName = packageName; // 默认使用包名 + + // 获取包文件列表 + dpkgProcess.start("dpkg", QStringList() << "-L" << packageName); + if (!dpkgProcess.waitForFinished(30000)) { // 30秒超时 + qWarning() << "获取包文件列表失败:" << packageName << "(超时)"; + dpkgProcess.kill(); + continue; + } + + // 修复:添加这行代码来获取进程输出 + QString output = dpkgProcess.readAllStandardOutput(); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList files = output.split('\n', Qt::SkipEmptyParts); +#else + QStringList files = output.split('\n', QString::SkipEmptyParts); +#endif + + // 先检查常规应用目录 + QStringList regularDesktopFiles = files.filter("/usr/share/applications/"); + QString regularAppName; + if (!regularDesktopFiles.isEmpty()) { + checkDesktopFiles(regularDesktopFiles, regularAppName, lang, packageName); + } + + // 如果常规目录没有找到,再检查特殊目录 + if (regularAppName.isEmpty()) { + QStringList specialDesktopFiles = files.filter(QRegularExpression(QString("/opt/apps/%1/entries/applications").arg(packageName))); + QString specialAppName; + if (!specialDesktopFiles.isEmpty()) { + checkDesktopFiles(specialDesktopFiles, specialAppName, lang, packageName); + if (!specialAppName.isEmpty()) { + finalName = specialAppName; + } + } + } else { + finalName = regularAppName; + } + + // 输出格式为[软件名|包名] + appNames << QString("[%1|%2]").arg(finalName, packageName); + } + qDebug()<< "应用名称列表:" << appNames; + return appNames; +} + + + + +bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName) +{ + QString lastValidName; + QRegularExpression noDisplayRe("^NoDisplay=(true|True)"); + QRegularExpression nameRe("^Name\\[?" + lang + "?\\]?=(.*)"); + QRegularExpression nameOrigRe("^Name=(.*)"); + + foreach (const QString &filePath, desktopFiles) { + if (!filePath.endsWith(".desktop")) continue; + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) continue; + + bool skip = false; + QString currentName; + + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + // 检查NoDisplay属性 + if (line.startsWith("NoDisplay=")) { + if (noDisplayRe.match(line).hasMatch()) { + skip = true; + break; + } + } + + // 优先匹配本地化名称 + if (currentName.isEmpty()) { + QRegularExpressionMatch match = nameRe.match(line); + if (match.hasMatch()) { + currentName = match.captured(1); + continue; + } + + // 匹配原始名称 + match = nameOrigRe.match(line); + if (match.hasMatch()) { + currentName = match.captured(1); + } + } + } + + if (!skip && !currentName.isEmpty()) { + lastValidName = currentName; + } + } + + // 处理最终的有效名称 + if (!lastValidName.isEmpty()) { + appName = lastValidName; // 直接赋值而不是使用<< + return true; + } + + // 回退到包名 + appName = packageName; + return false; +} + +QStringList aptssUpdater::getPackageIcons() +{ + QStringList packageIcons; + + // 遍历所有可更新包 + QStringList packages = packageName; + + foreach (const QString &package, packages) { + QProcess dpkgProcess; // 在循环内部创建新的QProcess实例 + + QString packageName = package.split(":")[0]; + QString iconPath = ":/resources/default_icon.png"; // 默认图标 + + // 获取包文件列表 + dpkgProcess.start("dpkg", QStringList() << "-L" << packageName); + if (!dpkgProcess.waitForFinished(30000)) { // 30秒超时 + qWarning() << "获取包文件列表失败:" << packageName << "(超时)"; + dpkgProcess.kill(); + packageIcons << QString("%1: %2").arg(packageName, iconPath); + continue; + } +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', Qt::SkipEmptyParts); +#else + QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', QString::SkipEmptyParts); +#endif + + + // 查找.desktop文件 + QStringList desktopFiles = files.filter(QRegularExpression("/(usr/share|opt/apps)/.*\\.desktop$")); + + // 从.desktop文件中提取图标 + foreach (const QString &desktopFile, desktopFiles) { + QFile file(desktopFile); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + if (line.startsWith("Icon=")) { + QString iconName = line.mid(5).trimmed(); + + // 处理相对图标名(如Icon=vscode) + if (!iconName.contains('/')) { + // 查找标准图标路径 + QStringList iconPaths = { + QString("/usr/share/pixmaps/%1.png").arg(iconName), + QString("/usr/share/icons/hicolor/48x48/apps/%1.png").arg(iconName), + QString("/usr/share/icons/hicolor/scalable/apps/%1.svg").arg(iconName), + QString("/opt/apps/%1/entries/icons/hicolor/48x48/apps/%2.png").arg(packageName, iconName) + }; + + foreach (const QString &path, iconPaths) { + if (QFile::exists(path)) { + iconPath = path; + qDebug() << "找到图标文件:" << path; + break; + } + } + } else { + // 已经是绝对路径 + if (QFile::exists(iconName)) { + iconPath = iconName; + qDebug() << "使用绝对路径图标文件:" << iconName; + } + } + break; + } + } + file.close(); + } + } + + // 如果.desktop中没有找到图标,尝试直接查找包中的图标文件 + if (iconPath == ":/resources/default_icon.png") { + qDebug() << "未在.desktop文件中找到图标,尝试直接查找包中的图标文件"; + QStringList iconFiles = files.filter(QRegularExpression("/(usr/share/pixmaps|usr/share/icons|opt/apps/.*/entries/icons)/.*\\.(png|svg)$")); + if (!iconFiles.isEmpty()) { + iconPath = iconFiles.first(); + qDebug() << "从包中找到图标文件:" << iconPath; + } else { + qDebug() << "未在包中找到图标文件,使用默认图标"; + } + } + + qDebug() << "包名:" << packageName << "图标路径:" << iconPath; + packageIcons << QString("%1: %2").arg(packageName, iconPath); + } + + return packageIcons; +} + + +QJsonArray aptssUpdater::getUpdateInfoAsJson() +{ + QJsonArray jsonArray; + + // 获取所有需要的信息 + QStringList sizes = getPackageSizes(); + QStringList names = getDesktopAppNames(); + QStringList icons = getPackageIcons(); + + // 创建包名到各种信息的映射 + QHash> 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()<1')"; + + process.start("bash", QStringList() << "-c" << command); + if (!process.waitForFinished(30000)) { // 30秒超时 + qWarning() << "APM process failed to finish within 30 seconds."; + process.kill(); + return packageDetails; + } + + QString output = process.readAllStandardOutput(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList lines = output.split('\n', Qt::SkipEmptyParts); +#else + QStringList lines = output.split('\n', QString::SkipEmptyParts); +#endif + + for (const QString &line : lines) { + QRegularExpression regex(R"(([\w\-\+\.]+)/\S+\s+([^\s]+)\s+\S+\s+\[upgradable from: ([^\]]+)\])"); + QRegularExpressionMatch match = regex.match(line); + if (match.hasMatch()) { + QString name = match.captured(1); + QString newVersion = match.captured(2); + QString oldVersion = match.captured(3); + + // 检查版本是否相同,相同则跳过 + if (newVersion == oldVersion) { + qDebug() << "跳过版本相同的APM包:" << name << "(" << oldVersion << "→" << newVersion << ")"; + continue; + } + + // 写入内存列表 + packageDetails << QString("%1: %2 → %3").arg(name, oldVersion, newVersion); + } + } + + return packageDetails; +} + +QJsonArray aptssUpdater::getApmUpdateInfoAsJson() +{ + QJsonArray jsonArray; + + // 解析APM包版本信息 + QHash> packageInfo; + for (const QString &pkg : apmPackageName) { + QStringList parts = pkg.split(": "); + if (parts.size() >= 2) { + QString packageName = parts[0]; + QStringList versions = parts[1].split(" → "); + if (versions.size() == 2) { + packageInfo[packageName]["current_version"] = versions[0]; + packageInfo[packageName]["new_version"] = versions[1]; + packageInfo[packageName]["source"] = "apm"; + } + } + } + + // 构建JSON数组 + for (const QString &packageName : packageInfo.keys()) { + QJsonObject jsonObj; + jsonObj["package"] = packageName; + + // 从APM桌面文件中解析应用名称和图标 + QString displayName = packageName; // 默认使用包名 + QString iconPath = ":/resources/default_icon.png"; // 默认图标 + + // APM应用的desktop文件路径 + QString apmDesktopPath = QString("/var/lib/apm/apm/files/ace-env/var/lib/apm/%1/entries/applications").arg(packageName); + QDir desktopDir(apmDesktopPath); + if (desktopDir.exists()) { + // 查找desktop文件 + QStringList desktopFiles = desktopDir.entryList(QStringList() << "*.desktop", QDir::Files); + if (!desktopFiles.isEmpty()) { + QString desktopFile = desktopDir.absoluteFilePath(desktopFiles.first()); + QFile file(desktopFile); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + if (line.startsWith("Name=")) { + displayName = line.mid(5).trimmed(); + } else if (line.startsWith("Icon=")) { + QString iconName = line.mid(5).trimmed(); + // 处理图标路径 + if (!iconName.contains('/')) { + // 查找APM包中的图标 + QString apmIconPath = QString("/var/lib/apm/apm/files/ace-env/var/lib/apm/%1/entries/icons/hicolor/48x48/apps/%2.png").arg(packageName, iconName); + if (QFile::exists(apmIconPath)) { + iconPath = apmIconPath; + } + } else { + // 已经是绝对路径 + if (QFile::exists(iconName)) { + iconPath = iconName; + } + } + } + } + file.close(); + } + } + } + + // 获取APM包大小和下载信息 + QString size = "0"; + QString url = ""; + QString sha512 = ""; + + QProcess process; + QString command = QString("amber-pm-debug /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf download %1 --print-uris").arg(packageName); + + process.start("bash", QStringList() << "-c" << command); + if (process.waitForFinished(30000)) { // 30秒超时 + QString output = process.readAllStandardOutput(); + // 解析输出格式:'URL' 文件名 大小 SHA512:哈希值 + QRegularExpression regex(R"('([^']+)'\s+\S+\s+(\d+)\s+SHA512:([^\s]+))"); + QRegularExpressionMatch match = regex.match(output); + + if (match.hasMatch()) { + url = match.captured(1); + size = match.captured(2); + sha512 = match.captured(3); + } + } + + jsonObj["name"] = displayName; + jsonObj["current_version"] = packageInfo[packageName]["current_version"]; + jsonObj["new_version"] = packageInfo[packageName]["new_version"]; + jsonObj["icon"] = iconPath; + jsonObj["ignored"] = false; // 默认不忽略 + jsonObj["source"] = "apm"; + jsonObj["size"] = size; + jsonObj["download_url"] = url; + jsonObj["sha512"] = sha512; + jsonArray.append(jsonObj); + } + qDebug()<<"APM更新信息:"< aptssMap; + for (const QJsonValue &value : aptssInfo) { + QJsonObject obj = value.toObject(); + QString packageName = obj["package"].toString(); + obj["source"] = "aptss"; + aptssMap[packageName] = obj; + } + + QHash apmMap; + for (const QJsonValue &value : apmInfo) { + QJsonObject obj = value.toObject(); + QString packageName = obj["package"].toString(); + obj["source"] = "apm"; + apmMap[packageName] = obj; + } + + QJsonArray mergedArray; + + // 处理只在aptss中存在的包 + for (const QString &packageName : aptssMap.keys()) { + if (!apmMap.contains(packageName)) { + mergedArray.append(aptssMap[packageName]); + } + } + + // 处理只在apm中存在的包 + for (const QString &packageName : apmMap.keys()) { + if (!aptssMap.contains(packageName)) { + mergedArray.append(apmMap[packageName]); + } + } + + // 处理在两者中都存在的包 + for (const QString &packageName : aptssMap.keys()) { + if (apmMap.contains(packageName)) { + QJsonObject aptssObj = aptssMap[packageName]; + QJsonObject apmObj = apmMap[packageName]; + + // 比较版本 + QString aptssVersion = aptssObj["new_version"].toString(); + QString apmVersion = apmObj["new_version"].toString(); + + // 这里简化处理,实际应该使用版本比较函数 + if (apmVersion > aptssVersion) { + // APM版本更高,使用APM版本 + mergedArray.append(apmObj); + } else { + // APTSS版本更高或相同,不展示该包 + qDebug() << "APTSS版本更高,不展示APM包:" << packageName; + } + } + } + + qDebug()<<"合并后的更新信息:"< +#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; + + // APM 相关方法 + QStringList getApmUpdateablePackages(); // 查询 APM 可更新包列表及更新内容 + QJsonArray getApmUpdateInfoAsJson(); // 获取 APM 更新信息的 JSON 格式 + QJsonArray mergeUpdateInfo(); // 合并 APTSS 和 APM 的更新信息 + +signals: +private: + bool checkDesktopFiles(const QStringList &desktopFiles, QString &appName, const QString &lang, const QString &packageName); + QStringList packageName; + QStringList apmPackageName; // APM 包列表 +}; + +#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 00000000..04553284 --- /dev/null +++ b/spark-update-tool/src/downloadmanager.cpp @@ -0,0 +1,133 @@ +#include "downloadmanager.h" +#include +#include +#include +#include +#include + +DownloadManager::DownloadManager(QObject *parent) : QObject(parent) +{ + cleanupTempFiles(); +} + +DownloadManager::~DownloadManager() +{ + // 终止并清理所有正在运行的下载进程 + for (auto it = m_processes.begin(); it != m_processes.end(); ) { + QProcess *process = it.value(); + if (process->state() != QProcess::NotRunning) { + process->kill(); // 立即终止进程 + process->waitForFinished(3000); // 最多等待3秒 + } + process->deleteLater(); + it = m_processes.erase(it); + } +} + +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", + "--connect-timeout=30", + "--max-tries=3", + "--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); + } +} \ No newline at end of file diff --git a/spark-update-tool/src/downloadmanager.h b/spark-update-tool/src/downloadmanager.h new file mode 100644 index 00000000..8e1aa317 --- /dev/null +++ b/spark-update-tool/src/downloadmanager.h @@ -0,0 +1,29 @@ +#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 &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 \ No newline at end of file diff --git a/spark-update-tool/src/icons.qrc b/spark-update-tool/src/icons.qrc new file mode 100644 index 00000000..9bbc9d3c --- /dev/null +++ b/spark-update-tool/src/icons.qrc @@ -0,0 +1,9 @@ + + + ../resources/down_arrow.svg + ../resources/default_icon.svg + ../resources/spark-update-tool.svg + ../resources/128*128/spark-update-tool.png + ../resources/default_icon.png + + diff --git a/spark-update-tool/src/ignoreconfig.cpp b/spark-update-tool/src/ignoreconfig.cpp new file mode 100644 index 00000000..2a61371b --- /dev/null +++ b/spark-update-tool/src/ignoreconfig.cpp @@ -0,0 +1,148 @@ +#include "ignoreconfig.h" +#include +#include +#include +#include +#include +#include // for geteuid + +IgnoreConfig::IgnoreConfig(QObject *parent) + : QObject(parent) +{ + // 设置配置文件路径 + QString configDir; + + // // 检查是否以 root 权限运行 + // if (geteuid() == 0) { + // // 首先检查是否有 SUDO_USER_HOME 环境变量(表示是通过 pkexec 提权的普通用户) + // QByteArray sudoUserHomeEnv = qgetenv("SUDO_USER_HOME"); + // if (!sudoUserHomeEnv.isEmpty()) { + // // 通过 pkexec 提权的普通用户,使用原用户的配置目录 + // QString sudoUserHomePath = QString::fromLocal8Bit(sudoUserHomeEnv); + // configDir = sudoUserHomePath + "/.config"; + // } else { + // // 获取实际的 HOME 目录来判断是真正的 root 用户还是其他方式提权的用户 + // QByteArray homeEnv = qgetenv("HOME"); + // QString homePath = QString::fromLocal8Bit(homeEnv); + + // if (homePath == "/root") { + // // 真正的 root 用户,使用 /root/.config + // configDir = "/root/.config"; + // } else { + // // 其他方式提权的用户,使用 HOME 目录下的配置 + // configDir = homePath + "/.config"; + // } + // } + // } else { + // // 普通用户,使用标准配置目录 + // configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + // } + configDir = "/etc/"; + QDir dir(configDir); + if (!dir.exists()) { + dir.mkpath("."); + } + m_configFilePath = dir.filePath("spark-store/ignored_apps.conf"); + + // 确保目录存在 + QFileInfo fileInfo(m_configFilePath); + QDir configDirPath = fileInfo.dir(); + if (!configDirPath.exists()) { + configDirPath.mkpath("."); + } + + // 加载现有配置 + loadConfig(); + + // 输出忽略列表到 qDebug + printIgnoredApps(); +} + +void IgnoreConfig::addIgnoredApp(const QString &packageName, const QString &version) +{ + m_ignoredApps.insert(qMakePair(packageName, version)); + saveConfig(); +} + +void IgnoreConfig::removeIgnoredApp(const QString &packageName) +{ + // 移除所有该包名的版本 + auto it = m_ignoredApps.begin(); + while (it != m_ignoredApps.end()) { + if (it->first == packageName) { + it = m_ignoredApps.erase(it); + } else { + ++it; + } + } + saveConfig(); +} + +bool IgnoreConfig::isAppIgnored(const QString &packageName, const QString &version) const +{ + return m_ignoredApps.contains(qMakePair(packageName, version)); +} + +QSet> IgnoreConfig::getIgnoredApps() const +{ + return m_ignoredApps; +} + +void IgnoreConfig::printIgnoredApps() const +{ + qDebug() << "=== 忽略的应用列表 ==="; + qDebug() << "配置文件路径:" << m_configFilePath; + + if (m_ignoredApps.isEmpty()) { + qDebug() << "没有忽略的应用"; + } else { + for (const auto &app : m_ignoredApps) { + qDebug() << "忽略的应用:" << app.first << "版本:" << app.second; + } + } + qDebug() << "===================="; +} + +bool IgnoreConfig::saveConfig() +{ + QFile file(m_configFilePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qDebug() << "无法打开配置文件进行写入:" << m_configFilePath; + return false; + } + + QTextStream out(&file); + for (const auto &app : m_ignoredApps) { + out << app.first << "|" << app.second << "\n"; + } + file.close(); + return true; +} + +bool IgnoreConfig::loadConfig() +{ + QFile file(m_configFilePath); + if (!file.exists()) { + // 配置文件不存在,这是正常的,返回true表示没有错误 + return true; + } + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "无法打开配置文件进行读取:" << m_configFilePath; + return false; + } + + m_ignoredApps.clear(); + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + if (!line.isEmpty()) { + QStringList parts = line.split('|'); + if (parts.size() == 2) { + m_ignoredApps.insert(qMakePair(parts[0], parts[1])); + } + } + } + file.close(); + return true; +} \ No newline at end of file diff --git a/spark-update-tool/src/ignoreconfig.h b/spark-update-tool/src/ignoreconfig.h new file mode 100644 index 00000000..2952eca2 --- /dev/null +++ b/spark-update-tool/src/ignoreconfig.h @@ -0,0 +1,42 @@ +#ifndef IGNORECONFIG_H +#define IGNORECONFIG_H + +#include +#include +#include +#include + +class IgnoreConfig : public QObject +{ + Q_OBJECT + +public: + explicit IgnoreConfig(QObject *parent = nullptr); + + // 添加忽略的应用(包名和版本号) + void addIgnoredApp(const QString &packageName, const QString &version); + + // 移除忽略的应用 + void removeIgnoredApp(const QString &packageName); + + // 检查应用是否被忽略 + bool isAppIgnored(const QString &packageName, const QString &version) const; + + // 获取所有被忽略的应用 + QSet> getIgnoredApps() const; + + // 输出所有被忽略的应用到 qDebug + void printIgnoredApps() const; + + // 保存配置到文件 + bool saveConfig(); + + // 从文件加载配置 + bool loadConfig(); + +private: + QSet> m_ignoredApps; + QString m_configFilePath; +}; + +#endif // IGNORECONFIG_H \ No newline at end of file diff --git a/spark-update-tool/src/main.cpp b/spark-update-tool/src/main.cpp new file mode 100644 index 00000000..f70a913e --- /dev/null +++ b/spark-update-tool/src/main.cpp @@ -0,0 +1,88 @@ +#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"); + QByteArray home = qgetenv("HOME"); // 获取原始用户的 HOME 目境变量 + + QStringList args; + args << "env" + << "DISPLAY=" + display + << "XAUTHORITY=" + xauthority + << "SUDO_USER_HOME=" + home // 传递原始用户的 HOME 路径 + << 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 00000000..f2cf74e7 --- /dev/null +++ b/spark-update-tool/src/mainwindow.cpp @@ -0,0 +1,445 @@ +#include "mainwindow.h" +#include "./ui_mainwindow.h" +#include +#include +#include +#include // 新增 +#include // 新增 +#include +#include +#include // for geteuid +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , m_model(new AppListModel(this)) + , m_delegate(new AppDelegate(this)) + , m_ignoreConfig(new IgnoreConfig(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(m_delegate, &AppDelegate::ignoreApp, this, &MainWindow::onIgnoreApp); + connect(m_delegate, &AppDelegate::unignoreApp, this, &MainWindow::onUnignoreApp); + + // 新增:点击“更新全部”按钮批量下载 + connect(ui->updatePushButton, &QPushButton::clicked, this, [=](){ + qDebug()<<"更新按钮被点击"; + if (m_delegate->getSelectedPackages().isEmpty()) { + // 没有选中任何应用,更新全部 + m_delegate->startDownloadForAll(); + } else { + // 有选中应用,更新选中 + m_delegate->startDownloadForSelected(); + m_delegate->clearSelection(); + updateButtonText(); + } + }); + + // 新增:监听选择变化 + connect(m_delegate, &AppDelegate::updateDisplay, this, &MainWindow::handleSelectionChanged); + + checkUpdates(); + // 新增:监听搜索框文本变化 + connect(ui->searchPlainTextEdit, &QPlainTextEdit::textChanged, this, [=]() { + QString keyword = ui->searchPlainTextEdit->toPlainText(); + filterAppsByKeyword(keyword); + }); + initStyle(); + + // 确保搜索框内容为空,placeholder 能显示 + ui->searchPlainTextEdit->clear(); + }); + + // 启动异步任务 + watcher->setFuture(QtConcurrent::run([this](){ + runAptssUpgrade(); + })); + QScreen *screen = QGuiApplication::screenAt(QCursor::pos()); + if (!screen) screen = QGuiApplication::primaryScreen(); + QRect screenGeometry = screen->geometry(); + int x = screenGeometry.x() + (screenGeometry.width() - this->width()) / 2; + int y = screenGeometry.y() + (screenGeometry.height() - this->height()) / 2; + this->move(x, y); +} +//初始化控件样式 +void MainWindow::initStyle() +{ + //设置窗口标题 + this->setWindowTitle("软件更新中心"); + + //查询框样式 + ui->searchPlainTextEdit->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; + } + QPlainTextEdit[placeholderText]:empty { + color: #9CA3AF; + } + )"); + + ui->searchPlainTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->searchPlainTextEdit->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.mergeUpdateInfo(); + + // 分离正常应用和忽略应用 + QJsonArray normalApps; + QJsonArray ignoredApps; + + for (const auto &item : updateInfo) { + QJsonObject obj = item.toObject(); + QString packageName = obj["package"].toString(); + QString currentVersion = obj["current_version"].toString(); + + // 检查应用是否被忽略 + if (m_ignoreConfig->isAppIgnored(packageName, currentVersion)) { + // 标记为忽略状态 + obj["ignored"] = true; + ignoredApps.append(obj); + } else { + obj["ignored"] = false; + normalApps.append(obj); + } + } + + // 合并数组:正常应用在前,忽略应用在后 + QJsonArray finalApps; + for (const auto &item : normalApps) { + finalApps.append(item); + } + for (const auto &item : ignoredApps) { + finalApps.append(item); + } + + m_allApps = finalApps; // 保存所有应用数据 + m_model->setUpdateData(finalApps); + + for (const auto &item : finalApps) { + QJsonObject obj = item.toObject(); + qDebug() << "模型设置的包名:" << obj["package"].toString() << "忽略状态:" << obj["ignored"].toBool() << "来源:" << obj["source"].toString(); + qDebug() << "模型设置的下载 URL:" << obj["download_url"].toString(); // 检查模型数据 + } +} + +// 新增:根据关键字过滤应用 +void MainWindow::filterAppsByKeyword(const QString &keyword) +{ + if (keyword.trimmed().isEmpty()) { + m_model->setUpdateData(m_allApps); + return; + } + + // 分离正常应用和忽略应用 + QJsonArray normalApps; + QJsonArray ignoredApps; + + 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)) { + + // 检查是否为忽略状态 + if (obj.value("ignored").toBool()) { + ignoredApps.append(item); + } else { + normalApps.append(item); + } + } + } + + // 合并数组:正常应用在前,忽略应用在后 + QJsonArray filtered; + for (const auto &item : normalApps) { + filtered.append(item); + } + for (const auto &item : ignoredApps) { + filtered.append(item); + } + + m_model->setUpdateData(filtered); +} + +void MainWindow::runAptssUpgrade() +{ + QProcess process; + + // 检查是否已经是root用户,如果是则直接执行命令,否则使用sudo + if (geteuid() == 0) { + // root用户直接执行 + process.start("aptss", QStringList() << "ssupdate"); + } else { + // 非root用户使用sudo + process.start("sudo", QStringList() << "aptss" << "ssupdate"); + } + + if (!process.waitForStarted(5000)) { + QMessageBox::warning(this, "升级失败", "无法启动 aptss ssupdate"); + return; + } + process.write("n\n"); + process.closeWriteChannel(); + + // 设置超时时间,避免无限等待 + if (!process.waitForFinished(30000)) { // 30秒超时 + qDebug() << "aptss ssupdate 执行超时"; + process.kill(); // 强制终止进程 + return; + } + + if (process.exitCode() != 0) { + QMessageBox::warning(this, "升级失败", "执行 aptss ssupdate 失败,请检查系统环境或稍后再试。"); + } +} +void MainWindow::closeEvent(QCloseEvent *event) +{ + // 检查是否正在进行更新 + bool isUpdating = false; + + // 通过AppDelegate检查是否有正在下载或安装的应用 + const QHash& downloads = m_delegate->getDownloads(); + for (auto it = downloads.constBegin(); it != downloads.constEnd(); ++it) { + if (it.value().isDownloading || it.value().isInstalling) { + isUpdating = true; + break; + } + } + + // 如果正在更新,才显示确认对话框 + if (isUpdating) { + QMessageBox::StandardButton reply = QMessageBox::question(this, "确认关闭", "正在更新,是否确认关闭窗口?", QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + event->accept(); + } else { + event->ignore(); + } + } else { + // 如果没有更新,直接关闭窗口 + event->accept(); + } +} +void MainWindow::handleUpdateFinished(bool success) +{ + if (success) { + // 更新成功时的处理逻辑 + QMessageBox::information(this, "更新完成", "软件更新已成功完成!"); + } else { + // 更新失败时的处理逻辑 + QMessageBox::warning(this, "更新失败", "软件更新过程中出现错误,请稍后再试。"); + } + + // 刷新应用列表 + checkUpdates(); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +// 新增:更新按钮文本 +void MainWindow::updateButtonText() { + int selectedCount = m_delegate->getSelectedPackages().size(); + if (selectedCount > 0) { + ui->updatePushButton->setText(QString("更新选中(%1)").arg(selectedCount)); + } else { + ui->updatePushButton->setText("更新全部"); + } +} + +// 新增:处理选择变化 +void MainWindow::handleSelectionChanged() { + updateButtonText(); +} + +// 新增:处理忽略应用的槽函数 +void MainWindow::onIgnoreApp(const QString &packageName, const QString &version) { + // 将应用添加到忽略配置中 + m_ignoreConfig->addIgnoredApp(packageName, version); + + // 更新模型中应用的状态,而不是移除 + QJsonArray updatedApps; + for (const auto &item : m_allApps) { + QJsonObject obj = item.toObject(); + if (obj["package"].toString() == packageName) { + obj["ignored"] = true; // 标记为忽略状态 + } + updatedApps.append(obj); + } + m_allApps = updatedApps; + + // 重新排序:正常应用在前,忽略应用在后 + checkUpdates(); +} + +// 新增:处理取消忽略应用的槽函数 +void MainWindow::onUnignoreApp(const QString &packageName) { + // 从忽略配置中移除应用 + m_ignoreConfig->removeIgnoredApp(packageName); + + // 更新模型中应用的状态 + QJsonArray updatedApps; + for (const auto &item : m_allApps) { + QJsonObject obj = item.toObject(); + if (obj["package"].toString() == packageName) { + obj["ignored"] = false; // 标记为非忽略状态 + } + updatedApps.append(obj); + } + m_allApps = updatedApps; + + // 重新排序:正常应用在前,忽略应用在后 + checkUpdates(); +} \ No newline at end of file diff --git a/spark-update-tool/src/mainwindow.h b/spark-update-tool/src/mainwindow.h new file mode 100644 index 00000000..5bf2f0a5 --- /dev/null +++ b/spark-update-tool/src/mainwindow.h @@ -0,0 +1,48 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "aptssupdater.h" +#include "applistmodel.h" +#include "appdelegate.h" +#include "ignoreconfig.h" +#include +#include // 添加头文件 +#include +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +protected: + void closeEvent(QCloseEvent *event) override; + +private: + Ui::MainWindow *ui; + void checkUpdates(); + void initStyle(); + void runAptssUpgrade(); + AppListModel *m_model; + AppDelegate *m_delegate; + IgnoreConfig *m_ignoreConfig; // 新增:忽略配置管理 + QListView *listView; // 声明 QListView 指针 + QJsonArray m_allApps; // 新增:保存所有应用数据 + void filterAppsByKeyword(const QString &keyword); // 新增:搜索过滤函数声明 + void updateButtonText(); // 新增:更新按钮文本 + +private slots: + void handleUpdateFinished(bool success); // 新增:处理更新完成的槽函数 + void handleSelectionChanged(); // 新增:处理选择变化的槽函数 + void onIgnoreApp(const QString &packageName, const QString &version); // 新增:处理忽略应用的槽函数 + void onUnignoreApp(const QString &packageName); // 新增:处理取消忽略应用 +}; +#endif // MAINWINDOW_H \ No newline at end of file diff --git a/spark-update-tool/src/mainwindow.ui b/spark-update-tool/src/mainwindow.ui new file mode 100644 index 00000000..8c2b025e --- /dev/null +++ b/spark-update-tool/src/mainwindow.ui @@ -0,0 +1,256 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Noto Sans Vai + 22 + + + + 软件更新中心 + + + + + + + Qt::Orientation::Horizontal + + + + 1245 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 320 + 38 + + + + + 320 + 38 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 200 + 38 + + + + + + + 81.000000000000000 + + + 搜索软件... + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + 214 + 40 + + + + + 214 + 40 + + + + + + 0 + 0 + 102 + 38 + + + + + 102 + 38 + + + + + 102 + 38 + + + + + + + Qt::LayoutDirection::LeftToRight + + + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow + + + + 按名称 + + + + + + + 118 + 0 + 96 + 40 + + + + 更新全部 + + + + + + + + + + + true + + + + 0 + 0 + + + + + + + + + + + + + 0 + 0 + 1440 + 23 + + + + + + + \ No newline at end of file diff --git a/tool/apt-fast-conf/aptss-apt.conf b/tool/apt-fast-conf/aptss-apt.conf new file mode 100644 index 00000000..a6e0d633 --- /dev/null +++ b/tool/apt-fast-conf/aptss-apt.conf @@ -0,0 +1,16 @@ +Debug::RunScripts true; +Dir::Cache::archives "/var/cache/apt/archives"; +Dir::Cache "/var/lib/aptss/"; +Dir::Etc::SourceParts "/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/"; +Dir::State::lists "/var/lib/aptss/lists/"; + +APT::Get::Fix-Broken true; +APT::Get::List-Cleanup="0"; + +Acquire::GzipIndexes "false"; + +#clear APT::Update::Post-Invoke-Success; + +#clear DPkg::Post-Invoke; + +#clear DPkg::Pre-Install-Pkgs; diff --git a/tool/apt-fast-conf/sources.list.d/.keep b/tool/apt-fast-conf/sources.list.d/.keep new file mode 100644 index 00000000..e69de29b diff --git a/tool/apt-fast/ss-apt-fast b/tool/apt-fast/ss-apt-fast new file mode 100755 index 00000000..86731c65 --- /dev/null +++ b/tool/apt-fast/ss-apt-fast @@ -0,0 +1,865 @@ +#!/bin/bash +# +# apt-fast v1.10.0 +# Use this just like aptitude or apt-get for faster package downloading. +# +# Copyright: 2008-2012 Matt Parnell, http://www.mattparnell.com +# Improvements, maintenance, revisions - 2012, 2017-2019 Dominique Lasserre +# +# You may distribute this file 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. +# + +shopt -s nullglob +[ -n "$DEBUG" ] && set -xv + +# Print colored messages. +# Usage: msg "message text" "message type" "optional: err" +# Message types are 'normal', 'hint' or 'warning'. Warnings and messages with a +# third argument are piped to stderr. + +THREADS=$(nproc 2>/dev/null || echo 4) + +# Set color variables. +cGreen='\e[0;32m' +cRed='\e[0;31m' +cBlue='\e[0;34m' +endColor='\e[0m' + +msg(){ + msg_options=() + case "$2" in + normal) beginColor="$cGreen";; + hint) beginColor="$cBlue";; + warning) beginColor="$cRed";; + question) beginColor="$cRed"; msg_options=(-n);; + *) beginColor= ;; + esac + + if [ -z "$3" ] && [ "$2" != "warning" ]; then + echo -e "${msg_options[@]}" "${aptfast_prefix}${beginColor}$1${endColor}" + else + echo -e "${msg_options[@]}" "${aptfast_prefix}${beginColor}$1${endColor}" >&2 + fi +} +msg_already_running() +{ + msg "Other aptss is running. Waited $timer senconds..." "normal" + msg "有其他的aptss正在运行。已经等待了$timer秒" "normal" +} + +# Check if a lock file exists. +#if [ -f "$LCK_FILE.lock" ]; then +# msg_already_running +# exit 1 +#fi + +# Move download file away so missing permissions won't stop usage. +CLEANUP_STATE=0 +cleanup_dllist() +{ + if [ -f "$DLLIST" ] + then + if ! mv -- "$DLLIST{,.old}" 2>/dev/null + then + if ! rm -fr -- "${LISTTEMP}" 2>/dev/null + then + msg "Could not clean up download list file." "warning" + msg "无法清除下载列表文件." "warning" + CLEANUP_STATE=1 + fi + fi + fi +} + +cleanup_aptfast() +{ + local last_exit_code=$? + [ "$CLEANUP_STATE" -eq 0 ] && CLEANUP_STATE=$last_exit_code + cleanup_dllist + _remove_lock + # 添加删除临时目录的逻辑 + if [ -n "$LISTTEMP" ] && [ -d "$LISTTEMP" ]; then + rm -rf "$LISTTEMP" + fi +} +exit_cleanup_state() +{ + cleanup_aptfast + exit $CLEANUP_STATE +} + +LCK_FD=99 +# create the lock file and lock it, die on failure +_create_lock() +{ + eval "exec $LCK_FD>\"$LCK_FILE.lock\"" + + # 设置 trap 来清理资源 + trap "cleanup_aptfast" EXIT + trap "cleanup_aptfast; exit 1" INT TERM + + timer=0 + max_wait=180 # 最大等待时间为180秒(3分钟) + + until $(flock -xn $LCK_FD); do + msg_already_running + sleep 1 + let timer+=1 + + if [ $timer -ge $max_wait ]; then + echo "timeout" + exit 1 + fi + done + + unset timer +} + +# unlock and remove the lock file +_remove_lock() +{ + # Only unlock if lock file exists (was created by _create_lock) + if [ -f "$LCK_FILE.lock" ]; then + flock -u "$LCK_FD" 2>/dev/null + rm -f "$LCK_FILE.lock" + fi +} + +# Search for known options and decide if root privileges are needed. +root=$# +option= +for argument in "$@"; do + case "$argument" in + upgrade | full-upgrade | install | dist-upgrade | build-dep) + option="install" + _create_lock + ;; + clean | autoclean) + option="clean" + ;; + download) + option="download" + root=0 + ;; + source) + option="source" + root=0 + ;; + *) + root=0 + ;; + esac +done + +# To handle priority of options correctly (environment over config file vars) +# we need to preserve all interesting env variables. As this wouldn't be +# difficult enough we have to preserve complete env vars (especially if value +# ist set (even empty) or not) when changing context (sudo)... +# Set a 'random' string to all unset variables. +TMP_RANDOM="13979853562951413" +TMP_LCK_FILE="${LCK_FILE-${TMP_RANDOM}}" +TMP_DOWNLOADBEFORE="${DOWNLOADBEFORE-${TMP_RANDOM}}" +TMP__APTMGR="${_APTMGR-${TMP_RANDOM}}" +TMP_APTCACHE="${APTCACHE-${TMP_RANDOM}}" +TMP_DLDIR="${DLDIR-${TMP_RANDOM}}" +TMP_DLLIST="${DLLIST-${TMP_RANDOM}}" +TMP__MAXNUM="${MAXNUM-${TMP_RANDOM}}" +TMP__MAXCONPERSRV="${MAXCONPERSRV-${TMP_RANDOM}}" +TMP__SPLITCON="${SPLITCON-${TMP_RANDOM}}" +TMP__MINSPLITSZ=${MINSPLITSZ-${TMP_RANDOM}} +TMP__PIECEALGO=${PIECEALGO-${TMP_RANDOM}} +TMP_aptfast_prefix="${aptfast_prefix-${TMP_RANDOM}}" +TMP_APT_FAST_TIMEOUT="${APT_FAST_TIMEOUT-${TMP_RANDOM}}" +TMP_APT_FAST_APT_AUTH="${APT_FAST_APT_AUTH-${TMP_RANDOM}}" +TMP_VERBOSE_OUTPUT="${VERBOSE_OUTPUT-${TMP_RANDOM}}" +TMP_ftp_proxy="${ftp_proxy-${TMP_RANDOM}}" +TMP_http_proxy="${http_proxy-${TMP_RANDOM}}" +TMP_https_proxy="${https_proxy-${TMP_RANDOM}}" + +# Check for proper privileges. +# Call explicitly with environment variables to get them into root conext. +if [ "$root" -ne 0 ] && [ "$UID" != 0 ]; then + exec sudo DEBUG="$DEBUG" \ + LCK_FILE="$TMP_LCK_FILE" \ + DOWNLOADBEFORE="$TMP_DOWNLOADBEFORE" \ + _APTMGR="$TMP__APTMGR" \ + APTCACHE="$TMP_APTCACHE" \ + DLDIR="$TMP_DLDIR" \ + DLLIST="$TMP_DLLIST" \ + _MAXNUM="$TMP__MAXNUM" \ + _MAXCONPERSRV="$TMP__MAXCONPERSRV" \ + _SPLITCON="$TMP__SPLITCON" \ + _MINSPLITSZ="$TMP__MINSPLITSZ" \ + _PIECEALGO="$TMP__PIECEALGO" \ + aptfast_prefix="$TMP_aptfast_prefix" \ + APT_FAST_TIMEOUT="$TMP_APT_FAST_TIMEOUT" \ + APT_FAST_APT_AUTH="$TMP_APT_FAST_APT_AUTH" \ + VERBOSE_OUTPUT="$TMP_VERBOSE_OUTPUT" \ + ftp_proxy="$TMP_ftp_proxy" \ + http_proxy="$TMP_http_proxy" \ + https_proxy="$TMP_https_proxy" \ + "$0" "$@" +fi + +# Define lockfile. +# Use /tmp as directory because everybody (not only root) has to have write +# permissions. +# We need lock for non-root commands too, because we only have one download +# list file. +if [ "$IS_ACE_ENV" != "" ];then +LCK_FILE="/tmp/apt-fast-in-container.lock" +else +LCK_FILE="/tmp/apt-fast.lock" +fi + + +# Set default package manager, APT cache, temporary download dir, +# temporary download list file, and maximal parallel downloads +_APTMGR=apt-get +eval "$(apt-config shell APTCACHE Dir::Cache::archives/d)" +# Check if APT config option Dir::Cache::archives::apt-fast-partial is set. +eval "$(apt-config shell apt_fast_partial Dir::Cache::archives::apt-fast-partial/d)" +if [ -z "$apt_fast_partial" ]; then + DLDIR="$(realpath "${APTCACHE}/../apt-fast")" +else + DLDIR="${apt_fast_partial}" +fi + +# Check for apt auth files +eval "$(apt-config shell NETRC Dir::Etc::netrc/f)" +eval "$(apt-config shell NETRCDIR Dir::Etc::netrcparts/d)" +APTAUTHFILES=() +if [ -f "$NETRC" ]; then + APTAUTHFILES=("$NETRC") +fi +APTAUTHFILES+=("$NETRCDIR"*) + +LISTTEMP=$(mktemp -d) +DLLIST="${LISTTEMP}/apt-fast.list" + + + + +_MAXNUM=5 +_MAXCONPERSRV=10 +_SPLITCON=8 +_MINSPLITSZ="1M" +_PIECEALGO="default" +MIRRORS=() + +# Prefix in front of apt-fast output: +aptfast_prefix= +# aptfast_prefix="$(date '+%b %_d %T.%N') apt-fast: " + + + +# Set timout value for apt-fast download confirmation dialog. +# Value is in seconds. +APT_FAST_TIMEOUT=60 + +# Ask for download confirmation if unset +DOWNLOADBEFORE= + +# Enable APT authentication support +APT_FAST_APT_AUTH=1 + +# Formatted package list in download confirmation if unset +VERBOSE_OUTPUT= + +# Download command. +_DOWNLOADER='aria2c --no-conf -c -j ${_MAXNUM} -x ${_MAXCONPERSRV} -s ${_SPLITCON} -i ${DLLIST} --min-split-size=${_MINSPLITSZ} --stream-piece-selector=${_PIECEALGO} --connect-timeout=60 --timeout=600 -m0' + +# 定义默认的配置文件列表(按加载顺序排列) +CONFIG_FILES=( + "/tmp/aptss-conf/apt-fast.conf" # 原始配置文件位置 + "/etc/aptss/apt-fast.conf" # 系统级配置 +) + + +# 按顺序加载所有配置文件 +for conf_file in "${CONFIG_FILES[@]}"; do + if [ -e "$conf_file" ]; then + source "$conf_file" + fi +done + + + +# no proxy as default +ftp_proxy= +http_proxy= +https_proxy= + +# Now overwrite with preserved values if values were set before (compare with +# 'random' string). +[ "$TMP_LCK_FILE" = "$TMP_RANDOM" ] || LCK_FILE="$TMP_LCK_FILE" +[ "$TMP_DOWNLOADBEFORE" = "$TMP_RANDOM" ] || DOWNLOADBEFORE="$TMP_DOWNLOADBEFORE" +[ "$TMP__APTMGR" = "$TMP_RANDOM" ] || _APTMGR="$TMP__APTMGR" +[ "$TMP_APTCACHE" = "$TMP_RANDOM" ] || APTCACHE="$TMP_APTCACHE" +[ "$TMP_DLDIR" = "$TMP_RANDOM" ] || DLDIR="$TMP_DLDIR" +[ "$TMP_DLLIST" = "$TMP_RANDOM" ] || DLLIST="$TMP_DLLIST" +[ "$TMP__MAXNUM" = "$TMP_RANDOM" ] || _MAXNUM="$TMP__MAXNUM" +[ "$TMP__MAXCONPERSRV" = "$TMP_RANDOM" ] || _MAXCONPERSRV="$TMP__MAXCONPERSRV" +[ "$TMP__SPLITCON" = "$TMP_RANDOM" ] || _SPLITCON="$TMP__SPLITCON" +[ "$TMP__MINSPLITSZ" = "$TMP_RANDOM" ] || _MINSPLITSZ="$TMP__MINSPLITSZ" +[ "$TMP__PIECEALGO" = "$TMP_RANDOM" ] || _PIECEALGO="$TMP__PIECEALGO" +[ "$TMP_aptfast_prefix" = "$TMP_RANDOM" ] || aptfast_prefix="$TMP_aptfast_prefix" +[ "$TMP_APT_FAST_TIMEOUT" = "$TMP_RANDOM" ] || APT_FAST_TIMEOUT="$TMP_APT_FAST_TIMEOUT" +[ "$TMP_APT_FAST_APT_AUTH" = "$TMP_RANDOM" ] || APT_FAST_APT_AUTH="$TMP_APT_FAST_APT_AUTH" +[ "$TMP_VERBOSE_OUTPUT" = "$TMP_RANDOM" ] || VERBOSE_OUTPUT="$TMP_VERBOSE_OUTPUT" +[ "$TMP_ftp_proxy" = "$TMP_RANDOM" ] || ftp_proxy="$TMP_ftp_proxy" +[ "$TMP_http_proxy" = "$TMP_RANDOM" ] || http_proxy="$TMP_http_proxy" +[ "$TMP_https_proxy" = "$TMP_RANDOM" ] || https_proxy="$TMP_https_proxy" + + +# Disable colors if not executed in terminal. +if [ ! -t 1 ]; then + cGreen= + cRed= + cBlue= + endColor= + #FIXME: Time not updated. + [ -z "$aptfast_prefix" ] && aptfast_prefix="[apt-fast $(date +"%T")]" +fi + + + + + +# decode url string +# translates %xx but must not convert '+' in spaces +urldecode() +{ + printf '%b' "${1//%/\\x}" +} + +# Check if mirrors are available. And if so add all mirrors to download list. +############ SPARK ADJUST: Now we ignore the first config for business request +get_mirrors(){ + # Check all mirror lists. + for mirrorstr in "${MIRRORS[@]}"; do + # Build mirrors array from comma separated string. + IFS=", " read -r -a mirrors <<< "$mirrorstr" + # Check for all mirrors if URI of $1 is from mirror. If so add all other + # mirrors to (resmirror) list and break all loops. + for mirror in "${mirrors[@]}"; do + # Real expension. + if [[ "$1" == "$mirror"* ]]; then + filepath="${1#"${mirror}"}" + # Build list for aria download list. + list="${mirrors[*]:1}" + echo -e "${list// /${filepath}\\t}$filepath\n" + return 0 + fi + done + done + # No other mirrors found. + echo "$1" +} + +##########SPARK ADJUST: END + +AUTH_INFO_PARSED=() +# Parse apt authentication files. +# Undefined behavior on whitespaces in host, username or password. +prepare_auth(){ + if [ "$APT_FAST_APT_AUTH" -eq 0 ]; then + return + fi + for auth_file in "${APTAUTHFILES[@]}"; do + # auth files have netrc syntax, possible multiline entries starting with "machine" + auth_info="$(tr '\n' ' ' < "$auth_file" | sed 's/\(\\)/\n\1/g' | sed '1d')" + while IFS= read -r auth; do + machine="$(echo "$auth" | sed 's/.*\[ \t]\+\([^ \t]\+\).*/\1/')" + login="$(echo "$auth" | sed 's/.*\[ \t]\+\([^ \t]\+\).*/\1/')" + password="$(echo "$auth" | sed 's/.*\[ \t]\+\([^ \t]\+\).*/\1/')" + # if machine does not have protocol, try https:// + if ! [[ "$machine" =~ ^.*:// ]]; then + machine="https://$machine" + fi + if [ -z "$machine" ] || [ -z "$login" ] || [ -z "$password" ]; then + msg "Could not parse apt authentication (skipping): $auth ($auth_file)" "warning" + continue + fi + # use space separated string to convert back to array later + AUTH_INFO_PARSED+=("$machine $login $password") + done <<< "$auth_info" + done + if [ "${#AUTH_INFO_PARSED[@]}" -eq 0 ]; then + # acts like auth disabled when no auth info is provided to improve performance + APT_FAST_APT_AUTH=0 + fi +} + +# Gets URI as parameter and tries to add basic http credentials. Will fail on +# credentials that contain characters that need URL-encoding. +get_auth(){ + for auth_info in "${AUTH_INFO_PARSED[@]}"; do + # convert to array, don't escape variable here + auth_info_arr=($auth_info) + machine="${auth_info_arr[0]}" + # takes first match + if [[ "$1" == "$machine"* ]]; then + login="${auth_info_arr[1]}" + password="${auth_info_arr[2]}" + uri="$(echo "$1" | sed "s|^\([^:]\+://\)|\1$login:$password@|")" + echo "$uri" + return + fi + done + echo "$1" +} + +# Globals to save package name, version, size and overall size. +DOWNLOAD_DISPLAY= +DOWNLOAD_SIZE=0 + +# 获取包的URI +# Get the package URLs. +get_uris(){ + if [ ! -d "$(dirname "$DLLIST")" ] + then + if ! mkdir -p -- "$(dirname "$DLLIST")" + then + msg "Could not create download file directory." "warning" + msg "无法创建下载目录" "warning" + CLEANUP_STATE=1 + exit + fi + elif [ -f "$DLLIST" ]; then + if ! rm -f -- "$DLLIST" 2>/dev/null && ! touch -- "$DLLIST" 2>/dev/null + then + msg "Unable to write to download file. Try restarting with root permissions or run 'apt-fast clean' first." "warning" + msg "无法下载文件。尝试使用root权限,或者运行 'aptss clean'" "warning" + CLEANUP_STATE=1 + exit + fi + fi + + # Add header to overwrite file. + echo "# apt-fast mirror list: $(date)" > "$DLLIST" + # NOTE: "aptitude" doesn't have this functionality + # so we use "${_APTMGR}" to get package URI's + case "$(basename "${_APTMGR}")" in + 'apt'|'apt-get') uri_mgr="${_APTMGR}";; + *) uri_mgr='apt-get';; + esac + uris_full="$("$uri_mgr" "${APT_SCRIPT_WARNING[@]}" -y --print-uris "$@")" + CLEANUP_STATE="$?" + if [ "$CLEANUP_STATE" -ne 0 ] + then + msg "Package manager quit with exit code. Here is the log" "warning" + msg "包管理器以错误代码退出.日志如下" "warning" + msg "${uris_full}" + exit "$CLEANUP_STATE" + fi + prepare_auth + local tmpdir + tmpdir=$(mktemp -d) || { + msg "Failed to create tmp dir" "warning" + msg "无法创建临时目录" "warning" + exit 1 + + + } + +cleanup_tmpdir() { + if [ -n "$tmpdir" ] && [ -d "$tmpdir" ]; then + rm -rf "$tmpdir" + fi +} +trap cleanup_tmpdir EXIT + + ## --print-uris format is: + # 'fileurl' filename filesize checksum_hint:filechecksum + # 修改:process_package函数增加第二个参数表示当前线程的临时输出文件 + process_package() { + local pkg_uri_info="$1" + local thread_file="$2" + local display_line="" # 初始化显示信息为空 + IFS=' ' read -r uri filename filesize checksum_string _ <<<"$pkg_uri_info" + [ -z "$uri" ] && return + uri="${uri//"'"/}" + [ "$APT_FAST_APT_AUTH" -ne 0 ] && uri="$(get_auth "$uri")" + IFS=':' read -r hash_algo checksum _ <<<"$checksum_string" + + if [[ "$filename" == *%* ]]; then + # decode url string + filename_decoded="$(printf '%b' "${filename//%/\\x}")" + else + filename_decoded="$filename" + fi + IFS='_' read -r pkg_name_decoded pkg_version_decoded _ <<<"$filename_decoded" + + display_line+="$pkg_name_decoded $pkg_version_decoded $filesize\n" + if [ -n "$HASH_SUPPORTED" ]; then + case "$hash_algo" in + SHA512) [ -z "$SHA512_SUPPORTED" ] && hash_algo= || hash_algo=sha-512 ;; + SHA256) [ -z "$SHA256_SUPPORTED" ] && hash_algo= || hash_algo=sha-256 ;; + SHA1) [ -z "$SHA1_SUPPORTED" ] && hash_algo= || hash_algo=sha-1 ;; + MD5Sum) [ -z "$MD5sum_SUPPORTED" ] && hash_algo= || hash_algo=md5 ;; + *) hash_algo= + esac + + + # Using apt-cache show package=version to ensure recover single and + # correct package version. + # Warning: assuming that package naming uses '_' as field separator. + # Therefore, this code expects package-name_version_arch.deb Otherwise + # below code will fail resoundingly + if [ -z "$hash_algo" ]; then + IFS='_' read -r pkg_name _ <<<"$filename" + pkg_version="$pkg_version_decoded" + # Transform multi-line field output from apt-cache to single line and sort checksums, strongest first + package_info="$(apt-cache show "$pkg_name=$pkg_version" | sed ':r;$!{N;br};s/\n / /g' | sort -r)" + + while IFS=': ' read -r field checksum _ + do + case "$field" in + SHA512) + [ -n "$SHA512_SUPPORTED" ] || continue + hash_algo="sha-512" + break ;; + SHA256) + [ -n "$SHA256_SUPPORTED" ] || continue + hash_algo="sha-256" + break ;; + SHA1) + [ -n "$SHA1_SUPPORTED" ] || continue + hash_algo="sha-1" + break ;; + MD5sum) + [ -n "$MD5sum_SUPPORTED" ] || continue + hash_algo="md5" + break ;; + esac + done <<<"$package_info" + + if [ -z "$hash_algo" ]; then + checksum= + msg "Couldn't get supported checksum for $pkg_name ($pkg_version)." "warning" + msg "无法获得 $pkg_name ($pkg_version) 版本受到支持的散列验证值" "warning" + REMOVE_WORKING_MESSAGE= + fi + fi + else + hash_algo= + fi + + # 原来利用文件锁写入,现在改为写入当前线程的临时文件 + { + get_mirrors "$uri" + [ -n "$hash_algo" ] && echo " checksum=$hash_algo=$checksum" + echo " out=$filename" + } >> "$thread_file" + + echo -e "$display_line" >> "$tmpdir/display" + echo "$filesize" >> "$tmpdir/sizes" + } + + # 主并行处理逻辑 + mapfile -t pkg_uri_list < <(echo "$uris_full" | grep -E "^'(http(s|)|(s|)ftp)://") + total_pkgs=${#pkg_uri_list[@]} + threads=${THREADS:-4} # 默认4线程 + per_thread=$(( (total_pkgs + threads - 1) / threads )) # 向上取整 + + # 分配任务到不同线程,每个线程使用自己的临时文件 + for ((i=0; i "$thread_file" # 清空或创建临时文件 + start=$((i * per_thread)) + end=$((start + per_thread -1)) + [ $end -ge $total_pkgs ] && end=$((total_pkgs -1)) + + # 启动后台线程处理任务块 + ( + for ((j=start; j<=end; j++)); do + [ -z "${pkg_uri_list[j]}" ] && continue + process_package "${pkg_uri_list[j]}" "$thread_file" + done + ) & + done + + # 等待所有后台任务完成 + wait + + # 合并所有线程的临时文件到最终的 $DLLIST 中(保留之前添加的 header) + for ((i=0; i> "$DLLIST" + rm -f "$thread_file" + fi + done + # 合并显示信息 + if [ -f "$tmpdir/display" ]; then + DOWNLOAD_DISPLAY+="\n$(cat "$tmpdir/display")" + fi + + # 计算总下载大小 + if [ -f "$tmpdir/sizes" ]; then + DOWNLOAD_SIZE=$(awk '{sum+=$1} END{print sum}' "$tmpdir/sizes") + fi + + # 清理临时目录 + rm -rf "$tmpdir" +} + + + +display_downloadfile(){ + if [ -n "$VERBOSE_OUTPUT" ]; then + cat "$DLLIST" + else + DISPLAY_SORT_OPTIONS=(-k 1,1) + # Sort output after package download size (decreasing): + #DISPLAY_SORT_OPTIONS=(-k 3,3 -hr) + while IFS=' ' read -r pkg ver size _; do + [ -z "$pkg" ] && continue + printf '%s%-40s %-20s %10s\n' "$aptfast_prefix" "$pkg" "$ver" "$size" + done <<<"$(echo -e "$DOWNLOAD_DISPLAY" | sort "${DISPLAY_SORT_OPTIONS[@]}" | numfmt --to=iec-i --suffix=B --field=3)" + fi + msg "Download size: $(echo "$DOWNLOAD_SIZE" | numfmt --to=iec-i --suffix=B)" "normal" + msg "下载大小: $(echo "$DOWNLOAD_SIZE" | numfmt --to=iec-i --suffix=B)" "normal" +} + + +# Create and insert a PID number to lockfile. + + + +# Make sure aria2c (in general first parameter from _DOWNLOADER) is available. +CMD="$(echo "$_DOWNLOADER" | sed 's/^\s*\([^ ]\+\).*$/\1/')" +if [ ! "$(command -v "$CMD")" ]; then + msg "Command not found: $CMD" "normal" "err" + msg "You must configure $CONFFILE to use aria2c or another supported download manager" "normal" "err" + CLEANUP_STATE=1 + exit +fi + +# Make sure package manager is available. +if [ ! "$(command -v "$_APTMGR")" ]; then + msg "\`$_APTMGR\` command not available." "warning" + msg "You must configure $CONFFILE to use either apt-get or aptitude." "normal" "err" + CLEANUP_STATE=1 + exit +fi + +# Disable script warning if apt is used. +APT_SCRIPT_WARNING=() +if [ "$(basename "${_APTMGR}")" == 'apt' ]; then + APT_SCRIPT_WARNING=(-o "Apt::Cmd::Disable-Script-Warning=true") +fi + +# Set supported hash algorithms by aria2c (and also by Debian repository). +SHA512_SUPPORTED= +SHA256_SUPPORTED= +SHA1_SUPPORTED= +MD5sum_SUPPORTED= +HASH_SUPPORTED= +if [ "$CMD" == "aria2c" ]; then + for supported_hash in $(LC_ALL=C aria2c -v | sed '/^Hash Algorithms:/!d; s/\(^Hash Algorithms: \|,\)\+//g'); do + case "$supported_hash" in + sha-512) SHA512_SUPPORTED=y; HASH_SUPPORTED=y ;; + sha-256) SHA256_SUPPORTED=y; HASH_SUPPORTED=y ;; + sha-1) SHA1_SUPPORTED=y; HASH_SUPPORTED=y ;; + md5) MD5sum_SUPPORTED=y; HASH_SUPPORTED=y ;; + esac + done + if [ -z "$HASH_SUPPORTED" ]; then + msg "Couldn't find supported checksum algorithm from aria2c. Checksums disabled." "warning" + msg "无法找到aria2c支持的散列验证算法. 散列验证已被禁用." "warning" + fi +fi + +# Check if "assume yes" switch is enabled and if yes enable $DOWNLOADBEFORE. +# Also check if "download only" switch is enabled. +#TODO: Get real value over APT items APT::Get::Assume-Yes and +# APT::Get::Assume-No . +# Respectively Aptitude::CmdLine::Download-Only and APT::Get::Download-Only. +DOWNLOAD_ONLY= +while true; do + while getopts ":dy-:" optchar; do + case "${optchar}" in + -) + case "${OPTARG}" in + yes | assume-yes) DOWNLOADBEFORE=true ;; + assume-no) DOWNLOADBEFORE= ;; + download-only) DOWNLOAD_ONLY=true ;; + esac + ;; + y) + DOWNLOADBEFORE=true + ;; + d) + DOWNLOAD_ONLY=true + ;; + *) + ;; + esac + done + ((OPTIND++)) + [ $OPTIND -gt $# ] && break +done + +# Configure proxies. Use apt values over environment variables. +# Note: If proxy setting is not set, there is no apt-config output. +# Therefore variable doesn't get overriden, which is intended. +# Export the variables to make them available in subshells (aka the +# downloader command). +eval "$(apt-config shell ftp_proxy Acquire::ftp::proxy)" +export ftp_proxy +eval "$(apt-config shell http_proxy Acquire::http::proxy)" +export http_proxy +eval "$(apt-config shell https_proxy Acquire::https::proxy)" +export https_proxy + +# aria2 has no socks support (see https://github.com/aria2/aria2/issues/153) +if echo "$http_proxy" | grep -q "^socks5h://" || echo "$https_proxy" | grep -q "^socks5h://"; then + msg "Socks proxy detected. Falling back to ${_APTMGR}" "hint" + "${_APTMGR}" "${APT_SCRIPT_WARNING[@]}" "$@" + exit 0 +fi + +# Run actions. +if [ "$option" == "install" ]; then + msg + msg "Working... this may take a while." "normal" + msg "正在工作中,请稍等" "normal" + REMOVE_WORKING_MESSAGE=y + + get_uris "$@" + [ -t 1 ] && [ -n "$REMOVE_WORKING_MESSAGE" ] && tput cuu 1 && tput el && tput cuu 1 + # Test /tmp/apt-fast.list file exists and not just the apt-fast comment line. + # Then download all files from the list. + if [ -f "$DLLIST" ] && [ "$(wc -l "$DLLIST" | cut -d' ' -f1)" -gt 1 ] && [ ! "$DOWNLOADBEFORE" ]; then + display_downloadfile + msg + msg "Do you want to download the packages? [Y/n] " "question" + + while ((!updsys)); do + read -r -sn1 -t "$APT_FAST_TIMEOUT" answer || { msg; msg "Timed out." "warning"; CLEANUP_STATE=1; exit; } + case "$answer" in + [JjYy]) result=1; updsys=1 ;; + [Nn]) result=0; updsys=1 ;; + "") result=1; updsys=1 ;; + *) updsys=0 ;; + esac + done + else + result=1 + fi + + if ((DOWNLOAD_SIZE)); then + msg + # Continue if answer was right or DOWNLOADBEFORE is enabled. + if ((result)); then + if [ -s "$DLLIST" ]; then + # Test if apt-fast directory is present where we put packages. + if [ ! -d "$DLDIR" ]; then + mkdir -p -- "$DLDIR" + fi + + cd "$DLDIR" &>/dev/null || { msg; msg "Not able to change into download directory." "warning"; CLEANUP_STATE=1; exit; } + + eval "${_DOWNLOADER}" # execute downloadhelper command + if [ "$(find "$DLDIR" -printf . | wc -c)" -gt 1 ]; then + + # Delete incomplete/corrupted downloaded files, if any: Not recursive, as we don't expect any dirs to exist within $DLDIR. + + # When Aria2c downloads a file and detects it is corrupted, its filename won't be renamed back to its actual name, + # preserving .aria2 file extension, which also indicates when a file hasn't been completely downloaded. + for x in *.aria2; do + rm -f "$x" "${x%.aria2}" + done + + # Move all packages to the apt install directory by force to ensure + # already existing debs which may be incomplete are replaced + find . -type f \( -name '*.deb' -o -name '*.ddeb' \) -execdir mv -ft "$APTCACHE" {} \+ + fi + cd - &>/dev/null || msg "Failed to change back directory" "warning" + fi + else + CLEANUP_STATE=1 + exit + fi + else + [ -t 1 ] && tput el + fi + + # different problem resolving for aptitude + if [ -z "$DOWNLOAD_ONLY" ] || [ "$(basename "${_APTMGR}")" == 'aptitude' ]; then + "${_APTMGR}" "${APT_SCRIPT_WARNING[@]}" "$@" + fi + + +elif [ "$option" == "clean" ]; then + "${_APTMGR}" "${APT_SCRIPT_WARNING[@]}" "$@" && { + if [ -d "$DLDIR" ]; then + find "$DLDIR" -maxdepth 1 -type f -delete + CLEANUP_STATE="$?" + [ -f "$DLLIST" ] && rm -f -- "$DLLIST"* || true + fi + } + +elif [ "$option" == "download" ]; then + msg + msg "Working... this may take a while." "normal" + msg "正在工作中,请稍等" "normal" + REMOVE_WORKING_MESSAGE=y + + get_uris "$@" + + [ -t 1 ] && [ -n "$REMOVE_WORKING_MESSAGE" ] && tput cuu 1 && tput el && tput cuu 1 + + if [ -f "$DLLIST" ] && [ "$(wc -l "$DLLIST" | cut -d' ' -f1)" -gt 1 ]; then + display_downloadfile + eval "${_DOWNLOADER}" + fi + + # different problem resolving for aptitude + if [ "$(basename "${_APTMGR}")" == 'aptitude' ]; then + "${_APTMGR}" "$@" + fi + + # Clean up temporary directory for download command + cleanup_aptfast + +elif [ "$option" == "source" ]; then + msg + msg "Working... this may take a while." "normal" + msg "正在工作中,请稍等" "normal" + REMOVE_WORKING_MESSAGE=y + + get_uris "$@" + + [ -t 1 ] && [ -n "$REMOVE_WORKING_MESSAGE" ] && tput cuu 1 && tput el && tput cuu 1 + + if [ -f "$DLLIST" ] && [ "$(wc -l "$DLLIST" | cut -d' ' -f1)" -gt 1 ]; then + display_downloadfile + eval "${_DOWNLOADER}" + fi + # We use APT manager here to provide more verbose output. This method is + # slightly slower then extractiong packages manually after download but also + # more hardened (e.g. some options like --compile are available). + "${_APTMGR}" "${APT_SCRIPT_WARNING[@]}" "$@" + # Uncomment following snippet to extract source directly and comment + # both lines before. + #while read srcfile; do + # # extract only .dsc files + # echo "$srcfile" | grep -q '\.dsc$' || continue + # dpkg-source -x "$(basename "$srcfile")" + #done < "$DLLIST" + + # Clean up temporary directory for source command + cleanup_aptfast + +# Execute package manager directly if unknown options are passed. +else + "${_APTMGR}" "${APT_SCRIPT_WARNING[@]}" "$@" +fi + +# After error or all done remove our lockfile (done with EXIT trap) + diff --git a/tool/aptss b/tool/aptss new file mode 100755 index 00000000..69d78c9c --- /dev/null +++ b/tool/aptss @@ -0,0 +1,160 @@ +#!/bin/bash + +SPARK_DOWNLOAD_SERVER_URL="https://d.spark-app.store/" +SPARK_DOWNLOAD_SERVER_URL_NO_PROTOCOL="d.spark-app.store" + +if [[ "$IS_APM_ENV" = "" ]] ;then +UPSTREAM_CATOGARY="sparkstore" +else +UPSTREAM_CATOGARY="apm" +fi + +source /opt/durapps/spark-store/bin/bashimport/transhell.amber +source /opt/durapps/spark-store/bin/bashimport/log.amber +load_transhell + +if [[ "$IS_APM_ENV" = "" ]] ;then +case $(arch) in + x86_64 | i686 | i386) + STORE_URL="store" + STORE_LIST_URL="" + ;; + aarch64) + STORE_URL="aarch64-store" + STORE_LIST_URL="-aarch64" + ;; + loongarch64) + STORE_URL="loong64-store" + STORE_LIST_URL="-loong64" + ;; + riscv64) + STORE_URL="riscv64-store" + STORE_LIST_URL="-riscv64" + ;; +esac +else +case $(arch) in + x86_64 | i686 | i386) + STORE_URL="amd64-apm" + STORE_LIST_URL="-amd64" + ;; + aarch64) + STORE_URL="arm64-apm" + STORE_LIST_URL="-arm64" + ;; + loongarch64) + STORE_URL="loong64-apm" + STORE_LIST_URL="-loong64" + ;; + riscv64) + STORE_URL="riscv64-apm" + STORE_LIST_URL="-riscv64" + ;; +esac +fi + + +SS_APT_FAST="/opt/durapps/spark-store/bin/apt-fast/ss-apt-fast" + + +is_empty_dir(){ + return `ls -A $1|wc -w` +} + +function update_list(){ +curl --progress-bar -o /opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list "${SPARK_DOWNLOAD_SERVER_URL}/${UPSTREAM_CATOGARY}${STORE_LIST_URL}.list" +log.info "${UPSTREAM_CATOGARY}${STORE_LIST_URL}.list update done" +} + +function update_conf(){ +mkdir -p /tmp/aptss-conf/ +curl --progress-bar -o /tmp/aptss-conf/apt-fast.conf "${SPARK_DOWNLOAD_SERVER_URL}/apt-fast.conf" +log.info "apt-fast.conf update done" +chmod -R 755 /tmp/aptss-conf +} + +if [ "$(id -u)" != "0" ];then +#############################无root权限时 +echo -e "\e[1;32m${TRANSHELL_CONTENT_RUNNING_IN_NOT_ROOT_USER}\e[0m" + +else + + +ln -sf /etc/apt/sources.list.d/* /opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d +###让这里和系统同步,先链接,然后清除无效链接 +find /opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d -xtype l -delete + +fi + +if [ ! -e "/tmp/aptss-conf/apt-fast.conf" ];then +###刷新apt-fast配置 +mkdir -p /tmp/aptss-conf/ +echo -e "\e[1;32m${TRANSHELL_CONTENT_GETTING_SERVER_CONFIG_AND_MIRROR_LIST}\e[0m" +echo +update_conf + +fi + + +if [ ! -e "/var/lib/aptss/lists/${SPARK_DOWNLOAD_SERVER_URL_NO_PROTOCOL}_${STORE_URL}_Packages" ] && [ ! -e "/var/lib/aptss/lists/d.store.deepinos.org.cn_${STORE_URL}_Packages" ] && [ ! -e "/var/lib/aptss/lists/mirrors.sdu.edu.cn_spark-store-repository_${STORE_URL}_Packages" ];then + +mkdir -p /tmp/aptss-conf/ +echo -e "\e[1;32m${TRANSHELL_CONTENT_GETTING_SERVER_CONFIG_AND_MIRROR_LIST}\e[0m" +echo + +update_list +update_conf + +#只更新星火源 + + +fi + + + + + +if [ "$1" = "install" ] || [ "$1" = "upgrade" ] || [ "$1" = "full-upgrade" ] || [ "$1" = "reinstall" ] || [ "$1" = "dist-upgrade" ]; then + + + +###执行 + +${SS_APT_FAST} "$@" --allow-downgrades -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf +ret="$?" +if [ "$ret" -ne 0 ];then +echo -e "\e[1;33m$TRANSHELL_CONTENT_PLEASE_USE_APTSS_INSTEAD_OF_APT\e[0m" +exit $ret +fi + + + + +elif [ "$1" = "ssupdate" ];then + +mkdir -p /tmp/aptss-conf/ +echo -e "\e[1;32m${TRANSHELL_CONTENT_GETTING_SERVER_CONFIG_AND_MIRROR_LIST}\e[0m" +echo + + +update_list +update_conf + +/usr/bin/apt update -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0" -o Dir::Etc::sourcelist="/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list" + +#只更新星火源 + +elif [ "$1" = "update" ];then + +echo -e "\e[1;32m${TRANSHELL_CONTENT_GETTING_SERVER_CONFIG_AND_MIRROR_LIST}\e[0m" +echo +update_list +update_conf +### 额外一份拿来给aptss自动补全用 + + ${SS_APT_FAST} "$@" -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf + +else + ${SS_APT_FAST} "$@" -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf +fi + diff --git a/tool/bashimport/log.amber b/tool/bashimport/log.amber new file mode 100644 index 00000000..fe97bef3 --- /dev/null +++ b/tool/bashimport/log.amber @@ -0,0 +1,5 @@ +#!/bin/bash +log.warn() { echo -e "[\e[33mWARN\e[0m]: \e[1m$*\e[0m"; } +log.error() { echo -e "[\e[31mERROR\e[0m]: \e[1m$*\e[0m"; } +log.info() { echo -e "[\e[96mINFO\e[0m]: \e[1m$*\e[0m"; } +log.debug() { echo -e "[\e[32mDEBUG\e[0m]: \e[1m$*\e[0m"; } diff --git a/tool/bashimport/transhell.amber b/tool/bashimport/transhell.amber new file mode 100755 index 00000000..73b8d90a --- /dev/null +++ b/tool/bashimport/transhell.amber @@ -0,0 +1,31 @@ +#!/bin/bash + +##load transhell +function load_transhell_debug() +{ +local WORK_PATH="$(cd "$(dirname "${0}")" && pwd)" +local CURRENT_LANG="$(echo ${LANG%.*})" +if [ -e "/usr/share/$(basename $0)/transhell/$(basename $0)_en_US.transhell" ]; then source /usr/share/$(basename $0)/transhell/$(basename $0)_en_US.transhell; echo "Loading transhell from /usr/share/$(basename $0)/transhell/$(basename $0)_en_US.transhell ..."; fi +if [ -e "/usr/share/$(basename $0)/transhell/$(basename $0)_$CURRENT_LANG.transhell" ]; then source /usr/share/$(basename $0)/transhell/$(basename $0)_$CURRENT_LANG.transhell; echo "Loading transhell from /usr/share/$(basename $0)/transhell/$(basename $0)_$CURRENT_LANG.transhell ..."; fi +if [ -e "${WORK_PATH}/transhell/$(basename $0)_en_US.transhell" ]; then source ${WORK_PATH}/transhell/$(basename $0)_en_US.transhell; echo "Loading transhell from ${WORK_PATH}/transhell/$(basename $0)_en_US.transhell ..."; fi +if [ -e "${WORK_PATH}/transhell/$(basename $0)_$CURRENT_LANG.transhell" ]; then source ${WORK_PATH}/transhell/$(basename $0)_$CURRENT_LANG.transhell; echo "Loading transhell from ${WORK_PATH}/transhell/$(basename $0)_$CURRENT_LANG.transhell ..."; fi + +echo "-----------------------------------------------------------------------------" +} + +function load_transhell() +{ +local WORK_PATH="$(cd "$(dirname "${0}")" && pwd)" +local CURRENT_LANG="$(echo ${LANG%.*})" +if [ -e "/usr/share/$(basename $0)/transhell/$(basename $0)_en_US.transhell" ]; then source /usr/share/$(basename $0)/transhell/$(basename $0)_en_US.transhell; fi +if [ -e "/usr/share/$(basename $0)/transhell/$(basename $0)_$CURRENT_LANG.transhell" ]; then source /usr/share/$(basename $0)/transhell/$(basename $0)_$CURRENT_LANG.transhell; fi +if [ -e "${WORK_PATH}/transhell/$(basename $0)_en_US.transhell" ]; then source ${WORK_PATH}/transhell/$(basename $0)_en_US.transhell; fi +if [ -e "${WORK_PATH}/transhell/$(basename $0)_$CURRENT_LANG.transhell" ]; then source ${WORK_PATH}/transhell/$(basename $0)_$CURRENT_LANG.transhell; fi + +} + +function update_transhell() +{ +load_transhell $@ +} + diff --git a/tool/open-in-terminal/open-in-terminal b/tool/open-in-terminal/open-in-terminal new file mode 100755 index 00000000..fda426d3 --- /dev/null +++ b/tool/open-in-terminal/open-in-terminal @@ -0,0 +1,33 @@ +#!/bin/bash +source /opt/durapps/spark-store/bin/bashimport/transhell.amber +load_transhell +# 检查是否传入了路径参数 +if [ -z "$1" ]; then + echo "${TRANSHELL_CONTENT_PLEASE_PROVIDE_FILE_PATH}" + exit 1 +fi + + DESKTOP_FILE_PATH=$1 + + if [[ $DESKTOP_FILE_PATH == file://* ]]; then + # 如果是,移除 'file://' 部分并输出结果 + DESKTOP_FILE_PATH="${DESKTOP_FILE_PATH#file://}" + fi + + # 获取文件内容中第一个 Exec= 后的命令 + exec_command=$(grep -m 1 -oP "(?<=Exec=).*" "$DESKTOP_FILE_PATH") + + # 删除 exec_command 中最后的 % 及其后面的内容 + exec_command="${exec_command%\%*}" + + # 打印提取的命令 + echo "$exec_command" + + # 在默认终端执行命令 + eval "$exec_command" + +echo -------------------------------------- +echo "${TRANSHELL_CONTENT_ABOVE_IS_TERMINAL_OUTPUT}" +echo "${TRANSHELL_CONTENT_PRESS_ENTER_TO_FINISH}" +read + diff --git a/tool/open-in-terminal/transhell/open-in-terminal_en_US.transhell b/tool/open-in-terminal/transhell/open-in-terminal_en_US.transhell new file mode 100644 index 00000000..0950339f --- /dev/null +++ b/tool/open-in-terminal/transhell/open-in-terminal_en_US.transhell @@ -0,0 +1,5 @@ +#!/bin/bash +TRANSHELL_CONTENT_PLEASE_PROVIDE_FILE_PATH="Please provide a file path as an argument" +TRANSHELL_CONTENT_ABOVE_IS_TERMINAL_OUTPUT="The above is the output executed in the terminal. Please copy and paste it when providing feedback." +TRANSHELL_CONTENT_PRESS_ENTER_TO_FINISH="Press Enter to finish" + diff --git a/tool/open-in-terminal/transhell/open-in-terminal_zh_CN.transhell b/tool/open-in-terminal/transhell/open-in-terminal_zh_CN.transhell new file mode 100644 index 00000000..e1682c26 --- /dev/null +++ b/tool/open-in-terminal/transhell/open-in-terminal_zh_CN.transhell @@ -0,0 +1,5 @@ +#!/bin/bash +TRANSHELL_CONTENT_PLEASE_PROVIDE_FILE_PATH="请传入文件路径作为参数" +TRANSHELL_CONTENT_ABOVE_IS_TERMINAL_OUTPUT="以上是在终端中执行的输出,请在反馈问题的时候完整复制并贴上" +TRANSHELL_CONTENT_PRESS_ENTER_TO_FINISH="按回车结束" + diff --git a/tool/spark-dstore-patch b/tool/spark-dstore-patch new file mode 100755 index 00000000..9bff3dfb --- /dev/null +++ b/tool/spark-dstore-patch @@ -0,0 +1,147 @@ +#!/bin/bash + + + + + +enumAppInfoList() { + appInfoList=() + apps="/opt/apps" + list=$(ls $apps 2>/dev/null) + for appID in $list; do + appInfoList+=("$appID") + done + echo "${appInfoList[@]}" +} +linkDir() { + ensureTargetDir() { + targetFile=$1 + t=$(dirname "$targetFile") + mkdir -p "$t" + } + + source=$1 + target=$2 + sourceDir=$(dirname "$source") + targetDir=$(dirname "$target") + find "$source" -type f | while read sourceFile; do + targetFile="$targetDir/${sourceFile#$sourceDir/}" + + + ensureTargetDir "$targetFile" + sourceFile=$(realpath --relative-to="$(dirname $targetFile)" "$sourceFile" ) + if [ ! -e "${targetFile}" ];then + ln -sv "$sourceFile" "$targetFile" + fi + done +} + + +linkApp() { + appID=$1 + appEntriesDir="/opt/apps/$appID/entries" + appLibsDir="/opt/apps/$appID/files/lib" + autoStartDir="$appEntriesDir/autostart" + + if [ -d "$autoStartDir" ]; then + linkDir "$autoStartDir" "/etc/xdg/autostart" + fi + + # link application + sysShareDir="/usr/share" + for folder in "$appEntriesDir/applications" "$appEntriesDir/icons" "$appEntriesDir/mime" "$appEntriesDir/glib-2.0" "$appEntriesDir/services" "$appEntriesDir/GConf" "$appEntriesDir/help" "$appEntriesDir/locale" "$appEntriesDir/fcitx"; do + if [ ! -d "$folder" ]; then + continue + fi + if [ "$folder" = "$appEntriesDir/polkit" ]; then + linkDir "$folder" "/usr/share/polkit-1" + elif [ "$folder" = "$appEntriesDir/fonts/conf" ]; then + linkDir "$folder" "/etc/fonts/conf.d" + else + linkDir "$folder" "$sysShareDir/${folder##*/}" + fi + done +} + +function exec_uos_package_link(){ + +for app in $(enumAppInfoList); do + linkApp "$app" & + +done +wait +} + +function exec_v23_icon_link(){ +# Fix v23 broken icon +if [ ! -d "/usr/share/icons/hicolor/scalable/apps" ];then +mkdir -p /usr/share/icons/hicolor/scalable/apps +fi + +for icon_root_icon_path in $(ls /usr/share/icons/*.png /usr/share/icons/*.svg 2>/dev/null) +do +target_icon_path=/usr/share/icons/hicolor/scalable/apps/$(basename ${icon_root_icon_path}) +if [ ! -e ${target_icon_path} ];then +ln -sv $(realpath --relative-to=/usr/share/icons/hicolor/scalable/apps ${icon_root_icon_path}) /usr/share/icons/hicolor/scalable/apps +fi +done +} + +function exec_link_clean(){ + # remove broken links in /usr/share + + find /usr/share/applications -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/icons -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/mime/packages -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/glib-2.0 -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/dbus-1/services -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/fcitx -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/help -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/locale -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + + # 根据 uname -m 确定 multiarch 目录名 + arch=$(uname -m) + case $arch in + x86_64) + multiarch="x86_64-linux-gnu" + ;; + aarch64) + multiarch="aarch64-linux-gnu" + ;; + loongarch64|loong64) + multiarch="loongarch64-linux-gnu" + ;; + *) + multiarch="" + ;; + esac + + if [ -n "$multiarch" ]; then + find "/usr/lib/$multiarch/fcitx" -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + fi + + find /usr/lib/mozilla/plugins -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/polkit-1/actions -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /usr/share/fonts -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & + find /etc/fonts/conf.d -xtype l -exec echo '{} is invalid now and going to be cleaned' \; -exec unlink {} \; 2>/dev/null & +} +function exec_uos_package_update(){ + update-icon-caches /usr/share/icons/* > /dev/null 2>&1 & + update-desktop-database -q > /dev/null 2>&1 & + update-mime-database -V /usr/share/mime > /dev/null 2>&1 & + glib-compile-schemas /usr/share/glib-2.0/schemas/ > /dev/null 2>&1 & + +} + +######################################################################################### +echo "----------------Running Spark DStore Patch----------------" +if [ ! -e /usr/bin/deepin-app-store-tool ];then +# execute linkApp function for each app and print output +exec_uos_package_link + +fi +#exec_v23_icon_link +exec_link_clean +wait +exec_uos_package_update +echo "----------------Finished----------------" diff --git a/tool/spark-store.asc b/tool/spark-store.asc new file mode 100644 index 00000000..9333af9d --- /dev/null +++ b/tool/spark-store.asc @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGNBF7sGtgBDADKux63RQqGjbilEBErDjbGH+/sya9VjNBZdge1G/kK+8SEU7x9 +QFkSoprS7MN9qEtLhdN4+jqKDwwwlB0kjOK/L3BTsSjeP1fonY+Foprnc5sBBNDq +2g4SQr1joafJq/d/E1GzCFCtUeo1/g8siEB9O2A8LFAqKB0ti6cXFQBc7QrRKNqb +mUQYYkva5TeyYXwg8dV/jlQ1HkRftHO+mDOlxhSZxjH8o/3cHpVB/Ef7LUbUfzTL +jT4Lxu5k6jFYeNI9EmIl36Nfz6o4T+iG19PQjv0d9aZe+4ceFeRQNPPqeubGJO9Z +STNhHBFisgr/NdCKDVimR9wR7NSDceO+NswgMZzzo2xIFCsTB+JrMpTkDEBF1eFC +F2RHwi6T4vJmFdt1rHhBfufgHrGNekZytgZw6tL9WDvDCiCKKZSGetfuBfaNYy63 +QNVszRVT5IOf6Rg2vtBIWM/iiAI6E9RsNhElRQj/cQLriIzuwHfgdHx8gPsRSgVx +ZgizW0/2u4ZkrHUAEQEAAbQiRENTdG9yZSA8amlmZW5nc2hlbm1vQG91dGxvb2su +Y29tPokBzgQTAQoAOAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgBYhBJ2aqFn3 +UCSxoezhbg5B01SimkQMBQJirsYKAAoJEA5B01SimkQMCx0L/2OvTYmOr4y4wC7i +oC/uCZpWt9eCMEkC1kB2a9xjPX2GbxTnzvrdkiqHDD8uR2gfO7NkHyoGies+zeCT +LcHH1Li+8KnGy3wye1KWgGTpxS3OV1gHawXi2w6OVhoQvod0y3cbGAOtOWpnbg1n +SiJdDy3cjC+BNYSNPuF3qoY6YEdIfE9SaXANxe/57TUbN0TaiQFYdRd7GyyevjtC +KNW8R06QKQ/zNqJSaoMHVVtDICXDCR4yvzmqXJppfMJKwHW9sPLC2c1xVx3pmyXc +yFzIPOyeu3CDvdbXlh1gfoMTnUfWQyB7oIZxmCfFJGdodZwoxA+pkAcyhx87JYpE +L4gy3SERvAog+/dD47gCb5alGYvyk9t7PQAAvwY8yr/6gf7f1U7DzxuT386LefW7 +6p5ET/R7xcuNLwRH0ZOp/eQECj72A7KXhQ5IL47Rfdh7VzCkf0MGKBFEIET9OV0G +zv1q/z281pt08wHPGM3CetPWUFWUD9/H/UvBUSmpoSLgBsMhdbkBjQRe7BrYAQwA +mAKDNHieo2P1WGNBMi4pPuhhgv8JyBzk8yrSOU+8s1ZTI4mI82iBEy5zAnAx3W1k +unXVlDyq1/LfzL2Nt8Apr5aQdyEqSu4zN/6JBETB0LIkdrwdwBciAHzAKPfJWCR8 +t+Ox76I2MNeVsVQFAjGeb/7QR1Ge6Sx/sgSG7NTWYD6PmQtqmH0xKJsfXYfgayRG +RF1rfu6CV0b2rPFfXOwB+3qQ8YInrPlI/9dswZiVElGGmbQTo3fGqk3T5iShqSnZ +wCYDj2ODDknoPrfE1uUkF7CoYEkGrPbrUMwFK/SHvvG6cUz0EFUENPg7nECPmHGm +GPWByBx/Yo0Jg68JavIeX7q9mnnlTP/3sp1JFLAQpR8q4S9lFOv6uYKJNUxQeBF+ +lBUkiafHzeHxJNP3ymDkrRRi640TubEZfVGjp5cskLY+U6KIpAXK/kCp42uPY7ob +cuc3vAZ+5EcYCOY+LI80urQ5a+iMqo2ZTxL7C0BAX79QLgTDmH/FW4ejkSbrXH8v +ABEBAAGJAbYEGAEKACACGwwWIQSdmqhZ91AksaHs4W4OQdNUoppEDAUCYq7GEwAK +CRAOQdNUoppEDOm8C/9w3/Qtd14531O+ZsrQkfQ+ByIvGFKrnz4BIqD/99lR7UXj +3Z2/bN7IGbwNUrBpgFqzlWAzpX9tiGhnwDphwSVeYNsvwepKmtmMAaPkP+ujR95E +62UKpdVVrHH/VOCT4ZsSddwEVOLeI9LltO6RmPr54e3bpBXv6bijGnjhgRyJU2Jg +DVE+UOU3m26fTQZZf3G9W55TBNdtpA1gggppJ7SgbwmuWcFjeF1gaEOeW2P5jaYe ++Nx4Xpc4uf341elTfym8NQ/CfEfgAn3zs0ZOmnCX3JlmFh7gPW8fOSIDTC0NkJtU +6LlguuprKhAUCSPKDlod7f7SmiwMsqvaAH+6Hi402tFnIwA1zjQk4BoCsUAVXVQx +l2LC2UD3zBZw9WO6Y/YDgzM6Q2TlI9l1IjmkMHBWHalZ2afA7Uutv4JeNm0joT1D +O5TmDYkkjjfu/+t+QnmBt5KgN2+HwF83ceJOqbPETvEviG5Wh+RXIT5kSgqgRPuV +44jA/CTiR2VibEJ22D0= +=mGFM +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tool/ss-feedback/sender-d b/tool/ss-feedback/sender-d new file mode 100755 index 00000000..51d7b152 --- /dev/null +++ b/tool/ss-feedback/sender-d @@ -0,0 +1,17 @@ +#!/bin/bash + +HERE=$(dirname $0) + +case `uname -m` in + x86_64) + sender_appendix="amd64" + ;; + aarch64) + sender_appendix="arm64" + ;; + loongarch64) + sender_appendix="loong64" + ;; +esac + +${HERE}/sender-d-${sender_appendix} $@ diff --git a/tool/ss-feedback/sender-d-amd64 b/tool/ss-feedback/sender-d-amd64 new file mode 100755 index 00000000..63febd3b Binary files /dev/null and b/tool/ss-feedback/sender-d-amd64 differ diff --git a/tool/ss-feedback/sender-d-arm64 b/tool/ss-feedback/sender-d-arm64 new file mode 100755 index 00000000..b49d84f9 Binary files /dev/null and b/tool/ss-feedback/sender-d-arm64 differ diff --git a/tool/ss-feedback/sender-d-loong64 b/tool/ss-feedback/sender-d-loong64 new file mode 100755 index 00000000..7b218d2a Binary files /dev/null and b/tool/ss-feedback/sender-d-loong64 differ diff --git a/tool/ssaudit b/tool/ssaudit new file mode 100755 index 00000000..13056eb3 --- /dev/null +++ b/tool/ssaudit @@ -0,0 +1,602 @@ +#!/bin/bash +# 初始化常量和全局变量 +readonly SPARK_DOWNLOAD_SERVER_URL="https://d.spark-app.store/" +readonly SPARK_DOWNLOAD_SERVER_URL_NO_PROTOCOL="d.spark-app.store" +# ACE环境配置 - 修改此数组即可添加或删除支持的环境——记得修改 store-helper 里的 uninstaller check-is-installed 和 ss-launcher +readonly ACE_ENVIRONMENTS=( + "bookworm-run:amber-ce-bookworm" + "trixie-run:amber-ce-trixie" + "deepin23-run:amber-ce-deepin23" + "sid-run:amber-ce-sid" +) +readonly ACE_ENVIRONMENTS_FOR_AUTOINSTALL=( + "bookworm-run:amber-ce-bookworm" + "trixie-run:amber-ce-trixie" +) +function get_current_user() { + # 优先通过 who 命令获取用户 + local user + user=$(who | awk '{print $1}' | head -n 1 2>/dev/null) + + # 如果 who 无输出,则通过 loginctl 获取 + if [[ -z "$user" ]]; then + user=$(loginctl list-sessions --no-legend 2>/dev/null | awk '{print $3}' | head -n 1) + fi + + # 返回最终结果(可能为空) + echo "${user}" +} + +function zenity() { + local user=$(get_current_user) + local uid=$(id -u "$user") + sudo -u "$user" DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/"$uid"/bus zenity "$@" +} + + +# 全局变量初始化(位于 parse_args 前) +ACE_PARAMS=() + +# 生成ACE环境参数帮助信息 +function generate_ace_help() { + local help_text="" + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + local ace_param="--${ace_entry#*:}" + help_text+=" $ace_param 使用${ace_entry%%:*} ACE容器安装\n" + done + echo -e "$help_text" +} +source /opt/durapps/spark-store/bin/bashimport/transhell.amber +# 脚本工作变量 +DELETE_AFTER_INSTALL="0" +DEBPATH="" +FORCE_ACE_ENV="" +FORCE_NATIVE="0" +NO_CREATE_DESKTOP="0" +FORCE_CREATE_DESKTOP="0" + +# 加载翻译和调试 +load_transhell_debug +export DEBIAN_FRONTEND=noninteractive +# 根据架构设置仓库URL +if [[ "$IS_APM_ENV" = "" ]] ;then +case $(arch) in + x86_64 | i686 | i386) + STORE_URL="store" + STORE_LIST_URL="" + ;; + aarch64) + STORE_URL="aarch64-store" + STORE_LIST_URL="-aarch64" + ;; + loongarch64) + STORE_URL="loong64-store" + STORE_LIST_URL="-loong64" + ;; + riscv64) + STORE_URL="riscv64-store" + STORE_LIST_URL="-riscv64" + ;; +esac +else +case $(arch) in + x86_64 | i686 | i386) + STORE_URL="amd64-apm" + STORE_LIST_URL="" + ;; + aarch64) + STORE_URL="aarch64-apm" + STORE_LIST_URL="-aarch64" + ;; + loongarch64) + STORE_URL="loong64-apm" + STORE_LIST_URL="-loong64" + ;; + riscv64) + STORE_URL="riscv64-apm" + STORE_LIST_URL="-riscv64" + ;; +esac +fi +# 帮助函数 +function show_help() { + echo "Spark Store Audit script. 星火商店审核脚本" + echo "用法: $0 [选项] " + echo "选项:" + echo " -h, --help 显示帮助信息" + echo " --delete-after-install 安装成功后删除软件包" + echo " --no-create-desktop-entry 不创建桌面快捷方式" + echo " --force-create-desktop-entry 强制创建桌面快捷方式" + echo "$(generate_ace_help)" + echo " --native 只在主机安装,不使用ACE容器" +} +# 参数解析 +function parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + show_help + exit 0 + ;; + --delete-after-install) + DELETE_AFTER_INSTALL="1" + shift + ;; + --native) + FORCE_NATIVE="1" + shift + ;; + --no-create-desktop-entry) + NO_CREATE_DESKTOP="1" + shift + ;; + --force-create-desktop-entry) + FORCE_CREATE_DESKTOP="1" + shift + ;; + *) + # 检查是否为ACE环境参数 + local is_ace_param=0 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + local ace_param="--${ace_entry#*:}" + if [ "$1" = "$ace_param" ]; then + # 将ACE环境命令名加入数组 + ACE_PARAMS+=("${ace_entry%%:*}") + is_ace_param=1 + shift + break + fi + done + + # 如果不是ACE环境参数,则视为DEB路径 + if [ "$is_ace_param" -eq 0 ]; then + DEBPATH="$1" + shift + fi + ;; + esac + done +} + + +# 验证当前用户 +function validate_user() { + if [ "$(id -u)" != "0" ]; then + echo "${TRANSHELL_CONTENT_PLEASE_RUN_AS_ROOT}" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi +} + +# 验证文件存在或尝试下载 +function validate_or_download_file() { + if [ ! -f "$1" ]; then + echo "${TRANSHELL_CONTENT_FILE_NOT_EXIST},Trying to redownload" + aptss update + FILEPATH=$(dirname "$1") + FILENAME=$(basename "$1") + PACKAGE_NAME=$(echo "$FILENAME" | sed -r 's/^([^_]+)_.*$/\1/') + VERSION=$(echo "$FILENAME" | sed -r 's/^[^_]+_([^_]+)_.*$/\1/') + pushd "${FILEPATH}" >/dev/null || exit 1 + aptss download "${PACKAGE_NAME}" + popd >/dev/null || exit 1 + + if [ ! -f "$1" ]; then + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + fi +} + +# 哈希校验 +function hash_check() { + local PACKAGES_DATA_PATH="" + + # 检查可能的仓库位置 + if [ -e "/var/lib/aptss/lists/${SPARK_DOWNLOAD_SERVER_URL_NO_PROTOCOL}_${STORE_URL}_Packages" ]; then + PACKAGES_DATA_PATH="/var/lib/aptss/lists/${SPARK_DOWNLOAD_SERVER_URL_NO_PROTOCOL}_${STORE_URL}_Packages" + elif [ -e "/var/lib/aptss/lists/d.store.deepinos.org.cn_${STORE_URL}_Packages" ]; then + PACKAGES_DATA_PATH="/var/lib/aptss/lists/d.store.deepinos.org.cn_${STORE_URL}_Packages" + else + PACKAGES_DATA_PATH="/var/lib/aptss/lists/mirrors.sdu.edu.cn_spark-store-repository_${STORE_URL}_Packages" + fi + + echo "正在运行包验证..." + echo "Running Spark Package Verify..." + + DEB_SHA512SUM=$(sha512sum "$1" | cut -d ' ' -f 1) + unset IS_SHA512SUM_CHECKED + IS_SHA512SUM_CHECKED=$(grep -F "$DEB_SHA512SUM" "$PACKAGES_DATA_PATH") +} + +# 确保aptss存在 +function ensure_aptss_exist() { + if ! command -v aptss &>/dev/null; then + apt update + local deb_file="/tmp/spark-store-console-in-container_latest_all.deb" + + if ! wget -O "$deb_file" "https://amber-ce-resource.spark-app.store/store/depends/spark-store-console-in-container_latest_all.deb"; then + echo "下载 .deb 安装包失败" >&2 + return 1 + fi + + if ! apt install -y "$deb_file"; then + echo "安装 .deb 包失败" >&2 + rm -f "$deb_file" + return 1 + fi + rm -f "$deb_file" + + if ! command -v aptss &>/dev/null; then + echo "成功安装但未找到 aptss 命令" >&2 + return 1 + fi + fi +} +export -f ensure_aptss_exist + +# 确保ACE环境存在 +# 确保ACE环境存在 +function ensure_ace_env() { + local ace_env_pkg="${1}" + local ace_cmd="" + + # 根据配置找到对应命令 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + if [ "${ace_entry#*:}" = "$ace_env_pkg" ]; then + ace_cmd="${ace_entry%%:*}" + break + fi + 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() { + # 如果明确要求不要创建或明确要创建,则跳过配置文件检查 + if [ "$NO_CREATE_DESKTOP" -eq 1 ]; then + echo "根据参数要求,跳过创建桌面快捷方式" + return + fi + + if [ "$FORCE_CREATE_DESKTOP" -eq 0 ]; then + if [ -e "$(sudo -u "$user" xdg-user-dir)/.config/spark-union/spark-store/ssshell-config-do-not-create-desktop" ]; then + echo "根据配置要求,跳过创建桌面快捷方式" + return + fi + fi + + exec_create_desktop_file +} +export CURRENT_USER_DIR_DESKTOP=$(sudo -u "$user" xdg-user-dir DESKTOP) +function exec_create_desktop_file() { + local desktop_files=() + local package_name_lower=$(echo "$package_name" | tr '[:upper:]' '[:lower:]') + + # 只收集指定路径下的桌面文件 + # 1. /usr/share/applications/ 目录下的 .desktop 文件 + desktop_files+=($(dpkg -L "$package_name" 2>/dev/null | grep '^/usr/share/applications/.*\.desktop$' || true)) + + # 2. /opt/apps/包名/entries/applications/ 目录下的 .desktop 文件 + # 先尝试精确匹配包名路径 + desktop_files+=($(dpkg -L "$package_name" 2>/dev/null | grep "^/opt/apps/$package_name/entries/applications/.*\.desktop$" || true)) + + # 再尝试小写包名路径(有些包可能使用小写路径) + desktop_files+=($(dpkg -L "$package_name" 2>/dev/null | grep "^/opt/apps/$package_name_lower/entries/applications/.*\.desktop$" || true)) + + # 如果没有找到任何符合条件的桌面文件,则直接返回 + if [ ${#desktop_files[@]} -eq 0 ]; then + echo "未找到符合条件的桌面快捷方式文件(/usr/share/applications/ 或 /opt/apps/$package_name/entries/applications/)" + return 0 + fi + + echo "找到 ${#desktop_files[@]} 个桌面快捷方式文件:" + printf '%s\n' "${desktop_files[@]}" + + for desktop_file_path in "${desktop_files[@]}"; do + # 检查文件是否存在 + if [ ! -f "$desktop_file_path" ]; then + echo "文件不存在,跳过: $desktop_file_path" + continue + fi + + # 检查是否是 NoDisplay=true 的桌面文件 + if [ -z "$(grep -i 'NoDisplay=true' "$desktop_file_path")" ]; then + echo "安装桌面快捷方式: $desktop_file_path" + chmod +x "$desktop_file_path" + sudo -u "$user" cp "$desktop_file_path" "${CURRENT_USER_DIR_DESKTOP}/" + else + echo "跳过 NoDisplay=true 的桌面文件: $desktop_file_path" + fi + done +} +export -f exec_create_desktop_file + +# 在ACE环境中创建桌面快捷方式 +function create_desktop_in_ace() { + local ace_cmd="$1" + local package_name="$2" + + # 如果明确要求不要创建,则直接返回 + if [ "$NO_CREATE_DESKTOP" -eq 1 ]; then + echo "根据参数要求,跳过在ACE中创建桌面快捷方式" + return 0 + fi + + # 如果是强制创建,或者没有配置禁止创建 + if [ "$FORCE_CREATE_DESKTOP" -eq 1 ] || ! $ace_cmd "[ -e ~/.config/spark-union/spark-store/ssshell-config-do-not-create-desktop ]"; then + echo "在ACE环境中创建桌面快捷方式..." + export -f exec_create_desktop_file + export package_name + export FORCE_CREATE_DESKTOP + $ace_cmd "exec_create_desktop_file" + else + echo "根据ACE环境中的配置,跳过创建桌面快捷方式" + fi +} + +# 在指定ACE环境中安装 +function install_in_ace_env() { + local ace_cmd="$1" + local deb_path="$2" + local ace_env_pkg="${3#*:}" + + if [ "$IS_ACE_ENV" != "" ] || command -v termux-chroot; then + echo "无法在ACE/termux/小小电脑中安装ACE包" + return 1 + fi + if ! ensure_ace_env "$ace_env_pkg"; then + return 1 + fi + + echo "----------------------------------------" + echo "正在尝试使用 $ace_cmd 环境安装..." + echo "----------------------------------------" + $ace_cmd "ensure_aptss_exist" + + # 首先尝试dry-run测试 + if ! $ace_cmd "aptss install --dry-run '$deb_path'"; then + echo "初始dry-run测试失败,尝试更新后重试..." + $ace_cmd "aptss update" + if ! $ace_cmd "aptss install --dry-run '$deb_path'"; then + echo "dry-run测试仍然失败,放弃安装" + echo "OMG_IT_GOES_WRONG" + return 1 + fi + fi + + # dry-run成功后执行实际安装 + $ace_cmd "aptss install store.spark-app.app-runtime-base --no-install-recommends -yfq" + if $ace_cmd "dpkg -i '$deb_path' || aptss install '$deb_path' -yfq"; then + return 0 + else + return 1 + fi +} + +# 在主机安装 +function install_in_host() { + local deb_path="$1" + + # 首先尝试dry-run测试 + if ! aptss install --dry-run "$deb_path"; then + echo "初始dry-run测试失败,尝试更新后重试..." + aptss update + if ! aptss install --dry-run "$deb_path"; then + echo "dry-run测试仍然失败,放弃安装" + return 1 + fi + fi + + # dry-run成功后执行实际安装 + if dpkg -i "$deb_path" || aptss install "$deb_path" -yfq; then + return 0 + else + return 1 + fi +} + +# 自动尝试在各种环境中安装 +function auto_try_install() { + local deb_path="$1" + + # 首先尝试在主机安装 + if install_in_host "$deb_path"; then + create_desktop_file + return 0 + fi + + # 如果主机安装失败,并非在ACE内运行且不在强制本地模式,尝试ACE环境 + if [ "$FORCE_NATIVE" -eq 0 ] && [ "$IS_ACE_ENV" = "" ] && ! command -v termux-chroot; then + for ace_entry in "${ACE_ENVIRONMENTS_FOR_AUTOINSTALL[@]}"; do + local ace_cmd=${ace_entry%%:*} + local ace_env_pkg=${ace_entry#*:} + + # 确保ACE环境存在 + if ensure_ace_env "$ace_env_pkg"; then + if install_in_ace_env "$ace_cmd" "$deb_path" "$ace_env_pkg"; then + # 在ACE环境中创建桌面快捷方式 + create_desktop_in_ace "$ace_cmd" "$package_name" + return 0 + fi + fi + done + fi + + return 1 +} +# 清理安装后的文件 +function post_install_cleanup() { + local success=$1 + local deb_path="$2" + local package_name="$3" + + if [ "$success" -eq 0 ] && [ "$DELETE_AFTER_INSTALL" -eq "1" ]; then + # 检查是否安装在主机 + if [ "$FORCE_NATIVE" -eq 1 ] || [ -n "$FORCE_ACE_ENV" ]; then + if [ "$FORCE_NATIVE" -eq 1 ]; then + if dpkg -s "$package_name" >/dev/null 2>&1; then + echo "软件包已在主机安装:$package_name" + create_desktop_file + unlock_file "$deb_path" + rm "$deb_path" + echo "${TRANSHELL_CONTENT_DEB_IS_DELETED}" + else + echo "软件包未在主机安装:$package_name" + echo "安装异常!抛出错误" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + else + # ACE环境中安装的情况,不检查主机dpkg数据库 + echo "软件包已在ACE环境安装:$package_name" + unlock_file "$deb_path" + rm "$deb_path" + echo "${TRANSHELL_CONTENT_DEB_IS_DELETED}" + fi + else + # 自动模式下,如果ACE安装成功也会走到这里 + echo "软件包已安装:$package_name" + unlock_file "$deb_path" + rm "$deb_path" + echo "${TRANSHELL_CONTENT_DEB_IS_DELETED}" + fi + else + echo "${TRANSHELL_CONTENT_WILL_NOT_DELETE_DEB}" + if [ "$FORCE_NATIVE" -eq 1 ] && ! dpkg -s "$package_name" >/dev/null 2>&1; then + echo "软件包未在主机安装:$package_name" + echo "安装异常!抛出错误" + echo "OMG-IT-GOES-WRONG" + exit 1 + elif [ -n "$FORCE_ACE_ENV" ] && ! command -v "$FORCE_ACE_ENV" >/dev/null 2>&1; then + echo "指定的ACE环境不可用" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + fi +} + +# 文件锁定/解锁函数 +function lock_file() { + chattr +i "$1" +} + +function unlock_file() { + if [ -e "$1" ];then + chattr -i "$1" + fi +} + +# 主安装流程 +function main_install() { + parse_args "$@" + + if [ -z "$DEBPATH" ]; then + echo "没有接收到参数,退出" + show_help + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + + # 设置退出时的文件解锁 + trap 'unlock_file $DEBPATH' EXIT + validate_user + validate_or_download_file "$DEBPATH" + + DEBPATH=$(realpath "$DEBPATH") + lock_file "$DEBPATH" + + + + package_name=$(dpkg-deb -f "$DEBPATH" Package) + local install_success=1 + if [ "$FORCE_NATIVE" -eq 1 ] || [ "$IS_ACE_ENV" = "1" ]; then + # 优先使用主机安装,忽略所有ACE参数 + echo "忽略ACE,使用主机安装 $package_name" + install_in_host "$DEBPATH" + install_success=$? + # 安装成功后在主机创建桌面快捷方式 + if [ "$install_success" -eq 0 ]; then + create_desktop_file + fi + + elif [ ${#ACE_PARAMS[@]} -gt 0 ] && [ "$IS_ACE_ENV" = "" ]; then + # 用户指定了一个或多个ACE环境,且未要求原生安装 + echo "使用ACE环境安装,已指定环境: ${ACE_PARAMS[*]}" + + # 查找第一个已安装的ACE环境 + chosen_env="" + for env_cmd in "${ACE_PARAMS[@]}"; do + if command -v "$env_cmd" >/dev/null 2>&1; then + chosen_env="$env_cmd" + break + fi + done + # 如果没有安装任何环境,则使用第一个指定的环境 + if [ -z "$chosen_env" ]; then + chosen_env="${ACE_PARAMS[0]}" + echo "未发现已安装的ACE环境,准备安装 $chosen_env..." + # 查找对应的ACE环境软件包名 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + if [ "${ace_entry%%:*}" = "$chosen_env" ]; then + ace_pkg="${ace_entry#*:}" + break + fi + done + # 安装ACE环境(示例使用aptss工具,可根据实际情况调整) + ensure_ace_env "$ace_pkg" -y + fi + + # 再次确认ACE环境命令是否可用 + if command -v "$chosen_env" >/dev/null 2>&1; then + # 查找软件包名(仅首次查找即可) + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + if [ "${ace_entry%%:*}" = "$chosen_env" ]; then + ace_pkg="${ace_entry#*:}" + break + fi + done + echo "在 ACE 环境 $chosen_env 中安装 $package_name" + install_in_ace_env "$chosen_env" "$DEBPATH" "$ace_pkg" + install_success=$? + if [ "$install_success" -eq 0 ]; then + create_desktop_in_ace "$chosen_env" "$package_name" + fi + else + echo "指定的ACE环境 $chosen_env 不可用" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + + else + # 未指定ACE环境和--native,使用自动安装逻辑(先主机再ACE) + echo "自动选择安装方式" + auto_try_install "$DEBPATH" + install_success=$? + fi + + post_install_cleanup "$install_success" "$DEBPATH" "$package_name" +} + +# 执行主函数 +main_install "$@" diff --git a/tool/ssinstall b/tool/ssinstall new file mode 100755 index 00000000..6a191400 --- /dev/null +++ b/tool/ssinstall @@ -0,0 +1,615 @@ +#!/bin/bash +# 初始化常量和全局变量 +readonly SPARK_DOWNLOAD_SERVER_URL="https://d.spark-app.store/" +readonly SPARK_DOWNLOAD_SERVER_URL_NO_PROTOCOL="d.spark-app.store" +# ACE环境配置 - 修改此数组即可添加或删除支持的环境——记得修改 store-helper 里的 uninstaller check-is-installed 和 ss-launcher +readonly ACE_ENVIRONMENTS=( + "bookworm-run:amber-ce-bookworm" + "trixie-run:amber-ce-trixie" + "deepin23-run:amber-ce-deepin23" + "sid-run:amber-ce-sid" +) +readonly ACE_ENVIRONMENTS_FOR_AUTOINSTALL=( + "bookworm-run:amber-ce-bookworm" + "trixie-run:amber-ce-trixie" +) +function get_current_user() { + # 优先通过 who 命令获取用户 + local user + user=$(who | awk '{print $1}' | head -n 1 2>/dev/null) + + # 如果 who 无输出,则通过 loginctl 获取 + if [[ -z "$user" ]]; then + user=$(loginctl list-sessions --no-legend 2>/dev/null | awk '{print $3}' | head -n 1) + fi + + # 返回最终结果(可能为空) + echo "${user}" +} + +function zenity() { + local user=$(get_current_user) + local uid=$(id -u "$user") + sudo -u "$user" DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/"$uid"/bus zenity "$@" +} + + +# 全局变量初始化(位于 parse_args 前) +ACE_PARAMS=() + +# 生成ACE环境参数帮助信息 +function generate_ace_help() { + local help_text="" + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + local ace_param="--${ace_entry#*:}" + help_text+=" $ace_param 使用${ace_entry%%:*} ACE容器安装\n" + done + echo -e "$help_text" +} +source /opt/durapps/spark-store/bin/bashimport/transhell.amber +# 脚本工作变量 +DELETE_AFTER_INSTALL="0" +DEBPATH="" +FORCE_ACE_ENV="" +FORCE_NATIVE="0" +NO_CREATE_DESKTOP="0" +FORCE_CREATE_DESKTOP="0" + +# 加载翻译和调试 +load_transhell_debug +export DEBIAN_FRONTEND=noninteractive +# 根据架构设置仓库URL +if [[ "$IS_APM_ENV" = "" ]] ;then +case $(arch) in + x86_64 | i686 | i386) + STORE_URL="store" + STORE_LIST_URL="" + ;; + aarch64) + STORE_URL="aarch64-store" + STORE_LIST_URL="-aarch64" + ;; + loongarch64) + STORE_URL="loong64-store" + STORE_LIST_URL="-loong64" + ;; + riscv64) + STORE_URL="riscv64-store" + STORE_LIST_URL="-riscv64" + ;; +esac +else +case $(arch) in + x86_64 | i686 | i386) + STORE_URL="amd64-apm" + STORE_LIST_URL="" + ;; + aarch64) + STORE_URL="arm64-apm" + STORE_LIST_URL="-arm64" + ;; + loongarch64) + STORE_URL="loong64-apm" + STORE_LIST_URL="-loong64" + ;; + riscv64) + STORE_URL="riscv64-apm" + STORE_LIST_URL="-riscv64" + ;; +esac +fi +# 帮助函数 +function show_help() { + echo "Spark Store Install script. 星火商店安装脚本" + echo "用法: $0 [选项] " + echo "选项:" + echo " -h, --help 显示帮助信息" + echo " --delete-after-install 安装成功后删除软件包" + echo " --no-create-desktop-entry 不创建桌面快捷方式" + echo " --force-create-desktop-entry 强制创建桌面快捷方式" + echo "$(generate_ace_help)" + echo " --native 只在主机安装,不使用ACE容器" +} +# 参数解析 +function parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + show_help + exit 0 + ;; + --delete-after-install) + DELETE_AFTER_INSTALL="1" + shift + ;; + --native) + FORCE_NATIVE="1" + shift + ;; + --no-create-desktop-entry) + NO_CREATE_DESKTOP="1" + shift + ;; + --force-create-desktop-entry) + FORCE_CREATE_DESKTOP="1" + shift + ;; + *) + # 检查是否为ACE环境参数 + local is_ace_param=0 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + local ace_param="--${ace_entry#*:}" + if [ "$1" = "$ace_param" ]; then + # 将ACE环境命令名加入数组 + ACE_PARAMS+=("${ace_entry%%:*}") + is_ace_param=1 + shift + break + fi + done + + # 如果不是ACE环境参数,则视为DEB路径 + if [ "$is_ace_param" -eq 0 ]; then + DEBPATH="$1" + shift + fi + ;; + esac + done +} + + +# 验证当前用户 +function validate_user() { + if [ "$(id -u)" != "0" ]; then + echo "${TRANSHELL_CONTENT_PLEASE_RUN_AS_ROOT}" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi +} + +# 验证文件存在或尝试下载 +function validate_or_download_file() { + if [ ! -f "$1" ]; then + echo "${TRANSHELL_CONTENT_FILE_NOT_EXIST},Trying to redownload" + aptss update + FILEPATH=$(dirname "$1") + FILENAME=$(basename "$1") + PACKAGE_NAME=$(echo "$FILENAME" | sed -r 's/^([^_]+)_.*$/\1/') + VERSION=$(echo "$FILENAME" | sed -r 's/^[^_]+_([^_]+)_.*$/\1/') + pushd "${FILEPATH}" >/dev/null || exit 1 + aptss download "${PACKAGE_NAME}" + popd >/dev/null || exit 1 + + if [ ! -f "$1" ]; then + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + fi +} + +# 哈希校验 +function hash_check() { + local PACKAGES_DATA_PATH="" + + # 检查可能的仓库位置 + if [ -e "/var/lib/aptss/lists/${SPARK_DOWNLOAD_SERVER_URL_NO_PROTOCOL}_${STORE_URL}_Packages" ]; then + PACKAGES_DATA_PATH="/var/lib/aptss/lists/${SPARK_DOWNLOAD_SERVER_URL_NO_PROTOCOL}_${STORE_URL}_Packages" + elif [ -e "/var/lib/aptss/lists/d.store.deepinos.org.cn_${STORE_URL}_Packages" ]; then + PACKAGES_DATA_PATH="/var/lib/aptss/lists/d.store.deepinos.org.cn_${STORE_URL}_Packages" + else + PACKAGES_DATA_PATH="/var/lib/aptss/lists/mirrors.sdu.edu.cn_spark-store-repository_${STORE_URL}_Packages" + fi + + echo "正在运行包验证..." + echo "Running Spark Package Verify..." + + DEB_SHA512SUM=$(sha512sum "$1" | cut -d ' ' -f 1) + unset IS_SHA512SUM_CHECKED + IS_SHA512SUM_CHECKED=$(grep -F "$DEB_SHA512SUM" "$PACKAGES_DATA_PATH") +} + +# 确保aptss存在 +function ensure_aptss_exist() { + if ! command -v aptss &>/dev/null; then + apt update + local deb_file="/tmp/spark-store-console-in-container_latest_all.deb" + + if ! wget -O "$deb_file" "https://amber-ce-resource.spark-app.store/store/depends/spark-store-console-in-container_latest_all.deb"; then + echo "下载 .deb 安装包失败" >&2 + return 1 + fi + + if ! apt install -y "$deb_file"; then + echo "安装 .deb 包失败" >&2 + rm -f "$deb_file" + return 1 + fi + rm -f "$deb_file" + + if ! command -v aptss &>/dev/null; then + echo "成功安装但未找到 aptss 命令" >&2 + return 1 + fi + fi +} +export -f ensure_aptss_exist + +# 确保ACE环境存在 +# 确保ACE环境存在 +function ensure_ace_env() { + local ace_env_pkg="${1}" + local ace_cmd="" + + # 根据配置找到对应命令 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + if [ "${ace_entry#*:}" = "$ace_env_pkg" ]; then + ace_cmd="${ace_entry%%:*}" + break + fi + 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() { + # 如果明确要求不要创建或明确要创建,则跳过配置文件检查 + if [ "$NO_CREATE_DESKTOP" -eq 1 ]; then + echo "根据参数要求,跳过创建桌面快捷方式" + return + fi + + if [ "$FORCE_CREATE_DESKTOP" -eq 0 ]; then + if [ -e "$(sudo -u "$user" xdg-user-dir)/.config/spark-union/spark-store/ssshell-config-do-not-create-desktop" ]; then + echo "根据配置要求,跳过创建桌面快捷方式" + return + fi + fi + + exec_create_desktop_file +} +export CURRENT_USER_DIR_DESKTOP=$(sudo -u "$user" xdg-user-dir DESKTOP) +function exec_create_desktop_file() { + local desktop_files=() + local package_name_lower=$(echo "$package_name" | tr '[:upper:]' '[:lower:]') + + # 只收集指定路径下的桌面文件 + # 1. /usr/share/applications/ 目录下的 .desktop 文件 + desktop_files+=($(dpkg -L "$package_name" 2>/dev/null | grep '^/usr/share/applications/.*\.desktop$' || true)) + + # 2. /opt/apps/包名/entries/applications/ 目录下的 .desktop 文件 + # 先尝试精确匹配包名路径 + desktop_files+=($(dpkg -L "$package_name" 2>/dev/null | grep "^/opt/apps/$package_name/entries/applications/.*\.desktop$" || true)) + + # 再尝试小写包名路径(有些包可能使用小写路径) + desktop_files+=($(dpkg -L "$package_name" 2>/dev/null | grep "^/opt/apps/$package_name_lower/entries/applications/.*\.desktop$" || true)) + + # 如果没有找到任何符合条件的桌面文件,则直接返回 + if [ ${#desktop_files[@]} -eq 0 ]; then + echo "未找到符合条件的桌面快捷方式文件(/usr/share/applications/ 或 /opt/apps/$package_name/entries/applications/)" + return 0 + fi + + echo "找到 ${#desktop_files[@]} 个桌面快捷方式文件:" + printf '%s\n' "${desktop_files[@]}" + + for desktop_file_path in "${desktop_files[@]}"; do + # 检查文件是否存在 + if [ ! -f "$desktop_file_path" ]; then + echo "文件不存在,跳过: $desktop_file_path" + continue + fi + + # 检查是否是 NoDisplay=true 的桌面文件 + if [ -z "$(grep -i 'NoDisplay=true' "$desktop_file_path")" ]; then + echo "安装桌面快捷方式: $desktop_file_path" + chmod +x "$desktop_file_path" + sudo -u "$user" cp "$desktop_file_path" "${CURRENT_USER_DIR_DESKTOP}/" + else + echo "跳过 NoDisplay=true 的桌面文件: $desktop_file_path" + fi + done +} + +export -f exec_create_desktop_file + +# 在ACE环境中创建桌面快捷方式 +function create_desktop_in_ace() { + local ace_cmd="$1" + local package_name="$2" + + # 如果明确要求不要创建,则直接返回 + if [ "$NO_CREATE_DESKTOP" -eq 1 ]; then + echo "根据参数要求,跳过在ACE中创建桌面快捷方式" + return 0 + fi + + # 如果是强制创建,或者没有配置禁止创建 + if [ "$FORCE_CREATE_DESKTOP" -eq 1 ] || ! [ -e ~/.config/spark-union/spark-store/ssshell-config-do-not-create-desktop ]; then + echo "在ACE环境中创建桌面快捷方式..." + export -f exec_create_desktop_file + export package_name + export FORCE_CREATE_DESKTOP + $ace_cmd "exec_create_desktop_file" + else + echo "根据ACE环境中的配置,跳过创建桌面快捷方式" + fi +} + +# 在指定ACE环境中安装 +function install_in_ace_env() { + local ace_cmd="$1" + local deb_path="$2" + local ace_env_pkg="${3#*:}" + + if [ "$IS_ACE_ENV" != "" ] || command -v termux-chroot; then + echo "无法在ACE/termux/小小电脑中安装ACE包" + return 1 + fi + if ! ensure_ace_env "$ace_env_pkg"; then + return 1 + fi + + echo "----------------------------------------" + echo "正在尝试使用 $ace_cmd 环境安装..." + echo "----------------------------------------" + $ace_cmd "ensure_aptss_exist" + + # 首先尝试dry-run测试 + if ! $ace_cmd aptss install --dry-run "$deb_path"; then + echo "初始dry-run测试失败,尝试更新后重试..." + $ace_cmd aptss update + if ! $ace_cmd aptss install --dry-run "$deb_path"; then + echo "dry-run测试仍然失败,放弃安装" + echo "OMG_IT_GOES_WRONG" + return 1 + fi + fi + + # dry-run成功后执行实际安装 + $ace_cmd aptss install store.spark-app.app-runtime-base --no-install-recommends -yfq + if $ace_cmd dpkg -i "$deb_path" || $ace_cmd aptss install "$deb_path" -yfq; then + return 0 + else + return 1 + fi +} + +# 在主机安装 +function install_in_host() { + local deb_path="$1" + + # 首先尝试dry-run测试 + if ! aptss install --dry-run "$deb_path"; then + echo "初始dry-run测试失败,尝试更新后重试..." + aptss update + if ! aptss install --dry-run "$deb_path"; then + echo "dry-run测试仍然失败,放弃安装" + return 1 + fi + fi + + # dry-run成功后执行实际安装 + if dpkg -i "$deb_path" || aptss install "$deb_path" -yfq; then + return 0 + else + return 1 + fi +} + +# 自动尝试在各种环境中安装 +function auto_try_install() { + local deb_path="$1" + + # 首先尝试在主机安装 + if install_in_host "$deb_path"; then + create_desktop_file + return 0 + fi + + # 如果主机安装失败,并非在ACE内运行且不在强制本地模式,尝试ACE环境 + if [ "$FORCE_NATIVE" -eq 0 ] && [ "$IS_ACE_ENV" = "" ] && ! command -v termux-chroot; then + for ace_entry in "${ACE_ENVIRONMENTS_FOR_AUTOINSTALL[@]}"; do + local ace_cmd=${ace_entry%%:*} + local ace_env_pkg=${ace_entry#*:} + + # 确保ACE环境存在 + if ensure_ace_env "$ace_env_pkg"; then + if install_in_ace_env "$ace_cmd" "$deb_path" "$ace_env_pkg"; then + # 在ACE环境中创建桌面快捷方式 + create_desktop_in_ace "$ace_cmd" "$package_name" + return 0 + fi + fi + done + fi + + return 1 +} +# 清理安装后的文件 +function post_install_cleanup() { + local success=$1 + local deb_path="$2" + local package_name="$3" + + if [ "$success" -eq 0 ] && [ "$DELETE_AFTER_INSTALL" -eq "1" ]; then + # 检查是否安装在主机 + if [ "$FORCE_NATIVE" -eq 1 ] || [ -n "$FORCE_ACE_ENV" ]; then + if [ "$FORCE_NATIVE" -eq 1 ]; then + if dpkg -s "$package_name" >/dev/null 2>&1; then + echo "软件包已在主机安装:$package_name" + create_desktop_file + unlock_file "$deb_path" + rm "$deb_path" + echo "${TRANSHELL_CONTENT_DEB_IS_DELETED}" + else + echo "软件包未在主机安装:$package_name" + echo "安装异常!抛出错误" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + else + # ACE环境中安装的情况,不检查主机dpkg数据库 + echo "软件包已在ACE环境安装:$package_name" + unlock_file "$deb_path" + rm "$deb_path" + echo "${TRANSHELL_CONTENT_DEB_IS_DELETED}" + fi + else + # 自动模式下,如果ACE安装成功也会走到这里 + echo "软件包已安装:$package_name" + unlock_file "$deb_path" + rm "$deb_path" + echo "${TRANSHELL_CONTENT_DEB_IS_DELETED}" + fi + else + echo "${TRANSHELL_CONTENT_WILL_NOT_DELETE_DEB}" + if [ "$FORCE_NATIVE" -eq 1 ] && ! dpkg -s "$package_name" >/dev/null 2>&1; then + echo "软件包未在主机安装:$package_name" + echo "安装异常!抛出错误" + echo "OMG-IT-GOES-WRONG" + exit 1 + elif [ -n "$FORCE_ACE_ENV" ] && ! command -v "$FORCE_ACE_ENV" >/dev/null 2>&1; then + echo "指定的ACE环境不可用" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + fi +} + +# 文件锁定/解锁函数 +function lock_file() { + chattr +i "$1" +} + +function unlock_file() { + if [ -e "$1" ];then + chattr -i "$1" + fi +} + +# 主安装流程 +function main_install() { + parse_args "$@" + + if [ -z "$DEBPATH" ]; then + echo "没有接收到参数,退出" + show_help + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + + # 设置退出时的文件解锁 + trap 'unlock_file $DEBPATH' EXIT + validate_user + validate_or_download_file "$DEBPATH" + + DEBPATH=$(realpath "$DEBPATH") + lock_file "$DEBPATH" + + hash_check "$DEBPATH" + + if [ -z "$IS_SHA512SUM_CHECKED" ]; then + echo "尝试更新仓库信息重新校验" + aptss ssupdate + hash_check "$DEBPATH" + if [ -z "$IS_SHA512SUM_CHECKED" ]; then + echo -e "$TRANSHELL_CONTENT_HASH_CHECK_FAILED" + zenity --info --icon-name=spark-store --height 270 --width 500 --text "$TRANSHELL_CONTENT_HASH_CHECK_FAILED" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + fi + + package_name=$(dpkg-deb -f "$DEBPATH" Package) + local install_success=1 + if [ "$FORCE_NATIVE" -eq 1 ] || [ "$IS_ACE_ENV" = "1" ]; then + # 优先使用主机安装,忽略所有ACE参数 + echo "忽略ACE,使用主机安装 $package_name" + install_in_host "$DEBPATH" + install_success=$? + # 安装成功后在主机创建桌面快捷方式 + if [ "$install_success" -eq 0 ]; then + create_desktop_file + fi + + elif [ ${#ACE_PARAMS[@]} -gt 0 ] && [ "$IS_ACE_ENV" = "" ]; then + # 用户指定了一个或多个ACE环境,且未要求原生安装 + echo "使用ACE环境安装,已指定环境: ${ACE_PARAMS[*]}" + + # 查找第一个已安装的ACE环境 + chosen_env="" + for env_cmd in "${ACE_PARAMS[@]}"; do + if command -v "$env_cmd" >/dev/null 2>&1; then + chosen_env="$env_cmd" + break + fi + done + # 如果没有安装任何环境,则使用第一个指定的环境 + if [ -z "$chosen_env" ]; then + chosen_env="${ACE_PARAMS[0]}" + echo "未发现已安装的ACE环境,准备安装 $chosen_env..." + # 查找对应的ACE环境软件包名 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + if [ "${ace_entry%%:*}" = "$chosen_env" ]; then + ace_pkg="${ace_entry#*:}" + break + fi + done + # 安装ACE环境(示例使用aptss工具,可根据实际情况调整) + ensure_ace_env "$ace_pkg" -y + fi + + # 再次确认ACE环境命令是否可用 + if command -v "$chosen_env" >/dev/null 2>&1; then + # 查找软件包名(仅首次查找即可) + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + if [ "${ace_entry%%:*}" = "$chosen_env" ]; then + ace_pkg="${ace_entry#*:}" + break + fi + done + echo "在 ACE 环境 $chosen_env 中安装 $package_name" + install_in_ace_env "$chosen_env" "$DEBPATH" "$ace_pkg" + install_success=$? + if [ "$install_success" -eq 0 ]; then + create_desktop_in_ace "$chosen_env" "$package_name" + fi + else + echo "指定的ACE环境 $chosen_env 不可用" + echo "OMG-IT-GOES-WRONG" + exit 1 + fi + + else + # 未指定ACE环境和--native,使用自动安装逻辑(先主机再ACE) + echo "自动选择安装方式" + auto_try_install "$DEBPATH" + install_success=$? + fi + + post_install_cleanup "$install_success" "$DEBPATH" "$package_name" +} + +# 执行主函数 +main_install "$@" diff --git a/tool/store-helper/check-is-installed b/tool/store-helper/check-is-installed new file mode 100755 index 00000000..b81d85d0 --- /dev/null +++ b/tool/store-helper/check-is-installed @@ -0,0 +1,43 @@ +#!/bin/bash +readonly ACE_ENVIRONMENTS=( + "bookworm-run:amber-ce-bookworm" + "trixie-run:amber-ce-trixie" + "deepin23-run:amber-ce-deepin23" + "sid-run:amber-ce-sid" +) +dpkg -s '$1' 2>/dev/null | grep -q 'Status: install ok installed' > /dev/null 2>&1 +RET="$?" +if [[ "$RET" != "0" ]] && [[ "$IS_ACE_ENV" == "" ]];then ## 如果未在ACE环境中 + + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + ace_cmd=${ace_entry%%:*} + if command -v "$ace_cmd" >/dev/null 2>&1; then + echo "----------------------------------------" + echo "正在检查 $ace_cmd 环境的安装..." + echo "----------------------------------------" + + # 在ACE环境中使用dpkg -s检查安装状态 + # 使用dpkg -s并检查输出中是否包含"Status: install ok installed" + $ace_cmd dpkg -s "$1" 2>/dev/null | grep -q 'Status: install ok installed' + try_run_ret="$?" + + # 最终检测结果处理 + if [ "$try_run_ret" -eq 0 ]; then + echo "----------------------------------------" + echo "在 $ace_cmd 环境中找到了安装" + echo "----------------------------------------" + exit $try_run_ret + else + echo "----------------------------------------" + echo "在 $ace_cmd 环境中未能找到安装,继续查找" + echo "----------------------------------------" + fi + fi + done + echo "----------------------------------------" + echo "所有已安装的 ACE 环境中未能找到安装,退出" + echo "----------------------------------------" + exit "$RET" +fi +## 如果在ACE环境中或者未出错 +exit "$RET" \ No newline at end of file diff --git a/tool/store-helper/pass-auth.sh b/tool/store-helper/pass-auth.sh new file mode 100755 index 00000000..cc11a537 --- /dev/null +++ b/tool/store-helper/pass-auth.sh @@ -0,0 +1,49 @@ + +#!/bin/bash +# We use sudo twice to avoid ACE bug here +# https://gitee.com/amber-ce/amber-ce-bookworm/commit/43e1a1599ede474b37e41aa10c53fd8afc4d35a1 + +function zenity_prompt() { + if [[ -e /usr/bin/garma ]]; then + garma "$@" + else + $(command -v zenity) "$@" + fi +} + +if [ "${IS_ACE_ENV}" = "" ]; then +echo "检测为非ACE环境,直接提权" +pkexec "$@" +exit $? +fi + +# 检查sudo是否需要密码 +if sudo -n true 2>/dev/null; then + echo "sudo 无需密码,继续执行" +else + # 循环输入密码直到成功或用户取消 + while true; do + # 使用zenity弹出密码输入框 + PASSWORD=$(zenity_prompt --password --title="需要sudo权限") + + # 检查用户是否取消输入 + if [ -z "$PASSWORD" ]; then + zenity_prompt --error --text="操作已取消" + exit 1 + fi + + # 尝试使用输入的密码执行sudo命令 + echo "$PASSWORD" | sudo -S -v 2>/dev/null + + # 检查sudo是否成功 + if [ $? -eq 0 ]; then + echo "密码正确,继续执行" + break + else + zenity_prompt --error --text="密码错误,请重新输入" + fi + done +fi + +# 使用sudo命令执行目标程序 +echo "$PASSWORD" | sudo sudo -S "$@" diff --git a/tool/store-helper/ss-launcher b/tool/store-helper/ss-launcher new file mode 100755 index 00000000..0460eaef --- /dev/null +++ b/tool/store-helper/ss-launcher @@ -0,0 +1,164 @@ +#!/bin/bash + +# ===== ACE环境配置 ===== +readonly ACE_ENVIRONMENTS=( + "bookworm-run:amber-ce-bookworm" + "trixie-run:amber-ce-trixie" + "deepin23-run:amber-ce-deepin23" + "sid-run:amber-ce-sid" +) + +# ===== 日志和函数 ===== +[ -f /opt/durapps/spark-store/bin/bashimport/log.amber ] && \ + source /opt/durapps/spark-store/bin/bashimport/log.amber || { + log.info() { echo "INFO: $*"; } + log.warn() { echo "WARN: $*"; } + log.error() { echo "ERROR: $*"; } + log.debug() { echo "DEBUG: $*"; } +} + +# ===== 功能函数 ===== +function scan_desktop_file_log() { + unset desktop_file_path + local package_name=$1 + # 标准desktop文件检测 + while IFS= read -r path; do + [ -z "$(grep 'NoDisplay=true' "$path")" ] && { + log.info "Found valid desktop file: $path" + export desktop_file_path="$path" + return 0 + } + done < <(dpkg -L "$package_name" 2>/dev/null | grep -E '/usr/share/applications/.*\.desktop$|/opt/apps/.*/entries/applications/.*\.desktop$') + + # 深度环境特殊处理 + while IFS= read -r path; do + [ -z "$(grep 'NoDisplay=true' "$path")" ] && { + log.info "Found deepin desktop file: $path" + export desktop_file_path="$path" + return 0 + } + done < <(find /opt/apps/$package_name -path '*/entries/applications/*.desktop' 2>/dev/null) + return 1 +} + +function scan_desktop_file() { + local package_name=$1 result="" + # 标准结果收集 + while IFS= read -r path; do + [ -z "$(grep 'NoDisplay=true' "$path")" ] && result+="$path," + done < <(dpkg -L "$package_name" 2>/dev/null | grep -E '/usr/share/applications/.*\.desktop$|/opt/apps/.*/entries/applications/.*\.desktop$') + + # 深度环境补充扫描 + while IFS= read -r path; do + [ -z "$(grep 'NoDisplay=true' "$path")" ] && result+="$path," + done < <(find /opt/apps/$package_name -path '*/entries/applications/*.desktop' 2>/dev/null) + + echo "${result%,}" +} + +function launch_app() { + local DESKTOP_FILE_PATH="${1#file://}" + # 提取并净化Exec命令 + exec_command=$(grep -m1 '^Exec=' "$DESKTOP_FILE_PATH" | cut -d= -f2- | sed 's/%.//g') + [ -z "$exec_command" ] && return 1 + [ ! -z "$IS_ACE_ENV" ] && HOST_PREFIX="host-spawn" + exec_command="${HOST_PREFIX} $exec_command" + log.info "Launching: $exec_command" + ${SHELL:-bash} -c " $exec_command" & + +} + +# 导出函数以便在ACE环境中使用 +export -f launch_app scan_desktop_file scan_desktop_file_log log.info log.warn log.debug log.error + +# ===== ACE环境执行器 ===== +function ace_runner() { + local action=$1 + local target=$2 + + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + local ace_cmd=${ace_entry%%:*} + local ace_env=${ace_entry#*:} + + if ! command -v "$ace_cmd" >/dev/null; then + log.debug "$ace_cmd not found, skipping..." + continue + fi + + log.info "Attempting in $ace_env environment..." + + case "$action" in + check) + if "$ace_cmd" scan_desktop_file_log "$target"; then + log.info "Found desktop file in $ace_env" + return 0 + fi + ;; + list) + local result + if result=$("$ace_cmd" scan_desktop_file "$target"); then + echo "$result" + return 0 + fi + ;; + launch|start) +"$ace_cmd" scan_desktop_file_log "$target" + if desktop_path=$("$ace_cmd" scan_desktop_file_log "$target"); then + log.info "Launching from $ace_env..." + "$ace_cmd" launch_app $("$ace_cmd" scan_desktop_file "$target") + return 0 + fi + ;; + esac + + log.debug "Attempt in $ace_env failed" + done + + return 1 +} + +# ===== 主逻辑 ===== +[ $# -lt 2 ] && { + log.error "Usage: $0 {check|launch|list|start} package_name/desktop_file" + exit 1 +} + +case $1 in +check) + # 当前环境检查 + if scan_desktop_file_log "$2"; then + exit 0 + else + # 非ACE环境下执行ACE环境扫描 + [ -z "$IS_ACE_ENV" ] && ace_runner check "$2" + exit $? + fi + ;; + +list) + # 当前环境列表 + if result=$(scan_desktop_file "$2"); then + echo "$result" + exit 0 + else + # 非ACE环境下执行ACE环境扫描 + [ -z "$IS_ACE_ENV" ] && ace_runner list "$2" + exit $? + fi + ;; + +launch|start) + # 当前环境启动 + if scan_desktop_file_log "$2" && launch_app "$desktop_file_path"; then + exit 0 + else + # 非ACE环境下通过ACE环境启动 + [ -z "$IS_ACE_ENV" ] && ace_runner launch "$2" + exit $? + fi + ;; +*) + log.error "Invalid command: $1" + exit 2 + ;; +esac diff --git a/tool/store-helper/uninstaller b/tool/store-helper/uninstaller new file mode 100755 index 00000000..ea10feba --- /dev/null +++ b/tool/store-helper/uninstaller @@ -0,0 +1,190 @@ +#!/bin/bash +# ===== ACE环境配置 ===== + +readonly ACE_ENVIRONMENTS=( + "bookworm-run:amber-ce-bookworm" + "trixie-run:amber-ce-trixie" + "deepin23-run:amber-ce-deepin23" + "sid-run:amber-ce-sid" +) +# 生成ACE环境参数帮助信息 +function generate_ace_help() { + local help_text="" + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + local ace_param="--${ace_entry#*:}" + help_text+=" $ace_param 使用${ace_entry%%:*} ACE容器卸载\n" + done + echo -e "$help_text" +} +# 帮助函数 +function show_help() { + echo "Spark Store Uninstall script. 星火商店卸载脚本" + echo "用法: $0 [选项] 包名" + echo "选项:" + echo " -h, --help 显示帮助信息" + echo " --delete-after-install 安装成功后删除软件包" + echo " --no-create-desktop-entry 不创建桌面快捷方式" + echo " --force-create-desktop-entry 强制创建桌面快捷方式" + echo "$(generate_ace_help)" + echo " --native 只在主机卸载,不使用ACE容器" +} + + +# 参数解析 +function parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + show_help + exit 0 + ;; + --delete-after-install) + DELETE_AFTER_INSTALL="1" + shift + ;; + --native) + FORCE_NATIVE="1" + shift + ;; + --no-create-desktop-entry) + NO_CREATE_DESKTOP="1" + shift + ;; + --force-create-desktop-entry) + FORCE_CREATE_DESKTOP="1" + shift + ;; + *) + # 检查是否为ACE环境参数 + local is_ace_param=0 + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + local ace_param="--${ace_entry#*:}" + if [ "$1" = "$ace_param" ]; then + # 将ACE环境命令名加入数组 + ACE_PARAMS+=("${ace_entry%%:*}") + is_ace_param=1 + shift + break + fi + done + + # 如果不是ACE环境参数,则视为包名 + if [ "$is_ace_param" -eq 0 ]; then + PACKAGE_NAME="$1" + shift + fi + ;; + esac + done +} + +# ===== 日志和函数 ===== +[ -f /opt/durapps/spark-store/bin/bashimport/log.amber ] && \ + source /opt/durapps/spark-store/bin/bashimport/log.amber || { + log.info() { echo "INFO: $*"; } + log.warn() { echo "WARN: $*"; } + log.error() { echo "ERROR: $*"; } + log.debug() { echo "DEBUG: $*"; } +} + +# 初始化变量 +FORCE_NATIVE=0 +ACE_PARAMS=() +PACKAGE_NAME="" +uninstall_success=0 + +# 解析参数 +parse_args "$@" + +if [ -z "$PACKAGE_NAME" ]; then + log.error "请指定要卸载的包名" + exit 1 +fi + +# 尝试在本地卸载 +try_native_uninstall() { + if [ "$FORCE_NATIVE" -eq 1 ] || [ ${#ACE_PARAMS[@]} -eq 0 ]; then + echo "----------------------------------------" + echo "正在检查本地环境中的安装..." + echo "----------------------------------------" + + dpkg -s "$PACKAGE_NAME" > /dev/null + RET="$?" + if [[ "$RET" == "0" ]]; then + echo "----------------------------------------" + echo "在本地环境中找到了安装" + echo "----------------------------------------" + apt autopurge "$PACKAGE_NAME" -y + uninstall_success=1 + return 0 + else + echo "----------------------------------------" + echo "在本地环境中未能找到安装" + echo "----------------------------------------" + fi + fi + return 1 +} + +# 尝试在ACE环境中卸载 +try_ace_uninstall() { + local ace_cmd="$1" + if command -v "$ace_cmd" >/dev/null 2>&1; then + echo "----------------------------------------" + echo "正在检查 $ace_cmd 环境的安装..." + echo "----------------------------------------" + + $ace_cmd dpkg -l | grep "^ii $PACKAGE_NAME " > /dev/null + try_run_ret="$?" + + if [ "$try_run_ret" -eq 0 ]; then + echo "----------------------------------------" + echo "在 $ace_cmd 环境中找到了安装" + echo "----------------------------------------" + $ace_cmd apt autopurge "$PACKAGE_NAME" -y + uninstall_success=1 + return 0 + else + echo "----------------------------------------" + echo "在 $ace_cmd 环境中未能找到安装" + echo "----------------------------------------" + fi + fi + return 1 +} + +# 主卸载逻辑 +if [ $FORCE_NATIVE -eq 1 ] && [ ${#ACE_PARAMS[@]} -eq 0 ]; then + # 只有 --native 参数时,只尝试本地卸载 + try_native_uninstall || exit $? +elif [ $FORCE_NATIVE -eq 0 ] && [ ${#ACE_PARAMS[@]} -gt 0 ]; then + # 只有 ACE 参数时,只尝试指定的 ACE 环境卸载 + for ace_param in "${ACE_PARAMS[@]}"; do + try_ace_uninstall "$ace_param" + done +elif [ $FORCE_NATIVE -eq 1 ] && [ ${#ACE_PARAMS[@]} -gt 0 ]; then + # 同时有 --native 和 ACE 参数时,先尝试本地卸载,再尝试 ACE 环境卸载 + try_native_uninstall + for ace_param in "${ACE_PARAMS[@]}"; do + try_ace_uninstall "$ace_param" + done +else + # 无参数时,先尝试本地卸载,再尝试所有 ACE 环境卸载 + try_native_uninstall + + for ace_entry in "${ACE_ENVIRONMENTS[@]}"; do + ace_cmd=${ace_entry%%:*} + if command -v "$ace_cmd" >/dev/null 2>&1; then + try_ace_uninstall "$ace_cmd" + fi + done +fi + +if [ $uninstall_success -eq 0 ]; then + echo "----------------------------------------" + echo "在所有指定的环境中未能找到安装,退出" + echo "----------------------------------------" + exit 1 +fi + +exit 0 diff --git a/tool/update-upgrade/ss-do-upgrade-worker.sh b/tool/update-upgrade/ss-do-upgrade-worker.sh new file mode 100755 index 00000000..308be21c --- /dev/null +++ b/tool/update-upgrade/ss-do-upgrade-worker.sh @@ -0,0 +1,84 @@ +#!/bin/bash +export LANGUAGE=en_US +export DEBIAN_FRONTEND=noninteractive +case $1 in + ssupdate) +if [ "$(id -u)" != "0" ] ; then + pkexec "$0" "$@" + exit +fi + aptss ssupdate 2>&1 | tee /tmp/spark-store-app-ssupdate-log.txt + IS_SSUPDATE_ERROR=`cat /tmp/spark-store-app-ssupdate-log.txt | grep "E: "` + echo "$IS_SSUPDATE_ERROR" > /tmp/spark-store-app-ssupdate-status.txt + chmod 777 /tmp/spark-store-app-ssupdate-status.txt + chmod 777 /tmp/spark-store-app-ssupdate-log.txt + ;; + + upgradable-list) + output=$(env LANGUAGE=en_US /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf list --upgradable -o Dir::Etc::sourcelist="/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list" -o Dir::Etc::sourceparts="/dev/null" -o APT::Get::List-Cleanup="0" | awk NR\>1) + + IFS_OLD="$IFS" + IFS=$'\n' + + for line in $output ; do + PKG_NAME=$(echo $line | awk -F '/' '{print $1}') + PKG_NEW_VER=$(echo $line | awk -F ' ' '{print $2}') + PKG_CUR_VER=$(echo $line | awk -F ' ' '{print $6}' | awk -F ']' '{print $1}') + echo "${PKG_NAME} ${PKG_NEW_VER} ${PKG_CUR_VER}" + done + + IFS="$IFS_OLD" + ;; + + upgrade-app) +if [ "$(id -u)" != "0" ] ; then + pkexec "$0" "$@" + exit +fi + + aptss install "${@:2}" --only-upgrade 2>&1 | tee /tmp/spark-store-app-upgrade-log.txt + sed -i '1i--------------------------------------------------------------' /tmp/spark-store-app-upgrade-log.txt + sed -i '1i更新失败可能是由于系统版本过低,您可先【卸载】此应用后再在商店【安装】此应用来尝试修复此问题,商店会在安装时尝试自动解决问题。若仍无法解决,请按照指引进行反馈' /tmp/spark-store-app-upgrade-log.txt + chmod 777 /tmp/spark-store-app-upgrade-log.txt + IS_UPGRADE_ERROR=`cat /tmp/spark-store-app-upgrade-log.txt | grep "Package manager quit with exit code."` + echo "$IS_UPGRADE_ERROR" > /tmp/spark-store-app-upgrade-status.txt + ;; + test-install-app) +if [ "$(id -u)" != "0" ] ; then + pkexec "$0" "$@" + exit +fi + +try_run_output=$(aptss --dry-run install $2) +try_run_ret="$?" + +if [ "$try_run_ret" -ne 0 ] + then + echo "Package manager quit with exit code.Here is the log" + echo "包管理器以错误代码退出.日志如下" + echo + echo -e "${try_run_output}" + echo "Will try after run aptss update" + echo "将会在aptss update之后再次尝试" + aptss update + echo ---------------------------------------------------------------------------- + try_run_output=$(aptss --dry-run install $2) + try_run_ret="$?" + if [ "$try_run_ret" -ne 0 ] + then + echo "Package manager quit with exit code.Here is the log" + echo "包管理器以错误代码退出.日志如下" + echo + echo -e "${try_run_output}" + exit "$try_run_ret" + fi + +fi + exit 0 + ;; + + clean-log) + + rm -f /tmp/spark-store-app-ssupdate-status.txt /tmp/spark-store-app-ssupdate-log.txt /tmp/spark-store-app-upgrade-log.txt /tmp/spark-store-app-upgrade-status.txt + ;; +esac diff --git a/tool/update-upgrade/ss-do-upgrade.sh b/tool/update-upgrade/ss-do-upgrade.sh new file mode 100755 index 00000000..dee6321e --- /dev/null +++ b/tool/update-upgrade/ss-do-upgrade.sh @@ -0,0 +1,172 @@ +#!/bin/bash + +if [ "$(id -u)" != "0" ] ; then + if [ "$IS_ACE_ENV" = "1" ] ; then + /opt/durapps/spark-store/bin/store-helper/pass-auth.sh "$0" "$@" + else + xhost + + pkexec "$0" "$@" + exit + fi +fi +HERE=$(dirname $0) +trap "rm -f /tmp/spark-store/upgradeStatus.txt" EXIT +source /opt/durapps/spark-store/bin/bashimport/transhell.amber +load_transhell_debug + +function get_name_from_desktop_file() { + local app_name_in_desktop + local name_orig + local name_i18n + local package_name + package_name=$1 + for desktop_file_path in $(dpkg -L "$package_name" |grep /usr/share/applications/ | awk '/\.desktop$/ {print}') ; do + if [ "$(grep -m 1 '^NoDisplay=' "$desktop_file_path" | cut -d '=' -f 2)" = "true" ] || [ "$(grep -m 1 '^NoDisplay=' "$desktop_file_path" | cut -d '=' -f 2)" = "True" ] ; then + continue + else + name_orig=$(awk -F= '/^\[Desktop Entry\]$/ {found=1} found && /^Name=/ {print $2; exit} /^\[.*\]$/ && !/\[Desktop Entry\]/ {exit}' "$desktop_file_path") + name_i18n=$(awk -v lang="Name[$LANGUAGE]" -F= '/^\[Desktop Entry\]$/ {found=1} found && /^Name\[/ && $1 == lang {print $2; exit} /^\[.*\]$/ && !/\[Desktop Entry\]/ {exit}' "$desktop_file_path") + if [ -z "$name_i18n" ] ; then + app_name_in_desktop=$name_orig + else + app_name_in_desktop=$name_i18n + fi + fi + done + + for desktop_file_path in $(dpkg -L "$package_name" |grep /opt/apps/$package_name/entries/applications | awk '/\.desktop$/ {print}') ; do + if [ "$(grep -m 1 '^NoDisplay=' "$desktop_file_path" | cut -d '=' -f 2)" = "true" ] || [ "$(grep -m 1 '^NoDisplay=' "$desktop_file_path" | cut -d '=' -f 2)" = "True" ] ; then + continue + else + name_orig=$(awk -F= '/^\[Desktop Entry\]$/ {found=1} found && /^Name=/ {print $2; exit} /^\[.*\]$/ && !/\[Desktop Entry\]/ {exit}' "$desktop_file_path") + name_i18n=$(awk -v lang="Name[$LANGUAGE]" -F= '/^\[Desktop Entry\]$/ {found=1} found && /^Name\[/ && $1 == lang {print $2; exit} /^\[.*\]$/ && !/\[Desktop Entry\]/ {exit}' "$desktop_file_path") + if [ -z "$name_i18n" ] ; then + app_name_in_desktop=$name_orig + else + app_name_in_desktop=$name_i18n + fi + fi + done + + if [ -z "$app_name_in_desktop" ] ; then + app_name_in_desktop=${package_name} + fi + + echo ${app_name_in_desktop} +} + +touch /tmp/spark-store/upgradeStatus.txt + +# 执行 apt update +pkexec ${HERE}/ss-do-upgrade-worker.sh ssupdate 2>&1 > /dev/null | zenity --progress --auto-close --pulsate --no-cancel --text="${TRANSHELL_CONTENT_UPDATE_CHEKING_PLEASE_WAIT}" --height 70 --width 400 --title="${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --window-icon=/usr/share/icons/hicolor/scalable/apps/spark-store.svg + +if [ -z `cat /tmp/spark-store-app-ssupdate-status.txt` ] ; then + ${HERE}/ss-do-upgrade-worker.sh clean-log +else + zenity --error --text "${TRANSHELL_CONTENT_CHECK_UPDATE_PROCESS_ERROR_PRESS_CONFIRM_TO_CHECK}" --title "${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --height 200 --width 350 --window-icon=/usr/share/icons/hicolor/scalable/apps/spark-store.svg + zenity --text-info --filename=/tmp/spark-store-app-ssupdate-log.txt --checkbox="${TRANSHELL_CONTENT_I_ALREDY_COPIED_THE_LOG_HERE_AND_WILL_USE_IT_TO_FEEDBACK}" --title="${TRANSHELL_CONTENT_FEEDBACK_CAN_BE_FOUND_IN_THE_SETTINGS}" --window-icon=/usr/share/icons/hicolor/scalable/apps/spark-store.svg + ${HERE}/ss-do-upgrade-worker.sh clean-log + rm -f /tmp/spark-store/upgradeStatus.txt + exit +fi + +# 获取可更新应用列表 +PKG_LIST="$(${HERE}/ss-do-upgrade-worker.sh upgradable-list)" +## 如果没更新,就弹出不需要更新 +if [ -z "$PKG_LIST" ] ; then + zenity --info --text "${TRANSHELL_CONTENT_NO_NEED_TO_UPGRADE}" --title "${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --height 150 --width 300 --window-icon=/usr/share/icons/hicolor/scalable/apps/spark-store.svg +else + ## 获取用户选择的要更新的应用 + ### 指定分隔符为 \n + IFS_OLD="$IFS" + IFS=$'\n' + + PKG_UPGRADE_LIST=$(for line in $PKG_LIST ; do + PKG_NAME=$(echo $line | awk -F ' ' '{print $1}') + PKG_NEW_VER=$(echo $line | awk -F ' ' '{print $2}') + PKG_CUR_VER=$(echo $line | awk -F ' ' '{print $3}') + + dpkg --compare-versions $PKG_NEW_VER le $PKG_CUR_VER + if [ $? -eq 0 ] ; then + continue + fi + APP_NAME=$(get_name_from_desktop_file $PKG_NAME) + #### 检测是否是 hold 状态 + PKG_STA=$(dpkg-query -W -f='${db:Status-Want}' $PKG_NAME) + if [ "$PKG_STA" != "hold" ] ; then + echo "true" + echo "$APP_NAME" + echo "$PKG_NEW_VER" + echo "$PKG_CUR_VER" + echo "$PKG_NAME" + else + echo "false" + echo "$APP_NAME${TRANSHELL_CONTENT_CAN_NOT_UPGRADE_FOR_BEING_HOLD}" + echo "$PKG_NEW_VER" + echo "$PKG_CUR_VER" + echo "$PKG_NAME" + fi +done) + + ### 还原分隔符 + IFS="$IFS_OLD" + + ## 如果没有应用需要更新,则直接退出 + if [ -z "$PKG_UPGRADE_LIST" ] ; then + zenity --info --text "${TRANSHELL_CONTENT_NO_NEED_TO_UPGRADE}" --title "${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --height 150 --width 300 + exit 0 + fi + + + while true;do + PKG_UPGRADE_LIST=$(echo "$PKG_UPGRADE_LIST" | zenity --list --text="${TRANSHELL_CONTENT_CHOOSE_APP_TO_UPGRADE}" --column="${TRANSHELL_CONTENT_CHOOSE}" --column="${TRANSHELL_CONTENT_APP_NAME}" --column="${TRANSHELL_CONTENT_NEW_VERSION}" --column="${TRANSHELL_CONTENT_UPGRADE_FROM}" --column="${TRANSHELL_CONTENT_PKG_NAME}" --separator=" " --checklist --multiple --print-column=5 --height 350 --width 650 ) + ## 如果没有选择,则直接退出 + if [ -z "$PKG_UPGRADE_LIST" ] ; then + zenity --info --text "${TRANSHELL_CONTENT_NO_APP_IS_CHOSEN}" --title "${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --height 150 --width 300 + exit 0 + fi + if [[ "$PKG_UPGRADE_LIST" == *"(null)"* ]]; then + zenity --error --text "${TRANSHELL_CONTENT_LIST_NOT_LOADED_PLEASE_WAIT}" --title "${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --height 150 --width 300 + else + break + fi +done + ### 更新用户选择的应用 +# for PKG_UPGRADE in $PKG_UPGRADE_LIST;do +# APP_UPGRADE="$(get_name_from_desktop_file $PKG_UPGRADE)" +# update_transhell + +( +total=$(echo "$PKG_UPGRADE_LIST" | wc -w) +count=0 + +for PKG_UPGRADE in $PKG_UPGRADE_LIST; do + count=$((count + 1)) + APP_UPGRADE="$(get_name_from_desktop_file $PKG_UPGRADE)" + update_transhell + + # 启动升级任务 + (yes n | pkexec ${HERE}/ss-do-upgrade-worker.sh upgrade-app $PKG_UPGRADE -yfq 2>&1 > /dev/null ) & + + # 计算进度百分比 + progress=$(( count * 100 / total - 1)) + + # 动态修改zenity的文本 + echo "# ${TRANSHELL_CONTENT_UPGRADING_PLEASE_WAIT}" + echo "$progress" + wait +done +) | zenity --progress --auto-close --no-cancel --text="Preparing..." --height 70 --width 400 --title="${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --window-icon=/usr/share/icons/hicolor/scalable/apps/spark-store.svg + + + #### 更新成功 + if [ -z "`cat /tmp/spark-store-app-upgrade-status.txt`" ] ; then + zenity --info --text "${TRANSHELL_CONTENT_CHOSEN_APP_UPGRADE_FINISHED}" --title "${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --height 150 --width 300 --window-icon=/usr/share/icons/hicolor/scalable/apps/spark-store.svg + else #### 更新异常 + zenity --error --text "${TRANSHELL_CONTENT_APP_UGRADE_PROCESS_ERROR_PRESS_CONFIRM_TO_CHECK}" --title "${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL}" --height 200 --width 350 --window-icon=/usr/share/icons/hicolor/scalable/apps/spark-store.svg + zenity --text-info --filename=/tmp/spark-store-app-upgrade-log.txt --checkbox="${TRANSHELL_CONTENT_I_ALREDY_COPIED_THE_LOG_HERE_AND_WILL_USE_IT_TO_FEEDBACK}" --title="${TRANSHELL_CONTENT_FEEDBACK_CAN_BE_FOUND_IN_THE_SETTINGS}" --window-icon=/usr/share/icons/hicolor/scalable/apps/spark-store.svg + fi +fi + +rm -f /tmp/spark-store/upgradeStatus.txt +# 从最开头 diff --git a/tool/update-upgrade/ss-update-controler.sh b/tool/update-upgrade/ss-update-controler.sh new file mode 100755 index 00000000..74f6130a --- /dev/null +++ b/tool/update-upgrade/ss-update-controler.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +source /opt/durapps/spark-store/bin/bashimport/transhell.amber +load_transhell_debug + +endloop=0 + +#####################检测是否启动过了更新检测工具 +while [ $endloop -eq 0 ] ;do + +if [ ! -e $HOME/.config/spark-union/spark-store/ssshell-config-do-not-show-upgrade-notify ];then +text_update_open="${TRANSHELL_CONTENT_CLOSE}" +#已经开启了就显示关闭 +else +text_update_open="${TRANSHELL_CONTENT_OPEN}" +fi + +if [ ! -e $HOME/.config/spark-union/spark-store/ssshell-config-do-not-create-desktop ];then +CONTENT_SET_CREATE_DESKTOP="${TRANSHELL_CONTENT_CLOSE_CREATE_DESKTOP}" +#已经开启了就显示关闭 +else +CONTENT_SET_CREATE_DESKTOP="${TRANSHELL_CONTENT_OPEN_CREATE_DESKTOP}" +fi + +update_transhell + +option=$(zenity --list --text="${TRANSHELL_CONTENT_WELCOME_AND_CHOOSE_ONE_TO_RUN}" --column 数字 --column=${TRANSHELL_CONTENT_OPTIONS} --print-column=2 --height 350 --width 760 0 "${TRANSHELL_CONTENT_OPEN_OR_CLOSE_UPGRADE_CHECK}" 1 "${CONTENT_SET_CREATE_DESKTOP}" 2 "${TRANSHELL_CONTENT_CHECK_FOR_UPDATE}" 3 "${TRANSHELL_CONTENT_EXIT}" --hide-column=1 --print-column=1) + +case $option in + 0) + if [ ! -e $HOME/.config/spark-union/spark-store/ssshell-config-do-not-show-upgrade-notify ];then + mkdir -p $HOME/.config/spark-union/spark-store/ + touch $HOME/.config/spark-union/spark-store/ssshell-config-do-not-show-upgrade-notify + zenity --info --icon-name=spark-store --height 150 --width 200 --text "${TRANSHELL_CONTENT_CLOSED}" --timeout=2 + else + rm -f $HOME/.config/spark-union/spark-store/ssshell-config-do-not-show-upgrade-notify + zenity --info --icon-name=spark-store --height 150 --width 200 --text "${TRANSHELL_CONTENT_OPENED}" --timeout=2 + fi + ;; + 1) + if [ ! -e $HOME/.config/spark-union/spark-store/ssshell-config-do-not-create-desktop ];then + mkdir -p $HOME/.config/spark-union/spark-store/ + touch $HOME/.config/spark-union/spark-store/ssshell-config-do-not-create-desktop + zenity --info --icon-name=spark-store --height 150 --width 200 --text "${TRANSHELL_CONTENT_CLOSED}" --timeout=2 + else + rm -f $HOME/.config/spark-union/spark-store/ssshell-config-do-not-create-desktop + zenity --info --icon-name=spark-store --height 150 --width 200 --text "${TRANSHELL_CONTENT_OPENED}" --timeout=2 + fi + ;; + 2) + /opt/durapps/spark-store/bin/update-upgrade/ss-do-upgrade.sh + ;; + + 3) + exit 0 + ;; + *) + exit 0 +esac + +done diff --git a/tool/update-upgrade/ss-update-notifier.sh b/tool/update-upgrade/ss-update-notifier.sh new file mode 100755 index 00000000..d4c52b5c --- /dev/null +++ b/tool/update-upgrade/ss-update-notifier.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +source /opt/durapps/spark-store/bin/bashimport/transhell.amber +load_transhell_debug + +############################################################# + +# 发送通知 +function notify-send() { + # Detect user using the display + local user=$(who | awk '{print $1}' | head -n 1) + + # Detect uid of the user + local uid=$(id -u $user) + + sudo -u $user DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus notify-send "$@" +} + +# 检测网络链接畅通 +function network-check() { + # 超时时间 + local timeout=15 + + # 目标网站 + local target=www.baidu.com + + # 获取响应状态码 + local ret_code=$(curl -I -s --connect-timeout ${timeout} ${target} -w %{http_code} | tail -n1) + + if [ "$ret_code" = "200" ]; then + # 网络畅通 + return 0 + else + # 网络不畅通 + return 1 + fi +} + +# 初始化等待时间和最大等待时间 +initial_wait_time=15 # 初始等待时间 15 秒 +max_wait_time=$((12 * 3600)) # 最大等待时间 12 小时 + +# 检测网络,若不通则进行重试,采用指数退避算法 +wait_time=$initial_wait_time +while ! network-check; do + echo "$TRANSHELL_CONTENT_NETWORK_FAIL" + echo "Waiting for network to recover... Retrying in ${wait_time} seconds." + + sleep $wait_time + wait_time=$((wait_time * 2)) # 等待时间翻倍 + if [ $wait_time -gt $max_wait_time ]; then + wait_time=$max_wait_time # 最大等待时间限制为12小时 + fi +done + +# 每日更新星火源文件 +aptss update + +updatetext=`LANGUAGE=en_US aptss ssupdate 2>&1` + +# 在网络恢复后,继续更新操作 +retry_count=0 +max_retries=12 # 最大重试次数,防止死循环 + +until ! echo $updatetext | grep -q "E:"; do + if [ $retry_count -ge $max_retries ]; then + echo "Reached maximum retry limit for aptss ssupdate." + exit 1 + fi + + echo "${TRANSHELL_CONTENT_UPDATE_ERROR_AND_WAIT_15_SEC}" + sleep 15 + updatetext=`LANGUAGE=en_US aptss ssupdate 2>&1` + retry_count=$((retry_count + 1)) +done + +update_app_number=$(env LANGUAGE=en_US /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf list --upgradable -o Dir::Etc::sourcelist="/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list" -o Dir::Etc::sourceparts="/dev/null" -o APT::Get::List-Cleanup="0" 2>/dev/null | grep -c upgradable) + +if [ "$update_app_number" -le 0 ]; then + exit 0 +fi + +# 读取忽略列表到数组 +declare -A ignored_apps +if [ -f "/etc/spark-store/ignored_apps.conf" ]; then + while IFS='|' read -r pkg_name pkg_version || [ -n "$pkg_name" ]; do + # 去除前后空白字符 + pkg_name=$(echo "$pkg_name" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + if [ -n "$pkg_name" ]; then + ignored_apps["$pkg_name"]=1 + fi + done < "/etc/spark-store/ignored_apps.conf" +fi + +# 获取用户选择的要更新的应用 +PKG_LIST="$(/opt/durapps/spark-store/bin/update-upgrade/ss-do-upgrade-worker.sh upgradable-list)" +# 指定分隔符为 \n +IFS_OLD="$IFS" +IFS=$'\n' + +for line in $PKG_LIST; do + PKG_NAME=$(echo $line | awk -F ' ' '{print $1}') + PKG_NEW_VER=$(echo $line | awk -F ' ' '{print $2}') + PKG_CUR_VER=$(echo $line | awk -F ' ' '{print $3}') + + dpkg --compare-versions $PKG_NEW_VER le $PKG_CUR_VER + + if [ $? -eq 0 ]; then + let update_app_number=$update_app_number-1 + continue + fi + + # 检测是否是 hold 状态 + PKG_STA=$(dpkg-query -W -f='${db:Status-Want}' $PKG_NAME) + if [ "$PKG_STA" = "hold" ]; then + let update_app_number=$update_app_number-1 + continue + fi + + # 检测是否在忽略列表中 + if [ -n "${ignored_apps[$PKG_NAME]}" ]; then + let update_app_number=$update_app_number-1 + continue + fi +done + +# 还原分隔符 +IFS="$IFS_OLD" +if [ $update_app_number -le 0 ]; then + exit 0 +fi +update_transhell + +# 如果都是hold或者版本一致的那就直接退出,否则把剩余的给提醒了 +# TODO: 除了apt-mark hold之外额外有一个禁止检查列表 +# 如果不想提示就不提示 + +user=$(who | awk '{print $1}' | head -n 1) +if [ -e "/home/$user/.config/spark-union/spark-store/ssshell-config-do-not-show-upgrade-notify" ]; then + echo "他不想站在世界之巅,好吧" + echo "Okay he don't want to be at the top of the world, okay" + exit +else + notify-send -a spark-store "${TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_NOTIFY}" "${TRANSHELL_CONTENT_THERE_ARE_APPS_TO_UPGRADE}" || true # Some machine don't have bus, or who command just print nothing. +fi diff --git a/tool/update-upgrade/transhell/ss-do-upgrade.sh_en_US.transhell b/tool/update-upgrade/transhell/ss-do-upgrade.sh_en_US.transhell new file mode 100644 index 00000000..b74f5449 --- /dev/null +++ b/tool/update-upgrade/transhell/ss-do-upgrade.sh_en_US.transhell @@ -0,0 +1,18 @@ +#!/bin/bash +TRANSHELL_CONTENT_UPDATE_CHEKING_PLEASE_WAIT="Checking for updates, please wait..." +TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL="Spark Store Update Module" +TRANSHELL_CONTENT_CHECK_UPDATE_PROCESS_ERROR_PRESS_CONFIRM_TO_CHECK="An error occurred during the update check process. Click Confirm to view the error log, which can be used for issue reporting." +TRANSHELL_CONTENT_I_ALREDY_COPIED_THE_LOG_HERE_AND_WILL_USE_IT_TO_FEEDBACK="I have copied the log in this text box and will attach it when submitting feedback. The feedback option can be found in the Settings menu at the top-right corner of the main interface." +TRANSHELL_CONTENT_FEEDBACK_CAN_BE_FOUND_IN_THE_SETTINGS="The feedback option can be found in the Settings menu at the top-right corner of the main interface." +TRANSHELL_CONTENT_NO_NEED_TO_UPGRADE="All packages are already up to date." +TRANSHELL_CONTENT_CAN_NOT_UPGRADE_FOR_BEING_HOLD="Unable to update: the current package status is marked as Hold." +TRANSHELL_CONTENT_CHOOSE_APP_TO_UPGRADE="Please select the application you want to update." +TRANSHELL_CONTENT_CHOOSE="Select" +TRANSHELL_CONTENT_APP_NAME="Application Name" +TRANSHELL_CONTENT_PKG_NAME="Package Name" +TRANSHELL_CONTENT_NEW_VERSION="New Version" +TRANSHELL_CONTENT_UPGRADE_FROM="Updating from this version" +TRANSHELL_CONTENT_NO_APP_IS_CHOSEN="You have not selected any application." +TRANSHELL_CONTENT_UPGRADING_PLEASE_WAIT="Updating $APP_UPGRADE, please wait..." +TRANSHELL_CONTENT_CHOSEN_APP_UPGRADE_FINISHED="The selected application has been successfully updated." +TRANSHELL_CONTENT_APP_UGRADE_PROCESS_ERROR_PRESS_CONFIRM_TO_CHECK="An error occurred during the update process. Click Confirm to view the error log, which can be used for issue reporting." \ No newline at end of file diff --git a/tool/update-upgrade/transhell/ss-do-upgrade.sh_zh_CN.transhell b/tool/update-upgrade/transhell/ss-do-upgrade.sh_zh_CN.transhell new file mode 100644 index 00000000..2a182374 --- /dev/null +++ b/tool/update-upgrade/transhell/ss-do-upgrade.sh_zh_CN.transhell @@ -0,0 +1,18 @@ +#!/bin/bash +TRANSHELL_CONTENT_UPDATE_CHEKING_PLEASE_WAIT="正在检查更新,请稍候……" +TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL="星火应用商店更新模块" +TRANSHELL_CONTENT_CHECK_UPDATE_PROCESS_ERROR_PRESS_CONFIRM_TO_CHECK="检查更新进程出现错误。单击“确定”查看错误日志,可用于问题反馈。" +TRANSHELL_CONTENT_I_ALREDY_COPIED_THE_LOG_HERE_AND_WILL_USE_IT_TO_FEEDBACK="我已复制且将会在反馈时附上此文本框中的日志,反馈渠道位于软件主界面右上角菜单的设置中。" +TRANSHELL_CONTENT_FEEDBACK_CAN_BE_FOUND_IN_THE_SETTINGS="反馈渠道位于软件主界面右上角菜单的设置中。" +TRANSHELL_CONTENT_NO_NEED_TO_UPGRADE="所有软件包版本已是最新。" +TRANSHELL_CONTENT_CAN_NOT_UPGRADE_FOR_BEING_HOLD="无法更新:当前软件包状态已被标记为“保留”。" +TRANSHELL_CONTENT_CHOOSE_APP_TO_UPGRADE="请选择您需要更新的软件。" +TRANSHELL_CONTENT_CHOOSE="选择" +TRANSHELL_CONTENT_APP_NAME="应用名" +TRANSHELL_CONTENT_PKG_NAME="软件包名" +TRANSHELL_CONTENT_NEW_VERSION="新版本" +TRANSHELL_CONTENT_UPGRADE_FROM="将从该版本更新" +TRANSHELL_CONTENT_NO_APP_IS_CHOSEN="您没有选中任何软件。" +TRANSHELL_CONTENT_UPGRADING_PLEASE_WAIT="正在更新 $APP_UPGRADE,请稍候……" +TRANSHELL_CONTENT_CHOSEN_APP_UPGRADE_FINISHED="选中的软件已更新完毕。" +TRANSHELL_CONTENT_APP_UGRADE_PROCESS_ERROR_PRESS_CONFIRM_TO_CHECK="更新进程出现错误。单击“确定”查看错误日志,可用于问题反馈。" \ No newline at end of file diff --git a/tool/update-upgrade/transhell/ss-do-upgrade.sh_zh_TW.transhell b/tool/update-upgrade/transhell/ss-do-upgrade.sh_zh_TW.transhell new file mode 100644 index 00000000..cd110a40 --- /dev/null +++ b/tool/update-upgrade/transhell/ss-do-upgrade.sh_zh_TW.transhell @@ -0,0 +1,18 @@ +#!/bin/bash +TRANSHELL_CONTENT_UPDATE_CHEKING_PLEASE_WAIT="正在檢查更新,請稍候……" +TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_MODEL="星火應用商店更新模組" +TRANSHELL_CONTENT_CHECK_UPDATE_PROCESS_ERROR_PRESS_CONFIRM_TO_CHECK="檢查更新行程出現錯誤。點擊「確定」查看錯誤日誌,可用於問題回饋。" +TRANSHELL_CONTENT_I_ALREDY_COPIED_THE_LOG_HERE_AND_WILL_USE_IT_TO_FEEDBACK="我已複製且將會在回饋時附上此文字框中的日誌,回饋渠道位於軟體主介面右上角選單的設定中。" +TRANSHELL_CONTENT_FEEDBACK_CAN_BE_FOUND_IN_THE_SETTINGS="回饋渠道位於軟體主介面右上角選單的設定中。" +TRANSHELL_CONTENT_NO_NEED_TO_UPGRADE="所有軟體包版本已是最新。" +TRANSHELL_CONTENT_CAN_NOT_UPGRADE_FOR_BEING_HOLD="無法更新:當前軟體包狀態已被標記為「保留」。" +TRANSHELL_CONTENT_CHOOSE_APP_TO_UPGRADE="請選擇您需要更新的軟體。" +TRANSHELL_CONTENT_CHOOSE="選擇" +TRANSHELL_CONTENT_APP_NAME="應用名稱" +TRANSHELL_CONTENT_PKG_NAME="軟體包名稱" +TRANSHELL_CONTENT_NEW_VERSION="新版本" +TRANSHELL_CONTENT_UPGRADE_FROM="將從該版本更新" +TRANSHELL_CONTENT_NO_APP_IS_CHOSEN="您沒有選中任何軟體。" +TRANSHELL_CONTENT_UPGRADING_PLEASE_WAIT="正在更新 $APP_UPGRADE,請稍候……" +TRANSHELL_CONTENT_CHOSEN_APP_UPGRADE_FINISHED="選中的軟體已更新完畢。" +TRANSHELL_CONTENT_APP_UGRADE_PROCESS_ERROR_PRESS_CONFIRM_TO_CHECK="更新行程出現錯誤。點擊「確定」查看錯誤日誌,可用於問題回饋。" \ No newline at end of file diff --git a/tool/update-upgrade/transhell/ss-update-controler.sh_en_US.transhell b/tool/update-upgrade/transhell/ss-update-controler.sh_en_US.transhell new file mode 100644 index 00000000..80c8cc43 --- /dev/null +++ b/tool/update-upgrade/transhell/ss-update-controler.sh_en_US.transhell @@ -0,0 +1,14 @@ +#!/bin/bash +TRANSHELL_CONTENT_CLOSE="Disable" +TRANSHELL_CONTENT_OPEN="Enable" +TRANSHELL_CONTENT_WELCOME_AND_CHOOSE_ONE_TO_RUN="Welcome to the Spark App Store update and installation settings tool. Please choose one of the following options to continue." +TRANSHELL_CONTENT_OPTIONS="Options" +TRANSHELL_CONTENT_OPEN_OR_CLOSE_UPGRADE_CHECK="$text_update_open Spark Store Update Check Tool (When enabled, this tool will automatically check for updates after system startup and display a notification if updates are available)" +TRANSHELL_CONTENT_CHECK_FOR_UPDATE="View the list of upgradable packages" +TRANSHELL_CONTENT_EXIT="Exit" +TRANSHELL_CONTENT_CLOSING_UPGRADE_CHECK="Please authorize to disable automatic update check." +TRANSHELL_CONTENT_CLOSED="Disabled" +TRANSHELL_CONTENT_OPENING_UPGRADE_CHECK="Please authorize to enable automatic update check." +TRANSHELL_CONTENT_OPENED="Enabled" +TRANSHELL_CONTENT_CLOSE_CREATE_DESKTOP="Disable automatic creation of desktop launcher" +TRANSHELL_CONTENT_OPEN_CREATE_DESKTOP="Enable automatic creation of desktop launcher" \ No newline at end of file diff --git a/tool/update-upgrade/transhell/ss-update-controler.sh_zh_CN.transhell b/tool/update-upgrade/transhell/ss-update-controler.sh_zh_CN.transhell new file mode 100644 index 00000000..901027be --- /dev/null +++ b/tool/update-upgrade/transhell/ss-update-controler.sh_zh_CN.transhell @@ -0,0 +1,14 @@ +#!/bin/bash +TRANSHELL_CONTENT_CLOSE="关闭" +TRANSHELL_CONTENT_OPEN="开启" +TRANSHELL_CONTENT_WELCOME_AND_CHOOSE_ONE_TO_RUN="欢迎使用星火应用商店更新与安装设置工具,请在以下选项中选择一个以继续。" +TRANSHELL_CONTENT_OPTIONS="操作选项" +TRANSHELL_CONTENT_OPEN_OR_CLOSE_UPGRADE_CHECK="$text_update_open星火更新检测工具(此工具于开启状态下将在系统启动后自动检测更新,如有更新则会弹出通知)" +TRANSHELL_CONTENT_CHECK_FOR_UPDATE="查看可更新软件包列表" +TRANSHELL_CONTENT_EXIT="退出" +TRANSHELL_CONTENT_CLOSING_UPGRADE_CHECK="执行“关闭自动更新检测”,请授权。" +TRANSHELL_CONTENT_CLOSED="已关闭" +TRANSHELL_CONTENT_OPENING_UPGRADE_CHECK="执行“开启自动更新检测”,请授权。" +TRANSHELL_CONTENT_OPENED="已开启" +TRANSHELL_CONTENT_CLOSE_CREATE_DESKTOP="关闭自动创建桌面启动器" +TRANSHELL_CONTENT_OPEN_CREATE_DESKTOP="开启自动创建桌面启动器" diff --git a/tool/update-upgrade/transhell/ss-update-controler.sh_zh_TW.transhell b/tool/update-upgrade/transhell/ss-update-controler.sh_zh_TW.transhell new file mode 100644 index 00000000..c60a4d2a --- /dev/null +++ b/tool/update-upgrade/transhell/ss-update-controler.sh_zh_TW.transhell @@ -0,0 +1,14 @@ +#!/bin/bash +TRANSHELL_CONTENT_CLOSE="關閉" +TRANSHELL_CONTENT_OPEN="開啟" +TRANSHELL_CONTENT_WELCOME_AND_CHOOSE_ONE_TO_RUN="歡迎使用星火應用商店更新與安裝設定工具,請在以下選項中選擇一個以繼續。" +TRANSHELL_CONTENT_OPTIONS="操作選項" +TRANSHELL_CONTENT_OPEN_OR_CLOSE_UPGRADE_CHECK="$text_update_open星火更新檢測工具(此工具於開啟狀態下將在系統啟動後自動檢測更新,如有更新則會彈出通知)" +TRANSHELL_CONTENT_CHECK_FOR_UPDATE="查看可更新軟體包清單" +TRANSHELL_CONTENT_EXIT="退出" +TRANSHELL_CONTENT_CLOSING_UPGRADE_CHECK="執行「關閉自動更新檢測」,請授權。" +TRANSHELL_CONTENT_CLOSED="已關閉" +TRANSHELL_CONTENT_OPENING_UPGRADE_CHECK="執行「開啟自動更新檢測」,請授權。" +TRANSHELL_CONTENT_OPENED="已開啟" +TRANSHELL_CONTENT_CLOSE_CREATE_DESKTOP="關閉自動建立桌面啟動器" +TRANSHELL_CONTENT_OPEN_CREATE_DESKTOP="開啟自動建立桌面啟動器" \ No newline at end of file diff --git a/tool/update-upgrade/transhell/ss-update-notifier.sh_en_US.transhell b/tool/update-upgrade/transhell/ss-update-notifier.sh_en_US.transhell new file mode 100644 index 00000000..70a88ba5 --- /dev/null +++ b/tool/update-upgrade/transhell/ss-update-notifier.sh_en_US.transhell @@ -0,0 +1,5 @@ +#!/bin/bash +TRANSHELL_CONTENT_NETWORK_FAIL="The current network is abnormal. To prevent blocking the dpkg process, the update has been paused." +TRANSHELL_CONTENT_UPDATE_ERROR_AND_WAIT_15_SEC="An error occurred during the update process. It will automatically retry in 15 seconds." +TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_NOTIFY="Spark Store Update Notifier" +TRANSHELL_CONTENT_THERE_ARE_APPS_TO_UPGRADE="There are ${update_app_number} packages available for update in the repository. Click to open the Spark Store menu for details." \ No newline at end of file diff --git a/tool/update-upgrade/transhell/ss-update-notifier.sh_zh_CN.transhell b/tool/update-upgrade/transhell/ss-update-notifier.sh_zh_CN.transhell new file mode 100644 index 00000000..35c68f4d --- /dev/null +++ b/tool/update-upgrade/transhell/ss-update-notifier.sh_zh_CN.transhell @@ -0,0 +1,5 @@ +#!/bin/bash +TRANSHELL_CONTENT_NETWORK_FAIL="当前网络异常,为防止阻塞 dpkg 进程,已暂停更新。" +TRANSHELL_CONTENT_UPDATE_ERROR_AND_WAIT_15_SEC="更新进程发生异常,将在 15 秒后自动重试。" +TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_NOTIFY="星火应用商店更新提醒" +TRANSHELL_CONTENT_THERE_ARE_APPS_TO_UPGRADE="软件仓库中有 $update_app_number 个软件包可供更新,点击前往星火应用商店菜单查看详情。" \ No newline at end of file diff --git a/tool/update-upgrade/transhell/ss-update-notifier.sh_zh_TW.transhell b/tool/update-upgrade/transhell/ss-update-notifier.sh_zh_TW.transhell new file mode 100644 index 00000000..071f71f9 --- /dev/null +++ b/tool/update-upgrade/transhell/ss-update-notifier.sh_zh_TW.transhell @@ -0,0 +1,5 @@ +#!/bin/bash +TRANSHELL_CONTENT_NETWORK_FAIL="當前網路異常,為防止阻塞 dpkg 行程,已暫停更新。" +TRANSHELL_CONTENT_UPDATE_ERROR_AND_WAIT_15_SEC="更新行程發生異常,將在 15 秒後自動重試。" +TRANSHELL_CONTENT_SPARK_STORE_UPGRADE_NOTIFY="星火應用商店更新提醒" +TRANSHELL_CONTENT_THERE_ARE_APPS_TO_UPGRADE="軟體倉庫中有 $update_app_number 個軟體包可供更新,點擊前往星火應用商店選單查看詳情。" \ No newline at end of file