-
Notifications
You must be signed in to change notification settings - Fork 53
/
service.go
123 lines (110 loc) · 2.36 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package ntp
import (
"context"
"os"
"time"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
const TimeLayout = "2006-01-02 15:04:05 -0700"
type TimeService interface {
TimeFunc() func() time.Time
}
type Options struct {
Context context.Context
Server M.Socksaddr
Interval time.Duration
Dialer N.Dialer
Logger logger.Logger
}
var _ TimeService = (*Service)(nil)
type Service struct {
ctx context.Context
cancel common.ContextCancelCauseFunc
server M.Socksaddr
dialer N.Dialer
logger logger.Logger
ticker *time.Ticker
clockOffset time.Duration
}
func NewService(options Options) *Service {
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := common.ContextWithCancelCause(ctx)
destination := options.Server
if !destination.IsValid() {
destination = M.Socksaddr{
Fqdn: "time.google.com",
}
}
if destination.Port == 0 {
destination.Port = 123
}
var interval time.Duration
if options.Interval > 0 {
interval = options.Interval
} else {
interval = 30 * time.Minute
}
var dialer N.Dialer
if options.Dialer != nil {
dialer = options.Dialer
} else {
dialer = N.SystemDialer
}
return &Service{
ctx: ctx,
cancel: cancel,
server: destination,
dialer: dialer,
logger: options.Logger,
ticker: time.NewTicker(interval),
}
}
func (s *Service) Start() error {
err := s.update()
if err != nil {
return E.Cause(err, "initialize time")
}
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(TimeLayout))
go s.loopUpdate()
return nil
}
func (s *Service) Close() error {
s.ticker.Stop()
s.cancel(os.ErrClosed)
return nil
}
func (s *Service) TimeFunc() func() time.Time {
return func() time.Time {
return time.Now().Add(s.clockOffset)
}
}
func (s *Service) loopUpdate() {
for {
select {
case <-s.ctx.Done():
return
case <-s.ticker.C:
}
err := s.update()
if err == nil {
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(TimeLayout))
} else {
s.logger.Warn("update time: ", err)
}
}
}
func (s *Service) update() error {
response, err := Exchange(s.ctx, s.dialer, s.server)
if err != nil {
return err
}
s.clockOffset = response.ClockOffset
return nil
}