F-Droid desktop client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
4.9 KiB

// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/pelletier/go-toml/v2"
)
const cmdName = "fdroidcl"
const version = "v0.8.1"
func subdir(dir, name string) string {
p := filepath.Join(dir, name)
if err := os.MkdirAll(p, 0o755); err != nil {
fmt.Fprintf(os.Stderr, "Could not create dir '%s': %v\n", p, err)
}
return p
}
func mustCache() string {
dir, err := os.UserCacheDir()
if err != nil {
fmt.Fprintln(os.Stderr, err)
panic("TODO: return an error")
}
return subdir(dir, cmdName)
}
func mustData() string {
dir, err := os.UserConfigDir()
if err != nil {
fmt.Fprintln(os.Stderr, err)
panic("TODO: return an error")
}
return subdir(dir, cmdName)
}
func configPath() string {
return filepath.Join(mustData(), "config.toml")
}
type repo struct {
ID string `toml:"id"`
URL string `toml:"url"`
Enabled bool `toml:"enabled"`
}
type setup struct {
ID string `toml:"id"`
Apps []string `toml:"apps"`
Repos []string `toml:"repos"`
}
type userConfig struct {
Repos []repo `toml:"repos"`
Setups []setup `toml:"setups"`
}
var config = userConfig{
Repos: []repo{
{
ID: "f-droid",
URL: "https://f-droid.org/repo",
Enabled: true,
},
{
ID: "f-droid-archive",
URL: "https://f-droid.org/archive",
Enabled: false,
},
},
Setups: []setup{},
}
func readConfig() error {
f, err := os.Open(configPath())
if err != nil {
// ignore error, if file does not exist
return nil
}
defer f.Close()
fileConfig := userConfig{}
err = toml.NewDecoder(f).Decode(&fileConfig)
if err != nil {
return err
}
config = fileConfig
return nil
}
// A Command is an implementation of a go command
// like go build or go fix.
type Command struct {
// Run runs the command.
// The args are the arguments after the command name.
Run func(args []string) error
// UsageLine is the one-line usage message.
// The first word in the line is taken to be the command name.
UsageLine string
// Short is the short, single-line description.
Short string
// Long is an optional longer version of the Short description.
Long string
Fset flag.FlagSet
}
// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
name := c.UsageLine
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
func (c *Command) usage() {
fmt.Fprintf(os.Stderr, "usage: %s %s\n\n", cmdName, c.UsageLine)
if c.Long == "" {
fmt.Fprintf(os.Stderr, "%s.\n", c.Short)
} else {
fmt.Fprint(os.Stderr, c.Long)
}
anyFlags := false
c.Fset.VisitAll(func(f *flag.Flag) { anyFlags = true })
if anyFlags {
fmt.Fprintf(os.Stderr, "\nAvailable options:\n")
c.Fset.PrintDefaults()
}
}
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: %s [-h] <command> [<args>]\n\n", cmdName)
fmt.Fprintf(os.Stderr, "Available commands:\n")
maxUsageLen := 0
for _, c := range commands {
if len(c.UsageLine) > maxUsageLen {
maxUsageLen = len(c.UsageLine)
}
}
for _, c := range commands {
fmt.Fprintf(os.Stderr, " %s%s %s\n", c.UsageLine,
strings.Repeat(" ", maxUsageLen-len(c.UsageLine)), c.Short)
}
fmt.Fprintf(os.Stderr, `
An appid is just an app's unique package name. A specific version of an app can
be selected by following the appid with a colon and the version code. The
'search' and 'show' commands can be used to find these strings. For example:
$ fdroidcl search redreader
$ fdroidcl show org.quantumbadger.redreader
$ fdroidcl install org.quantumbadger.redreader:85
`)
fmt.Fprintf(os.Stderr, "\nUse %s <command> -h for more information.\n", cmdName)
}
}
// Commands lists the available commands.
var commands = []*Command{
cmdUpdate,
cmdSearch,
cmdShow,
cmdInstall,
cmdUninstall,
cmdDownload,
cmdDevices,
cmdScan,
cmdList,
cmdRepo,
cmdSetup,
cmdClean,
cmdDefaults,
cmdVersion,
}
var cmdVersion = &Command{
UsageLine: "version",
Short: "Print version information",
Run: func(args []string) error {
if len(args) > 0 {
return fmt.Errorf("no arguments allowed")
}
fmt.Println(version)
return nil
},
}
func main() {
os.Exit(main1())
}
func main1() int {
flag.Parse()
args := flag.Args()
if len(args) < 1 {
flag.Usage()
return 2
}
cmdName := args[0]
for _, cmd := range commands {
if cmd.Name() != cmdName {
continue
}
cmd.Fset.Init(cmdName, flag.ContinueOnError)
cmd.Fset.Usage = cmd.usage
if err := cmd.Fset.Parse(args[1:]); err != nil {
if err == flag.ErrHelp {
return 0
}
return 2
}
err := readConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "config %s: %v\n", configPath(), err)
return 1
}
if err := cmd.Run(cmd.Fset.Args()); err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", cmdName, err)
return 1
}
return 0
}
fmt.Fprintf(os.Stderr, "Unrecognised command '%s'\n\n", cmdName)
flag.Usage()
return 2
}