// This file is part of beagles.
//
// Copyright © 2020 Chris Palmer <chris@red-oxide.org>
//
// 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 <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"git.sr.ht/~chrisppy/beagles/config"
"git.sr.ht/~chrisppy/beagles/db"
"git.sr.ht/~chrisppy/beagles/ui"
"git.sr.ht/~sircmpwn/getopt"
wlog "github.com/DataDrake/waterlog"
"github.com/DataDrake/waterlog/format"
"github.com/DataDrake/waterlog/level"
"github.com/emersion/go-appdir"
)
var (
// Version of the application
Version string
)
func main() {
opts, optind, err := getopt.Getopts(os.Args, "c:d:Df:g:hl:s:V")
if err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
var configDir, dbDir, dlDir, logDir, gmniDir, iopath string
debug := false
for _, opt := range opts {
switch opt.Option {
case 'c':
configDir = opt.Value
case 'd':
dbDir = opt.Value
case 'D':
debug = true
case 'f':
iopath = opt.Value
case 'g':
gmniDir = opt.Value
case 'h':
printHelp()
case 'l':
logDir = opt.Value
case 's':
dlDir = opt.Value
case 'V':
printVersion()
}
}
dirs := appdir.New("beagles")
gmniPath, err := getGeminiPath(gmniDir)
if err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
cfg, err := getConfig(configDir, dirs)
if err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
db, err := getDatabase(dbDir, gmniPath, dirs)
if err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
db.Version = Version
dlDir, err = getDownloadDir(dlDir, dirs)
if err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
logger, err := getLogger(logDir, debug, dirs)
if err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
args := os.Args[optind:]
if len(args) > 1 {
if _, err := fmt.Fprintln(os.Stderr, "only a single command is allowed"); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
} else if len(args) > 0 {
if iopath == "" {
if _, err := fmt.Fprintln(os.Stderr, `"-f path" is required to run a command`); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
}
switch args[0] {
case "import":
if err := db.Import(iopath); err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
case "export":
if err := db.Export(iopath); err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
case "backup":
if err := db.Backup(iopath); err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
case "restore":
default:
if _, err := fmt.Fprintf(os.Stderr, "invalid command: %v\n", args); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
os.Exit(0)
}
i := &ui.UI{
Version: Version,
DB: db,
Config: cfg,
DownloadDataHome: dlDir,
GeminiDataHome: gmniPath,
Logger: logger,
}
i.Init()
if cfg.AutoUpdateInterval > 0 {
go i.UpdateLoop(cfg.AutoUpdateInterval)
}
if err := i.Run(); err != nil {
if _, err := fmt.Fprintln(os.Stderr, err); err != nil {
panic(fmt.Sprintf("error writing to stderr: %e\n", err))
}
os.Exit(1)
}
}
func getDatabase(dir string, gmniPath string, dirs appdir.Dirs) (*db.Storage, error) {
if dir == "" {
dir = dirs.UserData()
if _, err := os.Stat(dir); os.IsNotExist(err) {
if errDir := os.MkdirAll(dir, 0750); errDir != nil {
return nil, err
}
}
}
return db.ReadDB(filepath.Clean(filepath.Join(dir, "beagles.db")), gmniPath)
}
func getConfig(dir string, dirs appdir.Dirs) (*config.Config, error) {
if dir == "" {
dir = dirs.UserConfig()
if _, err := os.Stat(dir); os.IsNotExist(err) {
if errDir := os.MkdirAll(dir, 0750); errDir != nil {
return nil, err
}
}
}
return config.Load(dir)
}
func getLogger(dir string, debug bool, dirs appdir.Dirs) (*wlog.WaterLog, error) {
logger := wlog.New(os.Stdout, "", log.Ltime)
logger.SetFormat(format.Un)
if debug {
logger.SetLevel(level.Debug)
} else {
logger.SetLevel(level.Info)
}
if dir == "" {
dir = dirs.UserLogs()
if _, err := os.Stat(dir); os.IsNotExist(err) {
if errDir := os.MkdirAll(dir, 0750); errDir != nil {
return nil, err
}
}
}
logPath := filepath.Join(dir, "beagles.log")
f, err := os.OpenFile(filepath.Clean(logPath), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 00600)
if err != nil {
return nil, err
}
logger.SetOutput(f)
return logger, nil
}
func getDownloadDir(dir string, dirs appdir.Dirs) (string, error) {
if dir == "" {
dir = dirs.UserData()
if _, err := os.Stat(dir); os.IsNotExist(err) {
if errDir := os.MkdirAll(dir, 0750); errDir != nil {
return "", err
}
}
}
return dir, nil
}
func getGeminiPath(dir string) (string, error) {
if dir == "" {
dirs := appdir.New("gemini")
dir = dirs.UserData()
}
path := filepath.Join(dir, "known_hosts")
return path, nil
}
func printHelp() {
usage := `beagles version %s Copyright (c) 2020 the beagles developers
Usage: beagles [options]...
-c [dir] Set the config directory. If not set, use
XDG_CONFIG_HOME/beagles.
-d [dir] Set the database directory. If not set, use
XDG_DATA_HOME/beagles.
-D Print debug lines.
-g [dir] Set the directory for your gemini known_hosts.
If not set, use XDG_DATA_HOME/gemini.
-h Print basic options
-l [dir] Set the logging directory. If not set, use
XDG_CACHE_HOME/beagles.
-s [dir] Set the download directory. If not set, use
XDG_DATA_HOME/beagles.
-V Print version information
For more information consult the beagles man pages.
`
if _, err := fmt.Fprintf(os.Stdout, usage, Version); err != nil {
panic(fmt.Sprintf("error writing help page to stdout: %s\n", err.Error()))
}
os.Exit(0)
}
func printVersion() {
if _, err := fmt.Fprintf(os.Stdout, "beagles v%s\n", Version); err != nil {
panic(fmt.Sprintf("error writing version to stdout: %s\n", err.Error()))
}
os.Exit(0)
}