@@ -22,6 +22,8 @@ import (
22
22
"log"
23
23
"os"
24
24
"time"
25
+
26
+ "github.com/kardianos/service"
25
27
)
26
28
27
29
// flags
@@ -33,10 +35,19 @@ var pollIntervalFlag = flag.Int("poll_interval", 30, "Number of seconds between
33
35
var responseStateFlag = flag .String ("response_state" , "notRejected" , "Which events to consider based on response: all, accepted, or notRejected" )
34
36
var deviceFailureRetriesFlag = flag .Int ("device_failure_retries" , 10 , "Number of times to retry initializing the device before quitting the program" )
35
37
var showDotsFlag = flag .Bool ("show_dots" , true , "Whether to show progress dots after every cycle of checking the calendar" )
38
+ var runAsServiceFlag = flag .Bool ("runAsService" , false , "Whether to run as a service or remain live in the current shell" )
39
+ var serviceFlag = flag .String ("service" , "" , "Control the system service." )
36
40
37
41
var debugOut io.Writer = ioutil .Discard
38
42
var dotOut io.Writer = ioutil .Discard
39
43
44
+ // Necessary status for running as a service
45
+ type program struct {
46
+ service service.Service
47
+ userPrefs * UserPrefs
48
+ exit chan struct {}
49
+ }
50
+
40
51
// Time calculation methods
41
52
42
53
func tomorrow () time.Time {
@@ -49,18 +60,6 @@ func setHourMinuteFromTime(t time.Time) time.Time {
49
60
return time .Date (now .Year (), now .Month (), now .Day (), t .Hour (), t .Minute (), 0 , 0 , now .Location ())
50
61
}
51
62
52
- func sleep (d time.Duration ) {
53
- // To fix the 'oversleeping' problem where we sleep too long if the machine goes to
54
- // sleep in the meantime, sleep for no more than 5 minutes at once.
55
- // TODO: Once the AbsoluteNow proposal goes in, replace this with that.
56
- max := time .Duration (5 ) * time .Minute
57
- if d > max {
58
- fmt .Fprintf (debugOut , "Cutting sleep short from %d to %d" , d , max )
59
- d = max
60
- }
61
- time .Sleep (d )
62
- }
63
-
64
63
// Print output methods
65
64
66
65
func usage () {
@@ -75,8 +74,10 @@ func main() {
75
74
if * debugFlag {
76
75
debugOut = os .Stdout
77
76
}
78
-
77
+
79
78
userPrefs := readUserPrefs ()
79
+ isService := false
80
+ serviceCmd := ""
80
81
81
82
// Overrides from command-line
82
83
flag .Visit (func (myFlag * flag.Flag ) {
@@ -94,13 +95,32 @@ func main() {
94
95
userPrefs .DeviceFailureRetries = myFlag .Value .(flag.Getter ).Get ().(int )
95
96
case "show_dots" :
96
97
userPrefs .ShowDots = myFlag .Value .(flag.Getter ).Get ().(bool )
98
+ case "runAsService" :
99
+ isService = myFlag .Value .(flag.Getter ).Get ().(bool )
100
+ case "service" :
101
+ serviceCmd = myFlag .Value .String ()
97
102
}
98
103
})
99
104
100
- if userPrefs .ShowDots {
105
+ if userPrefs .ShowDots && ! isService {
101
106
dotOut = os .Stdout
102
107
}
103
108
109
+ prg := & program {
110
+ userPrefs : userPrefs ,
111
+ exit : make (chan struct {}),
112
+ }
113
+
114
+ if isService {
115
+ prg .StartService (serviceCmd )
116
+ } else {
117
+ runLoop (prg )
118
+ }
119
+
120
+ }
121
+
122
+ func runLoop (p * program ) {
123
+ userPrefs := p .userPrefs
104
124
srv , err := Connect ()
105
125
if err != nil {
106
126
log .Fatalf ("Unable to retrieve Calendar client: %v" , err )
@@ -111,67 +131,80 @@ func main() {
111
131
go signalHandler (blinkerState )
112
132
go blinkerState .patternRunner ()
113
133
114
- printStartInfo (userPrefs )
134
+ if p .service == nil {
135
+ printStartInfo (userPrefs )
136
+ } else {
137
+ fmt .Printf ("Calblink starting at %v\n " , time .Now ())
138
+ }
115
139
140
+ ticker := time .NewTicker (time .Second )
141
+ nextEvent := time .Now ()
116
142
failures := 0
117
143
118
144
for {
119
- now := time .Now ()
120
- weekday := now .Weekday ()
121
- if userPrefs .SkipDays [weekday ] {
122
- tomorrow := tomorrow ()
123
- untilTomorrow := tomorrow .Sub (now )
124
- Black .Execute (blinkerState )
125
- fmt .Fprintf (debugOut , "Sleeping %v until tomorrow because it's a skip day\n " , untilTomorrow )
126
- fmt .Fprint (dotOut , "~" )
127
- sleep (untilTomorrow )
128
- continue
129
- }
130
- if userPrefs .StartTime != nil {
131
- start := setHourMinuteFromTime (* userPrefs .StartTime )
132
- fmt .Fprintf (debugOut , "Start time: %v\n " , start )
133
- if diff := time .Since (start ); diff < 0 {
134
- Black .Execute (blinkerState )
135
- fmt .Fprintf (debugOut , "Sleeping %v because start time after now\n " , - diff )
136
- fmt .Fprint (dotOut , ">" )
137
- sleep (- diff )
145
+ select {
146
+ case <- p .exit :
147
+ blinkerState .turnOff ()
148
+ fmt .Printf ("Calblink exiting at %v\n " , time .Now ())
149
+ ticker .Stop ()
150
+ return
151
+ case now := <- ticker .C :
152
+ if nextEvent .After (now ) {
138
153
continue
139
154
}
140
- }
141
- if userPrefs .EndTime != nil {
142
- end := setHourMinuteFromTime (* userPrefs .EndTime )
143
- fmt .Fprintf (debugOut , "End time: %v\n " , end )
144
- if diff := time .Since (end ); diff > 0 {
145
- Black .Execute (blinkerState )
155
+ weekday := now .Weekday ()
156
+ if userPrefs .SkipDays [weekday ] {
146
157
tomorrow := tomorrow ()
147
- untilTomorrow := tomorrow . Sub ( now )
148
- fmt .Fprintf (debugOut , "Sleeping %v until tomorrow because end time %v before now \n " , untilTomorrow , diff )
149
- fmt .Fprint (dotOut , "< " )
150
- sleep ( untilTomorrow )
158
+ Black . Execute ( blinkerState )
159
+ fmt .Fprintf (debugOut , "Sleeping until tomorrow (%v) because it's a skip day \n " , tomorrow )
160
+ fmt .Fprint (dotOut , "~ " )
161
+ nextEvent = tomorrow
151
162
continue
152
163
}
153
- }
154
- next , err := fetchEvents (now , srv , userPrefs )
155
- if err != nil {
156
- // Leave the same color, set a flag. If we get more than a critical number of these,
157
- // set the color to blinking magenta to tell the user we are in a failed state.
158
- failures ++
159
- if failures > failureRetries {
160
- MagentaFlash .Execute (blinkerState )
164
+ if userPrefs .StartTime != nil {
165
+ start := setHourMinuteFromTime (* userPrefs .StartTime )
166
+ fmt .Fprintf (debugOut , "Start time: %v\n " , start )
167
+ if diff := time .Since (start ); diff < 0 {
168
+ Black .Execute (blinkerState )
169
+ fmt .Fprintf (debugOut , "Sleeping %v because start time after now\n " , - diff )
170
+ fmt .Fprint (dotOut , ">" )
171
+ nextEvent = start
172
+ continue
173
+ }
161
174
}
162
- fmt .Fprintf (debugOut , "Fetch Error:\n %v\n " , err )
163
- fmt .Fprint (dotOut , "," )
164
- sleep (time .Duration (userPrefs .PollInterval ) * time .Second )
165
- continue
166
- } else {
167
- failures = 0
168
- }
169
- blinkState := blinkStateForEvent (next , userPrefs .PriorityFlashSide )
175
+ if userPrefs .EndTime != nil {
176
+ end := setHourMinuteFromTime (* userPrefs .EndTime )
177
+ fmt .Fprintf (debugOut , "End time: %v\n " , end )
178
+ if diff := time .Since (end ); diff > 0 {
179
+ Black .Execute (blinkerState )
180
+ tomorrow := tomorrow ()
181
+ untilTomorrow := tomorrow .Sub (now )
182
+ fmt .Fprintf (debugOut , "Sleeping %v until tomorrow because end time %v before now\n " , untilTomorrow , diff )
183
+ fmt .Fprint (dotOut , "<" )
184
+ nextEvent = tomorrow
185
+ continue
186
+ }
187
+ }
188
+ next , err := fetchEvents (now , srv , userPrefs )
189
+ if err != nil {
190
+ // Leave the same color, set a flag. If we get more than a critical number of these,
191
+ // set the color to blinking magenta to tell the user we are in a failed state.
192
+ failures ++
193
+ if failures > failureRetries {
194
+ MagentaFlash .Execute (blinkerState )
195
+ }
196
+ fmt .Fprintf (debugOut , "Fetch Error:\n %v\n " , err )
197
+ fmt .Fprint (dotOut , "," )
198
+ nextEvent = now .Add (time .Duration (userPrefs .PollInterval ) * time .Second )
199
+ continue
200
+ } else {
201
+ failures = 0
202
+ }
203
+ blinkState := blinkStateForEvent (next , userPrefs .PriorityFlashSide )
170
204
171
- blinkState .Execute (blinkerState )
172
- fmt .Fprint (dotOut , "." )
173
- sleep (time .Duration (userPrefs .PollInterval ) * time .Second )
205
+ blinkState .Execute (blinkerState )
206
+ fmt .Fprint (dotOut , "." )
207
+ nextEvent = now .Add (time .Duration (userPrefs .PollInterval ) * time .Second )
208
+ }
174
209
}
175
-
176
-
177
210
}
0 commit comments