Skip to content

Commit

Permalink
refactor: Remove global variables and complex import relationships
Browse files Browse the repository at this point in the history
  • Loading branch information
gabe565 committed May 3, 2024
1 parent c1674d3 commit a18bdec
Show file tree
Hide file tree
Showing 31 changed files with 591 additions and 503 deletions.
6 changes: 4 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
)

type Config struct {
File string `toml:"-"`
callbacks []func() `toml:"-"`

Title string `toml:"title" comment:"Tray title."`
URL string `toml:"url" comment:"Nightscout URL. (required)"`
Token string `toml:"token" comment:"Nightscout token. Using an access token is recommended instead of the API secret."`
Expand All @@ -31,10 +34,9 @@ type LocalFile struct {
Enabled bool `toml:"enabled"`
Format string `toml:"format" comment:"Local file format. (one of: csv)"`
Path string `toml:"path" comment:"Local file path. $TMPDIR will be replaced with the current temp directory."`
Cleanup bool `toml:"cleanup" comment:"If enabled, the local file will be cleaned up when Nightscout Menu Bar is closed."`
}

var configDir = "nightscout-menu-bar"
const configDir = "nightscout-menu-bar"

func GetDir() (string, error) {
switch runtime.GOOS {
Expand Down
11 changes: 4 additions & 7 deletions internal/config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import (
"time"
)

var Default = NewDefault()

const LocalFileFormatCsv = "csv"

func NewDefault() Config {
return Config{
func NewDefault() *Config {
return &Config{
Title: "Nightscout",
Units: UnitsMgdl,
Interval: Duration{30 * time.Second},
Expand All @@ -25,9 +23,8 @@ func NewDefault() Config {
Unknown: "-",
},
LocalFile: LocalFile{
Format: LocalFileFormatCsv,
Path: filepath.Join("$TMPDIR", "nightscout.csv"),
Cleanup: true,
Format: LocalFileFormatCsv,
Path: filepath.Join("$TMPDIR", "nightscout.csv"),
},
}
}
72 changes: 40 additions & 32 deletions internal/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package config

import (
"bytes"
"context"
"errors"
"log/slog"
"os"
Expand All @@ -18,33 +19,31 @@ import (
flag "github.com/spf13/pflag"
)

var cfgFile string

func init() {
flag.StringVarP(&cfgFile, "config", "c", "", "Config file")
func (conf *Config) RegisterFlags(fs *flag.FlagSet) {
fs.StringVarP(&conf.File, "config", "c", "", "Config file")
}

func Load() error {
func (conf *Config) Load() error {
flag.Parse()
k := koanf.New(".")

// Load default config
if err := k.Load(structs.Provider(Default, "toml"), nil); err != nil {
// Load conf config
if err := k.Load(structs.Provider(conf, "toml"), nil); err != nil {
return err
}

// Find config file
if cfgFile == "" {
if conf.File == "" {
cfgDir, err := GetDir()
if err != nil {
return err
}

cfgFile = filepath.Join(cfgDir, "config.toml")
conf.File = filepath.Join(cfgDir, "config.toml")
}

// Load config file if exists
cfgContents, err := os.ReadFile(cfgFile)
cfgContents, err := os.ReadFile(conf.File)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
Expand All @@ -55,32 +54,32 @@ func Load() error {
return err
}

if err := k.UnmarshalWithConf("", &Default, koanf.UnmarshalConf{Tag: "toml"}); err != nil {
if err := k.UnmarshalWithConf("", &conf, koanf.UnmarshalConf{Tag: "toml"}); err != nil {
return err
}

if err := Write(); err != nil {
if err := conf.Write(); err != nil {
return err
}

slog.Info("Loaded config", "file", cfgFile)
return err
slog.Info("Loaded config", "file", conf.File)
return nil
}

func Write() error {
func (conf *Config) Write() error {
// Find config file
if cfgFile == "" {
if conf.File == "" {
cfgDir, err := GetDir()
if err != nil {
return err
}

cfgFile = filepath.Join(cfgDir, "config.toml")
conf.File = filepath.Join(cfgDir, "config.toml")
}

var cfgNotExists bool
// Load config file if exists
cfgContents, err := os.ReadFile(cfgFile)
cfgContents, err := os.ReadFile(conf.File)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
cfgNotExists = true
Expand All @@ -89,47 +88,56 @@ func Write() error {
}
}

newCfg, err := toml.Marshal(Default)
newCfg, err := toml.Marshal(conf)
if err != nil {
return err
}

if !bytes.Equal(cfgContents, newCfg) {
if cfgNotExists {
slog.Info("Creating config", "file", cfgFile)
slog.Info("Creating config", "file", conf.File)

if err := os.MkdirAll(filepath.Dir(cfgFile), 0o777); err != nil {
if err := os.MkdirAll(filepath.Dir(conf.File), 0o777); err != nil {
return err
}
} else {
slog.Info("Updating config", "file", cfgFile)
slog.Info("Updating config", "file", conf.File)
}

if err := os.WriteFile(cfgFile, newCfg, 0o666); err != nil {
if err := os.WriteFile(conf.File, newCfg, 0o666); err != nil {
return err
}
}

return err
return nil
}

func Watch() error {
slog.Info("Watching config", "file", cfgFile)
f := file.Provider(cfgFile)
return f.Watch(func(event interface{}, err error) {
func (conf *Config) Watch(ctx context.Context) error {
slog.Info("Watching config", "file", conf.File)
f := file.Provider(conf.File)
return f.Watch(func(event any, err error) {
if err != nil {
slog.Error("Config watcher failed", "error", err.Error())
if ctx.Err() != nil {
conf.callbacks = nil
return
}
time.Sleep(time.Second)
defer func() {
_ = Watch()
_ = conf.Watch(ctx)
}()
}

if err := Load(); err != nil {
if err := conf.Load(); err != nil {
slog.Error("Failed to load config", "error", err.Error())
}
for _, reloader := range reloaders {
reloader()

for _, fn := range conf.callbacks {
fn()
}
})
}

func (conf *Config) AddCallback(fn func()) {
conf.callbacks = append(conf.callbacks, fn)
}
7 changes: 0 additions & 7 deletions internal/config/reload.go

This file was deleted.

134 changes: 134 additions & 0 deletions internal/fetch/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package fetch

import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"path"
"time"

"github.com/gabe565/nightscout-menu-bar/internal/config"
"github.com/gabe565/nightscout-menu-bar/internal/nightscout"
)

var (
ErrHttp = errors.New("unexpected HTTP error")
ErrNotModified = errors.New("not modified")
)

func NewFetch(conf *config.Config) *Fetch {
return &Fetch{
config: conf,
client: &http.Client{
Timeout: time.Minute,
},
}
}

type Fetch struct {
config *config.Config
client *http.Client
url *url.URL
tokenChecksum string
etag string
}

func (f *Fetch) Do() (*nightscout.Properties, error) {
if f.url == nil {
if err := f.UpdateUrl(); err != nil {
return nil, err
}
}

// Fetch JSON
req, err := http.NewRequest("GET", f.url.String(), nil)
if err != nil {
return nil, err
}
if f.etag != "" {
req.Header.Set("If-None-Match", f.etag)
}

if f.tokenChecksum != "" {
req.Header.Set("Api-Secret", f.tokenChecksum)
}

resp, err := f.client.Do(req)
if err != nil {
return nil, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()

switch resp.StatusCode {
case http.StatusNotModified:
return nil, ErrNotModified
case http.StatusOK:
// Decode JSON
var properties nightscout.Properties
if err := json.NewDecoder(resp.Body).Decode(&properties); err != nil {
return nil, err
}

f.etag = resp.Header.Get("etag")
return &properties, nil
default:
f.etag = ""
return nil, fmt.Errorf("%w: %d", ErrHttp, resp.StatusCode)
}
}

func (f *Fetch) UpdateUrl() error {
u, err := BuildUrl(f.config)
if err != nil {
return err
}

u.Path = path.Join(u.Path, "api", "v2", "properties", "bgnow,buckets,delta,direction")
f.url = u

if token := f.config.Token; token != "" {
rawChecksum := sha1.Sum([]byte(token))
f.tokenChecksum = hex.EncodeToString(rawChecksum[:])
} else {
f.tokenChecksum = ""
}

return nil
}

func (f *Fetch) Reset() {
f.url = nil
f.tokenChecksum = ""
f.etag = ""
}

func BuildUrl(conf *config.Config) (*url.URL, error) {
if conf.URL == "" {
return nil, errors.New("please configure your Nightscout URL")
}

return url.Parse(conf.URL)
}

func BuildUrlWithToken(conf *config.Config) (*url.URL, error) {
u, err := BuildUrl(conf)
if err != nil {
return u, err
}

if token := conf.Token; token != "" {
query := u.Query()
query.Set("token", conf.Token)
u.RawQuery = query.Encode()
}

return u, nil
}
Loading

0 comments on commit a18bdec

Please sign in to comment.