mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
Add support for getting binary icon and name from exe on Windows
This commit is contained in:
parent
19ad1817f2
commit
2a04bf33b1
10 changed files with 394 additions and 58 deletions
6
go.mod
6
go.mod
|
@ -4,6 +4,9 @@ go 1.21.1
|
|||
|
||||
toolchain go1.21.2
|
||||
|
||||
// TODO: Remove when https://github.com/tc-hib/winres/pull/4 is merged or changes are otherwise integrated.
|
||||
replace github.com/tc-hib/winres => github.com/dhaavi/winres v0.2.2
|
||||
|
||||
require (
|
||||
github.com/Xuanwo/go-locale v1.1.0
|
||||
github.com/agext/levenshtein v1.2.3
|
||||
|
@ -11,6 +14,7 @@ require (
|
|||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/florianl/go-conntrack v0.4.0
|
||||
github.com/florianl/go-nfqueue v1.3.1
|
||||
github.com/fogleman/gg v1.3.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
|
@ -18,6 +22,7 @@ require (
|
|||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/jackc/puddle/v2 v2.2.1
|
||||
github.com/mat/besticon v3.12.0+incompatible
|
||||
github.com/miekg/dns v1.1.57
|
||||
github.com/mitchellh/go-server-timing v1.0.1
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
|
@ -30,6 +35,7 @@ require (
|
|||
github.com/spkg/zipfs v0.7.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tannerryan/ring v1.1.2
|
||||
github.com/tc-hib/winres v0.2.1
|
||||
github.com/tevino/abool v1.2.0
|
||||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
|
||||
github.com/vincent-petithory/dataurl v1.0.0
|
||||
|
|
32
profile/icons/convert.go
Normal file
32
profile/icons/convert.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package icons
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/png" // Register png support for image package
|
||||
|
||||
"github.com/fogleman/gg"
|
||||
_ "github.com/mat/besticon/ico" // Register ico support for image package
|
||||
)
|
||||
|
||||
// ConvertICOtoPNG converts a an .ico to a .png image.
|
||||
func ConvertICOtoPNG(ico []byte) (png []byte, err error) {
|
||||
// Decode the ICO.
|
||||
icon, _, err := image.Decode(bytes.NewReader(ico))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode ICO: %w", err)
|
||||
}
|
||||
|
||||
// Convert to raw image.
|
||||
img := gg.NewContextForImage(icon)
|
||||
|
||||
// Convert to PNG.
|
||||
imgBuf := &bytes.Buffer{}
|
||||
err = img.EncodePNG(imgBuf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode PNG: %w", err)
|
||||
}
|
||||
|
||||
return imgBuf.Bytes(), nil
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
//go:build !linux
|
||||
//go:build !linux && !windows
|
||||
|
||||
package icons
|
||||
|
||||
import "context"
|
||||
|
||||
// FindIcon returns nil, nil for unsupported platforms.
|
||||
func FindIcon(ctx context.Context, binName string, homeDir string) (*Icon, error) {
|
||||
return nil, nil
|
||||
// GetIconAndName returns zero values for unsupported platforms.
|
||||
func GetIconAndName(ctx context.Context, binPath string, homeDir string) (icon *Icon, name string, err error) {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
|
|
@ -9,36 +9,46 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// FindIcon finds an icon for the given binary name.
|
||||
// Providing the home directory of the user running the process of that binary can help find an icon.
|
||||
func FindIcon(ctx context.Context, binName string, homeDir string) (*Icon, error) {
|
||||
// GetIconAndName returns an icon and name of the given binary path.
|
||||
// Providing the home directory of the user running the process of that binary can improve results.
|
||||
// Even if an error is returned, the other return values are valid, if set.
|
||||
func GetIconAndName(ctx context.Context, binPath string, homeDir string) (icon *Icon, name string, err error) {
|
||||
// Derive name from binary.
|
||||
name = GenerateBinaryNameFromPath(binPath)
|
||||
|
||||
// Search for icon.
|
||||
iconPath, err := search(binName, homeDir)
|
||||
iconPath, err := searchForIcon(binPath, homeDir)
|
||||
if iconPath == "" {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find icon for %s: %w", binName, err)
|
||||
return nil, name, fmt.Errorf("failed to find icon for %s: %w", binPath, err)
|
||||
}
|
||||
return nil, nil
|
||||
return nil, name, nil
|
||||
}
|
||||
|
||||
return LoadAndSaveIcon(ctx, iconPath)
|
||||
// Save icon to internal storage.
|
||||
icon, err = LoadAndSaveIcon(ctx, iconPath)
|
||||
if err != nil {
|
||||
return nil, name, fmt.Errorf("failed to store icon for %s: %w", binPath, err)
|
||||
}
|
||||
|
||||
return icon, name, nil
|
||||
}
|
||||
|
||||
func search(binName string, homeDir string) (iconPath string, err error) {
|
||||
binName = strings.ToLower(binName)
|
||||
func searchForIcon(binPath string, homeDir string) (iconPath string, err error) {
|
||||
binPath = strings.ToLower(binPath)
|
||||
|
||||
// Search for icon path.
|
||||
for _, iconLoc := range iconLocations {
|
||||
basePath := iconLoc.GetPath(binName, homeDir)
|
||||
basePath := iconLoc.GetPath(binPath, homeDir)
|
||||
if basePath == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch iconLoc.Type {
|
||||
case FlatDir:
|
||||
iconPath, err = searchDirectory(basePath, binName)
|
||||
iconPath, err = searchDirectory(basePath, binPath)
|
||||
case XDGIcons:
|
||||
iconPath, err = searchXDGIconStructure(basePath, binName)
|
||||
iconPath, err = searchXDGIconStructure(basePath, binPath)
|
||||
}
|
||||
|
||||
if iconPath != "" {
|
||||
|
@ -48,10 +58,10 @@ func search(binName string, homeDir string) (iconPath string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func searchXDGIconStructure(baseDirectory string, binName string) (iconPath string, err error) {
|
||||
func searchXDGIconStructure(baseDirectory string, binPath string) (iconPath string, err error) {
|
||||
for _, xdgIconDir := range xdgIconPaths {
|
||||
directory := filepath.Join(baseDirectory, xdgIconDir)
|
||||
iconPath, err = searchDirectory(directory, binName)
|
||||
iconPath, err = searchDirectory(directory, binPath)
|
||||
if iconPath != "" {
|
||||
return
|
||||
}
|
||||
|
@ -59,7 +69,7 @@ func searchXDGIconStructure(baseDirectory string, binName string) (iconPath stri
|
|||
return
|
||||
}
|
||||
|
||||
func searchDirectory(directory string, binName string) (iconPath string, err error) {
|
||||
func searchDirectory(directory string, binPath string) (iconPath string, err error) {
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
|
@ -82,13 +92,13 @@ func searchDirectory(directory string, binName string) (iconPath string, err err
|
|||
iconName := strings.ToLower(entry.Name())
|
||||
iconName = strings.TrimSuffix(iconName, filepath.Ext(iconName))
|
||||
switch {
|
||||
case len(iconName) < len(binName):
|
||||
case len(iconName) < len(binPath):
|
||||
// Continue to next.
|
||||
case iconName == binName:
|
||||
case iconName == binPath:
|
||||
// Exact match, return immediately.
|
||||
return filepath.Join(directory, entry.Name()), nil
|
||||
case strings.HasPrefix(iconName, binName):
|
||||
excessChars := len(iconName) - len(binName)
|
||||
case strings.HasPrefix(iconName, binPath):
|
||||
excessChars := len(iconName) - len(binPath)
|
||||
if bestMatch == "" || excessChars < bestMatchExcessChars {
|
||||
bestMatch = entry.Name()
|
||||
bestMatchExcessChars = excessChars
|
||||
|
|
|
@ -19,7 +19,7 @@ func TestFindIcon(t *testing.T) {
|
|||
func testFindIcon(t *testing.T, binName string, homeDir string) {
|
||||
t.Helper()
|
||||
|
||||
iconPath, err := search(binName, homeDir)
|
||||
iconPath, err := searchForIcon(binName, homeDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
|
115
profile/icons/find_windows.go
Normal file
115
profile/icons/find_windows.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package icons
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/tc-hib/winres"
|
||||
"github.com/tc-hib/winres/version"
|
||||
)
|
||||
|
||||
// GetIconAndName returns an icon and name of the given binary path.
|
||||
// Providing the home directory of the user running the process of that binary can improve results.
|
||||
// Even if an error is returned, the other return values are valid, if set.
|
||||
func GetIconAndName(ctx context.Context, binPath string, homeDir string) (icon *Icon, name string, err error) {
|
||||
// Get name and png from exe.
|
||||
png, name, err := getIconAndNamefromRSS(ctx, binPath)
|
||||
|
||||
// Fall back to name generation if name is not set.
|
||||
if name == "" {
|
||||
name = GenerateBinaryNameFromPath(binPath)
|
||||
}
|
||||
|
||||
// Handle previous error.
|
||||
if err != nil {
|
||||
return nil, name, err
|
||||
}
|
||||
|
||||
// Update profile icon and return icon object.
|
||||
filename, err := UpdateProfileIcon(png, "png")
|
||||
if err != nil {
|
||||
return nil, name, fmt.Errorf("failed to store icon: %w", err)
|
||||
}
|
||||
|
||||
return &Icon{
|
||||
Type: IconTypeAPI,
|
||||
Value: filename,
|
||||
}, name, nil
|
||||
}
|
||||
|
||||
func getIconAndNamefromRSS(ctx context.Context, binPath string) (png []byte, name string, err error) {
|
||||
// Open .exe file.
|
||||
exeFile, err := os.Open(binPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, "", nil
|
||||
}
|
||||
return nil, "", fmt.Errorf("failed to open exe %s to get icon: %w", binPath, err)
|
||||
}
|
||||
defer exeFile.Close() //nolint:errcheck
|
||||
|
||||
// Load .exe resources.
|
||||
rss, err := winres.LoadFromEXE(exeFile)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to get rss: %w", err)
|
||||
}
|
||||
|
||||
// DEBUG: Print all available resources:
|
||||
// rss.Walk(func(typeID, resID winres.Identifier, langID uint16, data []byte) bool {
|
||||
// fmt.Printf("typeID=%d resID=%d langID=%d\n", typeID, resID, langID)
|
||||
// return true
|
||||
// })
|
||||
|
||||
// Get first icon.
|
||||
var (
|
||||
icon *winres.Icon
|
||||
iconErr error
|
||||
)
|
||||
rss.WalkType(winres.RT_GROUP_ICON, func(resID winres.Identifier, langID uint16, _ []byte) bool {
|
||||
icon, iconErr = rss.GetIconTranslation(resID, langID)
|
||||
return iconErr != nil
|
||||
})
|
||||
if iconErr != nil {
|
||||
return nil, "", fmt.Errorf("failed to get icon: %w", err)
|
||||
}
|
||||
// Convert icon.
|
||||
icoBuf := &bytes.Buffer{}
|
||||
err = icon.SaveICO(icoBuf)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to save ico: %w", err)
|
||||
}
|
||||
png, err = ConvertICOtoPNG(icoBuf.Bytes())
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to convert ico to png: %w", err)
|
||||
}
|
||||
|
||||
// Get name from version record.
|
||||
var (
|
||||
versionInfo *version.Info
|
||||
versionInfoErr error
|
||||
)
|
||||
rss.WalkType(winres.RT_VERSION, func(resID winres.Identifier, langID uint16, data []byte) bool {
|
||||
versionInfo, versionInfoErr = version.FromBytes(data)
|
||||
switch {
|
||||
case versionInfoErr != nil:
|
||||
return true
|
||||
case versionInfo == nil:
|
||||
return true
|
||||
}
|
||||
|
||||
// Get metadata table and main language.
|
||||
table := versionInfo.Table().GetMainTranslation()
|
||||
if table == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
name = table[version.ProductName]
|
||||
return name == ""
|
||||
})
|
||||
name = cleanFileDescription(name)
|
||||
|
||||
return png, name, nil
|
||||
}
|
27
profile/icons/find_windows_test.go
Normal file
27
profile/icons/find_windows_test.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package icons
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFindIcon(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("test meant for compiling and running on desktop")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
binName := os.Args[len(os.Args)-1]
|
||||
t.Logf("getting name and icon for %s", binName)
|
||||
png, name, err := getIconAndNamefromRSS(context.Background(), binName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("name: %s", name)
|
||||
err = os.WriteFile("icon.png", png, 0o0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
121
profile/icons/name.go
Normal file
121
profile/icons/name.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package icons
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
segmentsSplitter = regexp.MustCompile("[^A-Za-z0-9]*[A-Z]?[a-z0-9]*")
|
||||
nameOnly = regexp.MustCompile("^[A-Za-z0-9]+$")
|
||||
delimitersAtStart = regexp.MustCompile("^[^A-Za-z0-9]+")
|
||||
delimitersOnly = regexp.MustCompile("^[^A-Za-z0-9]+$")
|
||||
removeQuotes = strings.NewReplacer(`"`, ``, `'`, ``)
|
||||
)
|
||||
|
||||
// GenerateBinaryNameFromPath generates a more human readable binary name from
|
||||
// the given path. This function is used as fallback in the GetBinaryName
|
||||
// functions.
|
||||
func GenerateBinaryNameFromPath(path string) string {
|
||||
// Get file name from path.
|
||||
_, fileName := filepath.Split(path)
|
||||
|
||||
// Split up into segments.
|
||||
segments := segmentsSplitter.FindAllString(fileName, -1)
|
||||
|
||||
// Remove last segment if it's an extension.
|
||||
if len(segments) >= 2 {
|
||||
switch strings.ToLower(segments[len(segments)-1]) {
|
||||
case
|
||||
".exe", // Windows Executable
|
||||
".msi", // Windows Installer
|
||||
".bat", // Windows Batch File
|
||||
".cmd", // Windows Command Script
|
||||
".ps1", // Windows Powershell Cmdlet
|
||||
".run", // Linux Executable
|
||||
".appimage", // Linux AppImage
|
||||
".app", // MacOS Executable
|
||||
".action", // MacOS Automator Action
|
||||
".out": // Generic Compiled Executable
|
||||
segments = segments[:len(segments)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// Debugging snippet:
|
||||
// fmt.Printf("segments: %s\n", segments)
|
||||
|
||||
// Go through segments and collect name parts.
|
||||
nameParts := make([]string, 0, len(segments))
|
||||
var fragments string
|
||||
for _, segment := range segments {
|
||||
// Group very short segments.
|
||||
if len(delimitersAtStart.ReplaceAllString(segment, "")) <= 2 {
|
||||
fragments += segment
|
||||
continue
|
||||
} else if fragments != "" {
|
||||
nameParts = append(nameParts, fragments)
|
||||
fragments = ""
|
||||
}
|
||||
|
||||
// Add segment to name.
|
||||
nameParts = append(nameParts, segment)
|
||||
}
|
||||
// Add last fragment.
|
||||
if fragments != "" {
|
||||
nameParts = append(nameParts, fragments)
|
||||
}
|
||||
|
||||
// Debugging snippet:
|
||||
// fmt.Printf("parts: %s\n", nameParts)
|
||||
|
||||
// Post-process name parts
|
||||
for i := range nameParts {
|
||||
// Remove any leading delimiters.
|
||||
nameParts[i] = delimitersAtStart.ReplaceAllString(nameParts[i], "")
|
||||
|
||||
// Title-case name-only parts.
|
||||
if nameOnly.MatchString(nameParts[i]) {
|
||||
nameParts[i] = strings.Title(nameParts[i]) //nolint:staticcheck
|
||||
}
|
||||
}
|
||||
|
||||
// Debugging snippet:
|
||||
// fmt.Printf("final: %s\n", nameParts)
|
||||
|
||||
return strings.Join(nameParts, " ")
|
||||
}
|
||||
|
||||
func cleanFileDescription(fileDescr string) string {
|
||||
fields := strings.Fields(fileDescr)
|
||||
|
||||
// Clean out and `"` and `'`.
|
||||
for i := range fields {
|
||||
fields[i] = removeQuotes.Replace(fields[i])
|
||||
}
|
||||
|
||||
// If there is a 1 or 2 character delimiter field, only use fields before it.
|
||||
endIndex := len(fields)
|
||||
for i, field := range fields {
|
||||
// Ignore the first field as well as fields with more than two characters.
|
||||
if i >= 1 && len(field) <= 2 && !nameOnly.MatchString(field) {
|
||||
endIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Concatenate name
|
||||
binName := strings.Join(fields[:endIndex], " ")
|
||||
|
||||
// If there are multiple sentences, only use the first.
|
||||
if strings.Contains(binName, ". ") {
|
||||
binName = strings.SplitN(binName, ". ", 2)[0]
|
||||
}
|
||||
|
||||
// If does not have any characters or numbers, return an empty string.
|
||||
if delimitersOnly.MatchString(binName) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.TrimSpace(binName)
|
||||
}
|
48
profile/icons/name_test.go
Normal file
48
profile/icons/name_test.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package icons
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenerateBinaryNameFromPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, "Nslookup", GenerateBinaryNameFromPath("nslookup.exe"))
|
||||
assert.Equal(t, "System Settings", GenerateBinaryNameFromPath("SystemSettings.exe"))
|
||||
assert.Equal(t, "One Drive Setup", GenerateBinaryNameFromPath("OneDriveSetup.exe"))
|
||||
assert.Equal(t, "Msedge", GenerateBinaryNameFromPath("msedge.exe"))
|
||||
assert.Equal(t, "SIH Client", GenerateBinaryNameFromPath("SIHClient.exe"))
|
||||
assert.Equal(t, "Openvpn Gui", GenerateBinaryNameFromPath("openvpn-gui.exe"))
|
||||
assert.Equal(t, "Portmaster Core v0-1-2", GenerateBinaryNameFromPath("portmaster-core_v0-1-2.exe"))
|
||||
assert.Equal(t, "Win Store App", GenerateBinaryNameFromPath("WinStore.App.exe"))
|
||||
assert.Equal(t, "Test Script", GenerateBinaryNameFromPath(".test-script"))
|
||||
assert.Equal(t, "Browser Broker", GenerateBinaryNameFromPath("browser_broker.exe"))
|
||||
assert.Equal(t, "Virtual Box VM", GenerateBinaryNameFromPath("VirtualBoxVM"))
|
||||
assert.Equal(t, "Io Elementary Appcenter", GenerateBinaryNameFromPath("io.elementary.appcenter"))
|
||||
assert.Equal(t, "Microsoft Windows Store", GenerateBinaryNameFromPath("Microsoft.WindowsStore"))
|
||||
}
|
||||
|
||||
func TestCleanFileDescription(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, "Product Name", cleanFileDescription("Product Name"))
|
||||
assert.Equal(t, "Product Name", cleanFileDescription("Product Name. Does this and that."))
|
||||
assert.Equal(t, "Product Name", cleanFileDescription("Product Name - Does this and that."))
|
||||
assert.Equal(t, "Product Name", cleanFileDescription("Product Name / Does this and that."))
|
||||
assert.Equal(t, "Product Name", cleanFileDescription("Product Name :: Does this and that."))
|
||||
assert.Equal(t, "/ Product Name", cleanFileDescription("/ Product Name"))
|
||||
assert.Equal(t, "Product", cleanFileDescription("Product / Name"))
|
||||
assert.Equal(t, "Software 2", cleanFileDescription("Software 2"))
|
||||
assert.Equal(t, "Launcher for Software 2", cleanFileDescription("Launcher for 'Software 2'"))
|
||||
assert.Equal(t, "", cleanFileDescription(". / Name"))
|
||||
assert.Equal(t, "", cleanFileDescription(". "))
|
||||
assert.Equal(t, "", cleanFileDescription("."))
|
||||
assert.Equal(t, "N/A", cleanFileDescription("N/A"))
|
||||
|
||||
assert.Equal(t,
|
||||
"Product Name a Does this and that.",
|
||||
cleanFileDescription("Product Name a Does this and that."),
|
||||
)
|
||||
}
|
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/utils"
|
||||
"github.com/safing/portbase/utils/osdetail"
|
||||
"github.com/safing/portmaster/intel/filterlists"
|
||||
"github.com/safing/portmaster/profile/endpoints"
|
||||
"github.com/safing/portmaster/profile/icons"
|
||||
|
@ -476,7 +475,7 @@ func (profile *Profile) updateMetadata(binaryPath string) (changed bool) {
|
|||
// Set Name if unset.
|
||||
if profile.Name == "" && profile.PresentationPath != "" {
|
||||
// Generate a default profile name from path.
|
||||
profile.Name = osdetail.GenerateBinaryNameFromPath(profile.PresentationPath)
|
||||
profile.Name = icons.GenerateBinaryNameFromPath(profile.PresentationPath)
|
||||
changed = true
|
||||
}
|
||||
|
||||
|
@ -514,38 +513,16 @@ func (profile *Profile) updateMetadataFromSystem(ctx context.Context, md Matchin
|
|||
return fmt.Errorf("tried to update metadata for non-local or non-path profile %s", profile.ScopedID())
|
||||
}
|
||||
|
||||
// Get binary name from PresentationPath.
|
||||
newName, err := osdetail.GetBinaryNameFromSystem(profile.PresentationPath)
|
||||
// Get home from ENV.
|
||||
var home string
|
||||
if env := md.Env(); env != nil {
|
||||
home = env["HOME"]
|
||||
}
|
||||
|
||||
// Get binary icon and name.
|
||||
newIcon, newName, err := icons.GetIconAndName(ctx, profile.PresentationPath, home)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, osdetail.ErrNotSupported):
|
||||
case errors.Is(err, osdetail.ErrNotFound):
|
||||
case errors.Is(err, osdetail.ErrEmptyOutput):
|
||||
default:
|
||||
log.Warningf("profile: error while getting binary name for %s: %s", profile.PresentationPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the new name is valid.
|
||||
if strings.TrimSpace(newName) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get icon if path matches presentation path.
|
||||
var newIcon *icons.Icon
|
||||
if profile.PresentationPath == md.Path() {
|
||||
// Get home from ENV.
|
||||
var home string
|
||||
if env := md.Env(); env != nil {
|
||||
home = env["HOME"]
|
||||
}
|
||||
var err error
|
||||
newIcon, err = icons.FindIcon(ctx, profile.PresentationPath, home)
|
||||
if err != nil {
|
||||
log.Warningf("profile: failed to find icon for %s: %s", profile.PresentationPath, err)
|
||||
newIcon = nil
|
||||
}
|
||||
log.Warningf("profile: failed to get binary icon/name for %s: %s", profile.PresentationPath, err)
|
||||
}
|
||||
|
||||
// Apply new data to profile.
|
||||
|
@ -555,7 +532,7 @@ func (profile *Profile) updateMetadataFromSystem(ctx context.Context, md Matchin
|
|||
defer profile.Unlock()
|
||||
|
||||
// Apply new name if it changed.
|
||||
if profile.Name != newName {
|
||||
if newName != "" && profile.Name != newName {
|
||||
profile.Name = newName
|
||||
changed = true
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue