Skip to content

Commit 8f198ff

Browse files
committed
Add initial project files and setup for flatpak-alias tool
1 parent bbd0024 commit 8f198ff

9 files changed

Lines changed: 412 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ go.work.sum
2323

2424
# env file
2525
.env
26+
# Added by goreleaser init:
27+
dist/

.goreleaser.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# This is an example .goreleaser.yml file with some sensible defaults.
2+
# Make sure to check the documentation at https://goreleaser.com
3+
4+
# The lines below are called `modelines`. See `:help modeline`
5+
# Feel free to remove those if you don't want/need to use them.
6+
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
7+
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
8+
9+
version: 2
10+
11+
before:
12+
hooks:
13+
# You may remove this if you don't use go modules.
14+
- go mod tidy
15+
# you may remove this if you don't need go generate
16+
# - go generate ./...
17+
18+
builds:
19+
- env:
20+
- CGO_ENABLED=0
21+
goos:
22+
- linux
23+
ldflags:
24+
- -s -w
25+
main: .
26+
binary: flatpak-alias.trigger
27+
28+
archives:
29+
- format: tar.gz
30+
# this name template makes the OS and Arch compatible with the results of `uname`.
31+
name_template: >-
32+
{{ .ProjectName }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }}
33+
files:
34+
- README.md
35+
- LICENSE
36+
37+
changelog:
38+
sort: asc
39+
filters:
40+
exclude:
41+
- "^docs:"
42+
- "^test:"
43+
44+
nfpms:
45+
- package_name: flatpak-alias
46+
description: A tool that generates flatpak aliases automatically!
47+
homepage: https://github.com/RedCommand/flatpak-alias
48+
bindir: /usr/share/flatpak/triggers/
49+
maintainer: RedCommand
50+
formats:
51+
- deb
52+
- rpm
53+
- archlinux
54+
- ipk
55+
# license: MIT
56+
57+
58+
release:
59+
footer: >-
60+
61+
---
62+
63+
Released by [GoReleaser](https://github.com/goreleaser/goreleaser).

flatpak.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package main
2+
3+
import (
4+
"os/exec"
5+
"slices"
6+
"strings"
7+
"sync"
8+
9+
"github.com/hbollon/go-edlib"
10+
"github.com/rs/zerolog/log"
11+
"gopkg.in/ini.v1"
12+
)
13+
14+
func getApps() ([]string, error) {
15+
cmd := exec.Command("flatpak", "list", "--app")
16+
cmd.Dir = "/var/lib/flatpak"
17+
18+
log.Debug().Str("command", cmd.Path+strings.Join(cmd.Args, " ")).Msg("Running command")
19+
out, err := cmd.Output()
20+
if err != nil {
21+
return nil, err
22+
}
23+
24+
appsLine := strings.Split(string(out), "\n")
25+
var apps []string
26+
for _, app := range appsLine {
27+
app = strings.TrimSpace(app)
28+
if app == "" {
29+
continue
30+
}
31+
data := strings.Split(app, "\t")
32+
if len(data) < 2 {
33+
log.Warn().Msgf("Skipping line: %s", app)
34+
continue
35+
}
36+
appID := strings.TrimSpace(data[1])
37+
if appID == "" {
38+
continue
39+
}
40+
log.Trace().Msgf("AppID: %s", appID)
41+
apps = append(apps, appID)
42+
}
43+
return apps, nil
44+
}
45+
46+
func getFlatpakApp(appID string) (Flatpak, error) {
47+
cmd := exec.Command("flatpak", "info", "-m", appID)
48+
49+
log.Debug().Str("command", cmd.Path+strings.Join(cmd.Args, " ")).Str("appID", appID).Msg("Running command")
50+
out, err := cmd.Output()
51+
if err != nil {
52+
return Flatpak{}, err
53+
}
54+
55+
log.Trace().Str("data", string(out)).Msg("Parsing toml")
56+
var flatpak Flatpak
57+
cfg, err := ini.Load(out)
58+
if err != nil {
59+
return Flatpak{}, err
60+
}
61+
err = cfg.MapTo(&flatpak)
62+
if err != nil {
63+
return Flatpak{}, err
64+
}
65+
66+
flatpak.Application.Command = strings.TrimSpace(flatpak.Application.Command)
67+
68+
rank := edlib.LevenshteinDistance(flatpak.Application.Command, appID)
69+
if rank <= 0 {
70+
if rank < 0 {
71+
log.Warn().Str("appID", appID).Str("command", flatpak.Application.Command).Int("rank", rank).Msg("AppID and AppName do not match")
72+
} else {
73+
log.Debug().Str("appID", appID).Str("command", flatpak.Application.Command).Int("rank", rank).Msg("AppID and AppName are the same")
74+
}
75+
flatpak.Application.Command = strings.ToLower(appID[strings.LastIndex(appID, ".")+1:])
76+
} else {
77+
log.Debug().Str("appID", appID).Str("command", flatpak.Application.Command).Int("rank", rank).Msg("AppID and AppName match")
78+
}
79+
80+
flatpak.Application.Command = strings.ToLower(flatpak.Application.Command)
81+
82+
return flatpak, nil
83+
}
84+
85+
func removeDuplicates(apps []Flatpak) []Flatpak {
86+
keys := make(map[string]*Flatpak)
87+
list := []Flatpak{}
88+
duplicates := make(map[string][]Flatpak)
89+
for _, app := range apps {
90+
if f, ok := keys[app.Application.Command]; !ok {
91+
keys[app.Application.Command] = &app
92+
list = append(list, app)
93+
} else {
94+
if len(duplicates[app.Application.Command]) == 0 {
95+
duplicates[app.Application.Command] = append(duplicates[app.Application.Command], *f, app)
96+
} else {
97+
duplicates[app.Application.Command] = append(duplicates[app.Application.Command], app)
98+
}
99+
}
100+
}
101+
return slices.DeleteFunc(list, func(app Flatpak) bool {
102+
apps, ok := duplicates[app.Application.Command]
103+
if ok {
104+
log.Warn().Str("command", app.Application.Command).Interface("apps", apps).Msg("Found duplicates")
105+
}
106+
return ok
107+
})
108+
}
109+
110+
func getAllFlatpakApps() []Flatpak {
111+
appsID, err := getApps()
112+
if err != nil {
113+
log.Fatal().Err(err).Msg("Error getting apps")
114+
}
115+
116+
apps := make([]Flatpak, len(appsID))
117+
wg := sync.WaitGroup{}
118+
for i, appID := range appsID {
119+
wg.Add(1)
120+
go func(appID string) {
121+
defer wg.Done()
122+
app, err := getFlatpakApp(appID)
123+
if err != nil {
124+
log.Error().Err(err).Str("appID", appID).Msg("Error getting app")
125+
}
126+
// No need to lock since we are writing to different indexes
127+
apps[i] = app
128+
}(appID)
129+
}
130+
wg.Wait()
131+
132+
apps = removeDuplicates(apps)
133+
134+
return apps
135+
}

go.mod

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module github.com/RedCommand/flatpak-alias
2+
3+
go 1.23.4
4+
5+
require (
6+
github.com/hbollon/go-edlib v1.6.0
7+
github.com/rs/zerolog v1.33.0
8+
gopkg.in/ini.v1 v1.67.0
9+
gopkg.in/natefinch/lumberjack.v2 v2.2.1
10+
)
11+
12+
require (
13+
github.com/mattn/go-colorable v0.1.13 // indirect
14+
github.com/mattn/go-isatty v0.0.19 // indirect
15+
github.com/stretchr/testify v1.10.0 // indirect
16+
golang.org/x/sys v0.12.0 // indirect
17+
)

go.sum

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
5+
github.com/hbollon/go-edlib v1.6.0 h1:ga7AwwVIvP8mHm9GsPueC0d71cfRU/52hmPJ7Tprv4E=
6+
github.com/hbollon/go-edlib v1.6.0/go.mod h1:wnt6o6EIVEzUfgbUZY7BerzQ2uvzp354qmS2xaLkrhM=
7+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
8+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
9+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
10+
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
11+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
12+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
13+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15+
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
16+
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
17+
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
18+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
19+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
20+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
21+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
22+
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
23+
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24+
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
25+
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
26+
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
27+
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
28+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
29+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

install.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
go build -o flatpak-alias.trigger .
6+
sudo cp flatpak-alias.trigger /usr/share/flatpak/triggers/flatpak-alias.trigger
7+
sudo chmod +x /usr/share/flatpak/triggers/flatpak-alias.trigger

main.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package main
2+
3+
import (
4+
_ "embed"
5+
"os"
6+
"path"
7+
"text/template"
8+
"time"
9+
10+
"github.com/rs/zerolog"
11+
"github.com/rs/zerolog/log"
12+
"gopkg.in/natefinch/lumberjack.v2"
13+
)
14+
15+
type Config struct {
16+
FlatpakDir string
17+
}
18+
19+
type Flatpak struct {
20+
Application struct {
21+
Name string `ini:"name"`
22+
Command string `ini:"command"`
23+
} `ini:"Application"`
24+
Timestamp time.Time `ini:"-"`
25+
}
26+
27+
//go:embed script.gotmpl
28+
var scriptTemplateRaw string
29+
30+
func parseArgs() Config {
31+
config := Config{}
32+
if len(os.Args) > 1 {
33+
config.FlatpakDir = os.Args[1]
34+
} else {
35+
config.FlatpakDir = "/var/lib/flatpak"
36+
}
37+
return config
38+
}
39+
40+
func prepare(config *Config) (*template.Template, *lumberjack.Logger) {
41+
logFile := &lumberjack.Logger{
42+
Filename: path.Join(config.FlatpakDir, "flatpak-alias.log"), // Path to the log file
43+
MaxSize: 1, // Maximum size in MB before rotation
44+
MaxBackups: 3, // Maximum number of old log files to retain
45+
MaxAge: 365, // Maximum number of days to retain old log files
46+
Compress: true, // Compress rotated files
47+
}
48+
49+
consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}
50+
51+
multiWriter := zerolog.MultiLevelWriter(consoleWriter, logFile)
52+
53+
log.Logger = zerolog.New(multiWriter).With().Timestamp().Logger()
54+
55+
zerolog.SetGlobalLevel(zerolog.InfoLevel)
56+
57+
scriptTemplate, err := template.New("script").Parse(scriptTemplateRaw)
58+
if err != nil {
59+
log.Fatal().Err(err).Msg("Error parsing template")
60+
}
61+
62+
config.FlatpakDir = path.Join(config.FlatpakDir, "aliases")
63+
log.Info().Str("path", config.FlatpakDir).Msg("Writing script")
64+
err = os.MkdirAll(config.FlatpakDir, 0755)
65+
if err != nil {
66+
log.Fatal().Err(err).Msg("Error creating directory")
67+
}
68+
69+
return scriptTemplate, logFile
70+
}
71+
72+
func main() {
73+
config := parseArgs()
74+
scriptTemplate, logFile := prepare(&config)
75+
defer logFile.Close()
76+
log.Info().Msg("Starting flatpak-alias")
77+
log.Info().Str("path", config.FlatpakDir).Any("args", os.Args).Msg("Writing script")
78+
apps := getAllFlatpakApps()
79+
removeOldScripts(config)
80+
generateScripts(apps, scriptTemplate, config)
81+
}

script.gotmpl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
# **Generated by flatpak-alias**
3+
# {{.Application.Command}} - Flatpak Wrapper
4+
# Generated on: {{.Timestamp}}
5+
# This script is a wrapper to start the Flatpak application {{.Application.Name}} using the alias '{{.Application.Command}}'.
6+
7+
set -e
8+
9+
# Check if Flatpak is installed
10+
if ! command -v flatpak &> /dev/null; then
11+
echo "Error: Flatpak is not installed." >&2
12+
exit 1
13+
fi
14+
15+
# Execute the Flatpak application
16+
flatpak run {{.Application.Name}} "$@"

0 commit comments

Comments
 (0)