mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 07:44:01 -04:00
scrollbar: interface style toggle and no scrollbars in the dash dash: never allow scrollbars cc: widget sheet cards, header close button and content-sized dialogs
189 lines
4.8 KiB
Go
189 lines
4.8 KiB
Go
package sysupdate
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
|
)
|
|
|
|
const ownerQueryTimeout = 10 * time.Second
|
|
|
|
var dmsPackageNames = map[string]bool{
|
|
"dms": true, "dms-git": true,
|
|
"dms-shell": true, "dms-shell-git": true, "dms-shell-bin": true,
|
|
}
|
|
|
|
// Arch/Debian/SUSE: 1.7.0+git4915.56234761; Fedora COPR: 0.0.git.4915.56234761
|
|
var gitBuildRe = regexp.MustCompile(`\+git(\d+)\.|\.git\.(\d+)\.`)
|
|
|
|
func GitBuildCount(version string) int {
|
|
m := gitBuildRe.FindStringSubmatch(version)
|
|
if m == nil {
|
|
return 0
|
|
}
|
|
for _, s := range m[1:] {
|
|
if s != "" {
|
|
n, _ := strconv.Atoi(s)
|
|
return n
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// "/usr/bin/dms (deleted)" after a package upgrade.
|
|
func dmsBinaryPath() string {
|
|
path, err := os.Executable()
|
|
if err != nil {
|
|
path, err = exec.LookPath("dms")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
}
|
|
path = strings.TrimSuffix(path, " (deleted)")
|
|
if resolved, err := filepath.EvalSymlinks(path); err == nil {
|
|
return resolved
|
|
}
|
|
return path
|
|
}
|
|
|
|
// Package managers unlink+create, so a different inode than /proc/self/exe means a new build.
|
|
func binaryReplaced(path string) bool {
|
|
if path == "" {
|
|
return false
|
|
}
|
|
running, err := os.Stat("/proc/self/exe")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
onDisk, err := os.Stat(path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return !os.SameFile(running, onDisk)
|
|
}
|
|
|
|
func unprobedShell(running string) ShellInfo {
|
|
info := ShellInfo{InstallMethod: InstallUnknown, Running: running, GitBuild: GitBuildCount(running)}
|
|
info.Channel = detectChannel(info, running)
|
|
return info
|
|
}
|
|
|
|
// Unowned binaries (manual copy, dev checkout) stay unknown: DMS only updates through packages.
|
|
func ProbeShell(ctx context.Context, running string) ShellInfo {
|
|
info := unprobedShell(running)
|
|
path := dmsBinaryPath()
|
|
|
|
if strings.HasPrefix(path, "/nix/store/") {
|
|
info.InstallMethod = InstallNix
|
|
} else if method, name, version := packageOwner(ctx, path); method != "" {
|
|
info.InstallMethod = method
|
|
info.PackageName = name
|
|
info.Installed = version
|
|
}
|
|
|
|
info.Channel = detectChannel(info, running)
|
|
info.RestartPending = binaryReplaced(path)
|
|
return info
|
|
}
|
|
|
|
func detectChannel(info ShellInfo, running string) Channel {
|
|
switch {
|
|
case strings.HasSuffix(info.PackageName, "-git"), GitBuildCount(running) > 0:
|
|
return ChannelGit
|
|
case info.InstallMethod == InstallUnknown, running == "", running == "dev":
|
|
return ChannelUnknown
|
|
default:
|
|
return ChannelStable
|
|
}
|
|
}
|
|
|
|
// Only one query runs on a normal system, but rpm can take a moment to open its database.
|
|
func packageOwner(ctx context.Context, path string) (InstallMethod, string, string) {
|
|
if path == "" {
|
|
return "", "", ""
|
|
}
|
|
ctx, cancel := context.WithTimeout(ctx, ownerQueryTimeout)
|
|
defer cancel()
|
|
|
|
switch {
|
|
case commandExists("pacman"):
|
|
// "/usr/bin/dms is owned by dms-shell-git 1.7.0+git4915-1"
|
|
out := runOutput(ctx, "pacman", "-Qo", path)
|
|
fields := strings.Fields(out)
|
|
if len(fields) < 2 {
|
|
return "", "", ""
|
|
}
|
|
return InstallPacman, fields[len(fields)-2], fields[len(fields)-1]
|
|
case commandExists("rpm"):
|
|
out := runOutput(ctx, "rpm", "-qf", "--qf", `%{NAME}\t%{VERSION}-%{RELEASE}`, path)
|
|
name, version, _ := strings.Cut(strings.TrimSpace(out), "\t")
|
|
if name == "" || strings.Contains(name, "not owned") {
|
|
return "", "", ""
|
|
}
|
|
return InstallRPM, name, version
|
|
case commandExists("dpkg-query"):
|
|
// "dms: /usr/bin/dms"
|
|
out := runOutput(ctx, "dpkg-query", "-S", path)
|
|
name, _, ok := strings.Cut(out, ":")
|
|
if !ok || name == "" {
|
|
return "", "", ""
|
|
}
|
|
name = strings.TrimSpace(name)
|
|
version := strings.TrimSpace(runOutput(ctx, "dpkg-query", "-W", "-f=${Version}", name))
|
|
return InstallDpkg, name, version
|
|
case commandExists("xbps-query"):
|
|
// "dms-1.7_1: /usr/bin/dms (regular file)"
|
|
out := runOutput(ctx, "xbps-query", "-o", path)
|
|
pkgver, _, found := strings.Cut(out, ":")
|
|
name, version, ok := splitPkgver(pkgver)
|
|
if !found || !ok {
|
|
return "", "", ""
|
|
}
|
|
return InstallXbps, name, version
|
|
}
|
|
return "", "", ""
|
|
}
|
|
|
|
func runOutput(ctx context.Context, name string, args ...string) string {
|
|
out, err := exec.CommandContext(ctx, name, args...).Output()
|
|
if err != nil {
|
|
log.Debugf("[sysupdate] %s %v: %v", name, args, err)
|
|
return ""
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
// Returns a copy: the packages slice is reused in place after upgrades.
|
|
func findShellPackage(pkgs []Package, ownerName string) *Package {
|
|
var fallback *Package
|
|
for i := range pkgs {
|
|
p := pkgs[i]
|
|
if ownerName != "" && p.Name == ownerName {
|
|
return &p
|
|
}
|
|
if fallback == nil && dmsPackageNames[p.Name] {
|
|
fallback = &p
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func commitsBehind(shell ShellInfo, master *MasterInfo) *int {
|
|
if master == nil || shell.Channel != ChannelGit {
|
|
return nil
|
|
}
|
|
n := GitBuildCount(shell.Running)
|
|
if n == 0 {
|
|
return nil
|
|
}
|
|
behind := max(master.CommitCount-n, 0)
|
|
return &behind
|
|
}
|