Skip to content

Commit

Permalink
cmd/watcher: use internal/localtime for timezones
Browse files Browse the repository at this point in the history
Allow user to specify dynamic or static timezones.
  • Loading branch information
kortschak committed May 19, 2024
1 parent fd783a6 commit 5a64c4c
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ jobs:
run: |
go test -v .
go test -p=1 -run TestDaemon ./cmd/* -verbose_log
go test -run TestDaemon ./cmd/watcher -dynamic_timezone=false -verbose_log
go test -run TestDaemon ./cmd/watcher -dynamic_timezone=true -verbose_log
- name: no xorg watcher
if: matrix.platform == 'ubuntu-latest'
Expand Down
9 changes: 5 additions & 4 deletions cmd/watcher/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ type Config struct {
LogLevel *slog.Level `json:"log_level,omitempty"`
AddSource *bool `json:"log_add_source,omitempty"`
Options struct {
Strategy string `json:"strategy,omitempty"`
Polling *rpc.Duration `json:"polling,omitempty"`
Heartbeat *rpc.Duration `json:"heartbeat,omitempty"`
Rules map[string]string `json:"rules,omitempty"`
DynamicLocation *bool `json:"dynamic_location,omitempty"`
Strategy string `json:"strategy,omitempty"`
Polling *rpc.Duration `json:"polling,omitempty"`
Heartbeat *rpc.Duration `json:"heartbeat,omitempty"`
Rules map[string]string `json:"rules,omitempty"`
} `json:"options,omitempty"`
}

Expand Down
78 changes: 76 additions & 2 deletions cmd/watcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
watcher "github.com/kortschak/dex/cmd/watcher/api"
"github.com/kortschak/dex/internal/celext"
"github.com/kortschak/dex/internal/device"
"github.com/kortschak/dex/internal/localtime"
"github.com/kortschak/dex/internal/slogext"
"github.com/kortschak/dex/internal/version"
"github.com/kortschak/dex/rpc"
Expand Down Expand Up @@ -132,6 +133,7 @@ func newDaemon(ctx context.Context, uid string, log *slog.Logger, level *slog.Le
log: log,
level: level,
addSource: addSource,
timezone: localtime.Static{},
detailer: noDetails{},
cancel: cancel,
}
Expand All @@ -149,7 +151,10 @@ func (d *daemon) dial(ctx context.Context, network, addr string, dialer net.Dial

func (d *daemon) close() error {
d.conn.Notify(context.Background(), rpc.Unregister, rpc.NewMessage(rpc.UID{Module: d.uid}, rpc.None{}))
return d.conn.Close()
return errors.Join(
closeCloser(d.timezone),
d.conn.Close(),
)
}

type daemon struct {
Expand All @@ -160,6 +165,8 @@ type daemon struct {

detailer detailer

timezone current

ctx context.Context
log *slog.Logger
level *slog.LevelVar
Expand All @@ -181,6 +188,10 @@ type detailer interface {
details() (watcher.Details, error)
}

type current interface {
Current() (*time.Location, error)
}

type noDetails struct{}

func (noDetails) strategy() string { return "none" }
Expand Down Expand Up @@ -224,6 +235,7 @@ func (d *daemon) Handle(ctx context.Context, req *jsonrpc2.Request) (any, error)
}
d.log.LogAttrs(ctx, slog.LevelDebug, "configure", slog.Any("details", m))

d.replaceTimezone(ctx, m.Body.Options.DynamicLocation)
d.replaceDetailer(ctx, m.Body.Options.Strategy)

if m.Body.Options.Rules != nil {
Expand Down Expand Up @@ -260,6 +272,55 @@ func (d *daemon) Handle(ctx context.Context, req *jsonrpc2.Request) (any, error)
}
}

func (d *daemon) replaceTimezone(ctx context.Context, dynamic *bool) {
if dynamic == nil {
return
}

d.pMu.Lock()
defer d.pMu.Unlock()

if is[localtime.Location](d.timezone) == *dynamic {
return
}

var (
tz current
err error
)
if *dynamic {
tz, err = localtime.NewLocation()
if err != nil {
d.log.LogAttrs(ctx, slog.LevelError, "configure", slog.Any("error", err))
return
}
} else {
tz = localtime.Static{}
}
err = closeCloser(d.timezone)
if err != nil {
d.log.LogAttrs(ctx, slog.LevelWarn, "configure", slog.Any("error", err))
}

var strategy string
switch tz.(type) {
case localtime.Static:
strategy = "static"
case *localtime.Location:
strategy = "dynamic"
default:
strategy = "unknown"
d.log.LogAttrs(ctx, slog.LevelError, "configure", slog.String("error", "unknown timezone strategy"))
}
d.log.LogAttrs(ctx, slog.LevelInfo, "configure", slog.String("timezone", strategy))
d.timezone = tz
}

func is[T any](v any) bool {
_, ok := v.(T)
return ok
}

func (d *daemon) replaceDetailer(ctx context.Context, strategy string) {
d.pMu.Lock()
defer d.pMu.Unlock()
Expand All @@ -278,10 +339,18 @@ func (d *daemon) replaceDetailer(ctx context.Context, strategy string) {
d.log.LogAttrs(ctx, slog.LevelWarn, "configure", slog.Any("error", err))
}
}
d.log.LogAttrs(ctx, slog.LevelInfo, "configure", slog.Any("strategy", det.strategy()))
d.log.LogAttrs(ctx, slog.LevelInfo, "configure", slog.String("strategy", det.strategy()))
d.detailer = det
}

func closeCloser(x any) error {
c, ok := x.(io.Closer)
if !ok {
return nil
}
return c.Close()
}

func (d *daemon) poll(ctx context.Context, p time.Duration) {
select {
case <-ctx.Done():
Expand Down Expand Up @@ -324,6 +393,11 @@ func (d *daemon) poll(ctx context.Context, p time.Duration) {
ticker.Stop()
return
case t := <-ticker.C:
loc, err := d.timezone.Current()
if err != nil {
d.log.LogAttrs(ctx, slog.LevelWarn, "polling current location", slog.Any("error", err))
}
t = t.In(loc)
d.log.LogAttrs(ctx, slog.LevelDebug, "polling", slog.Any("tick", t))
d.pMu.Lock()
details, err := d.detailer.details()
Expand Down
44 changes: 37 additions & 7 deletions cmd/watcher/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"sync"
"testing"
"time"
Expand All @@ -31,8 +32,35 @@ var (
verbose = flag.Bool("verbose_log", false, "print full logging")
lines = flag.Bool("show_lines", false, "log source code position")
strategy = flag.String("strategy", "", "details strategy")
timezone tern
)

func init() {
flag.Var(&timezone, "dynamic_timezone", "use dynamic timezone strategy")
}

type tern struct {
val *bool
}

func (t *tern) String() string {
if t.val == nil {
return "unset"
}
return strconv.FormatBool(*t.val)
}

func (t *tern) Set(f string) error {
b, err := strconv.ParseBool(f)
if err != nil {
return err
}
t.val = &b
return nil
}

func (t *tern) IsBoolFlag() bool { return true }

func TestDaemon(t *testing.T) {
if runtime.GOOS == "darwin" {
*strategy = ""
Expand Down Expand Up @@ -153,16 +181,18 @@ func TestDaemon(t *testing.T) {
var resp rpc.Message[string]

type options struct {
Strategy string `json:"strategy,omitempty"`
Polling *rpc.Duration `json:"polling,omitempty"`
Heartbeat *rpc.Duration `json:"heartbeat,omitempty"`
Rules map[string]string `json:"rules,omitempty"`
DynamicLocation *bool `json:"dynamic_location,omitempty"`
Strategy string `json:"strategy,omitempty"`
Polling *rpc.Duration `json:"polling,omitempty"`
Heartbeat *rpc.Duration `json:"heartbeat,omitempty"`
Rules map[string]string `json:"rules,omitempty"`
}
err := conn.Call(ctx, "configure", rpc.NewMessage(uid, watcher.Config{
Options: options{
Strategy: *strategy,
Polling: period,
Heartbeat: beat,
DynamicLocation: timezone.val,
Strategy: *strategy,
Polling: period,
Heartbeat: beat,
Rules: map[string]string{
"change": `
name.contains('tester') && (name != last.name || wid != last.wid || window != last.window) ?
Expand Down

0 comments on commit 5a64c4c

Please sign in to comment.