diff --git a/blinker.go b/blinker.go index eba56d2..67db293 100644 --- a/blinker.go +++ b/blinker.go @@ -105,7 +105,7 @@ func (blinker *BlinkerState) reinitialize() error { if blinker.failures > blinker.maxFailures { log.Fatalf("Unable to initialize blink(1): %v", err) } - fmt.Fprint(dotOut, "X") + printDot("X") } else { blinker.failures = 0 } @@ -121,22 +121,22 @@ func (blinker *BlinkerState) setState(state blink1.State) error { if blinker.failures > 0 { err := blinker.reinitialize() if err != nil { - fmt.Fprintf(debugOut, "Reinitialize failed, error %v\n", err) + errorLog("Reinitialize failed, error %v\n", err) return err } } err := blinker.device.SetState(state) if err != nil { - fmt.Fprintf(debugOut, "Re-initializing because of error %v\n", err) + errorLog("Re-initializing because of error %v\n", err) err = blinker.reinitialize() if err != nil { - fmt.Fprintf(debugOut, "Reinitialize failed, error %v\n", err) + errorLog("Reinitialize failed, error %v\n", err) return err } // Try one more time before giving up for this pass. err = blinker.device.SetState(state) if err != nil { - fmt.Fprintf(debugOut, "Setting blinker state failed, error %v\n", err) + errorLog("Setting blinker state failed, error %v\n", err) } } else { blinker.failures = 0 @@ -158,13 +158,13 @@ func (blinker *BlinkerState) patternRunner() { select { case newState := <-blinker.newState: if newState != currentState || failing { - fmt.Fprintf(debugOut, "Changing from state %v to %v\n", currentState, newState) + debugLog("Changing from state %v to %v\n", currentState, newState) currentState = newState if newState.primaryFlash > 0 || newState.secondaryFlash > 0 { ticker = time.After(time.Millisecond) } else { if ticker != nil { - fmt.Fprintf(debugOut, "Killing timer\n") + debugLog("Killing timer\n") ticker = nil } state1 := newState.primary @@ -176,11 +176,11 @@ func (blinker *BlinkerState) patternRunner() { failing = (err1 != nil) || (err2 != nil) } } else { - fmt.Fprintf(debugOut, "Retaining state %v unchanged\n", newState) + debugLog("Retaining state %v unchanged\n", newState) } case <-ticker: - fmt.Fprintf(debugOut, "Timer fired\n") + verboseLog("Timer fired\n") state1 := currentState.primary state2 := currentState.secondary if stateFlip { @@ -207,7 +207,7 @@ func (blinker *BlinkerState) patternRunner() { // We set state1 on LED 1 and state2 on LED 2. On an original (mk1) blink(1) state2 will be ignored. state1.LED = blink1.LED1 state2.LED = blink1.LED2 - fmt.Fprintf(debugOut, "Setting state (%v and %v)\n", state1, state2) + verboseLog("Setting state (%v and %v)\n", state1, state2) err1 := blinker.setState(state1) err2 := blinker.setState(state2) failing = (err1 != nil) || (err2 != nil) @@ -216,7 +216,7 @@ func (blinker *BlinkerState) patternRunner() { if state1.Duration == 0 { nextTick = state2.Duration } - fmt.Fprintf(debugOut, "Next tick: %s\n", nextTick) + verboseLog("Next tick: %s\n", nextTick) ticker = time.After(nextTick) } } @@ -232,7 +232,7 @@ func signalHandler(blinker *BlinkerState) { s := <-interrupt if s == syscall.SIGQUIT { fmt.Println("Turning on debug mode.") - debugOut = os.Stdout + debug = debugOn continue } if blinker.failures == 0 { diff --git a/calblink.go b/calblink.go index 0e9fc07..5f62b39 100644 --- a/calblink.go +++ b/calblink.go @@ -17,7 +17,6 @@ package main import ( "flag" "fmt" - "io" "log" "os" "time" @@ -27,6 +26,7 @@ import ( // flags var debugFlag = flag.Bool("debug", false, "Show debug messages") +var verboseFlag = flag.Bool("verbose", false, "Show verbose debug messages (forces --debug to true)") var clientSecretFlag = flag.String("clientsecret", "client_secret.json", "Path to JSON file containing client secret") var calNameFlag = flag.String("calendar", "primary", "Name of calendar to base blinker on (overrides value in config file)") var configFileFlag = flag.String("config", "conf.json", "Path to configuration file") @@ -37,8 +37,16 @@ var showDotsFlag = flag.Bool("show_dots", true, "Whether to show progress dots a var runAsServiceFlag = flag.Bool("runAsService", false, "Whether to run as a service or remain live in the current shell") var serviceFlag = flag.String("service", "", "Control the system service.") -var debugOut io.Writer = io.Discard -var dotOut io.Writer = io.Discard +type debugLevel int + +const ( + debugOff debugLevel = iota + debugOn + debugVerbose +) + +var debug debugLevel = debugOff +var dots bool = false // Necessary status for running as a service type program struct { @@ -59,6 +67,30 @@ func setHourMinuteFromTime(t time.Time) time.Time { return time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), 0, 0, now.Location()) } +// Log methods + +func errorLog(format string, args ...any) { + log.Printf(format, args...) +} + +func debugLog(format string, args ...any) { + if debug >= debugOn { + log.Printf(format, args...) + } +} + +func verboseLog(format string, args ...any) { + if debug >= debugVerbose { + log.Printf(format, args...) + } +} + +func printDot(s string) { + if dots { + fmt.Print(s) + } +} + // Print output methods func usage() { @@ -71,7 +103,11 @@ func main() { flag.Parse() if *debugFlag { - debugOut = os.Stdout + debug = debugOn + } + + if *verboseFlag { + debug = debugVerbose } userPrefs := readUserPrefs() @@ -102,7 +138,7 @@ func main() { }) if userPrefs.ShowDots && !isService { - dotOut = os.Stdout + dots = true } prg := &program{ @@ -155,31 +191,31 @@ func runLoop(p *program) { if userPrefs.SkipDays[weekday] { tomorrow := tomorrow() Black.Execute(blinkerState) - fmt.Fprintf(debugOut, "Sleeping until tomorrow (%v) because it's a skip day\n", tomorrow) - fmt.Fprint(dotOut, "~") + debugLog("Sleeping until tomorrow (%v) because it's a skip day\n", tomorrow) + printDot("~") nextEvent = tomorrow continue } if userPrefs.StartTime != nil { start := setHourMinuteFromTime(*userPrefs.StartTime) - fmt.Fprintf(debugOut, "Start time: %v\n", start) + debugLog("Start time: %v\n", start) if diff := time.Since(start); diff < 0 { Black.Execute(blinkerState) - fmt.Fprintf(debugOut, "Sleeping %v because start time after now\n", -diff) - fmt.Fprint(dotOut, ">") + debugLog("Sleeping %v because start time after now\n", -diff) + printDot(">") nextEvent = start continue } } if userPrefs.EndTime != nil { end := setHourMinuteFromTime(*userPrefs.EndTime) - fmt.Fprintf(debugOut, "End time: %v\n", end) + debugLog("End time: %v\n", end) if diff := time.Since(end); diff > 0 { Black.Execute(blinkerState) tomorrow := tomorrow() untilTomorrow := tomorrow.Sub(now) - fmt.Fprintf(debugOut, "Sleeping %v until tomorrow because end time %v before now\n", untilTomorrow, diff) - fmt.Fprint(dotOut, "<") + debugLog("Sleeping %v until tomorrow because end time %v before now\n", untilTomorrow, diff) + printDot("<") nextEvent = tomorrow continue } @@ -192,8 +228,8 @@ func runLoop(p *program) { if failures > failureRetries { MagentaFlash.Execute(blinkerState) } - fmt.Fprintf(os.Stderr, "Error receiving events from server:\n%v\n", err) - fmt.Fprint(dotOut, ",") + errorLog("Error receiving events from server:\n%v\n", err) + printDot(",") nextEvent = now.Add(time.Duration(userPrefs.PollInterval) * time.Second) continue } else { @@ -202,7 +238,7 @@ func runLoop(p *program) { blinkState := blinkStateForEvent(next, userPrefs.PriorityFlashSide) blinkState.Execute(blinkerState) - fmt.Fprint(dotOut, ".") + printDot(".") nextEvent = now.Add(time.Duration(userPrefs.PollInterval) * time.Second) } } diff --git a/calendar.go b/calendar.go index ddecd12..6116e70 100644 --- a/calendar.go +++ b/calendar.go @@ -17,7 +17,6 @@ package main import ( - "fmt" "log" "sort" "strings" @@ -33,8 +32,8 @@ func eventHasAcceptableResponse(item *calendar.Event, responseState ResponseStat return responseState.CheckStatus(attendee.ResponseStatus) } } - fmt.Fprintf(debugOut, "No self attendee found for %v\n", item) - fmt.Fprintf(debugOut, "Attendees: %v\n", item.Attendees) + debugLog("No self attendee found for %v\n", item) + debugLog("Attendees: %v\n", item.Attendees) return true } @@ -44,7 +43,7 @@ func eventExcludedByPrefs(item string, userPrefs *UserPrefs) bool { } for _, prefix := range userPrefs.ExcludePrefixes { if strings.HasPrefix(item, prefix) { - fmt.Fprintf(debugOut, "Skipping event '%v' due to prefix match '%v'\n", item, prefix) + debugLog("Skipping event '%v' due to prefix match '%v'\n", item, prefix) return true } } @@ -63,14 +62,14 @@ func nextEvent(items []*calendar.Event, locations []WorkSite, userPrefs *UserPre for _, prefLocation := range userPrefs.WorkingLocations { if locationSet[prefLocation] { - fmt.Fprintf(debugOut, "Found matching location: %v\n", prefLocation) + debugLog("Found matching location: %v\n", prefLocation) match = true break } } if !match { - fmt.Fprintf(debugOut, "Skipping all events due to no matching locations in %v\n", locations) + debugLog("Skipping all events due to no matching locations in %v\n", locations) return events } } @@ -85,7 +84,7 @@ func nextEvent(items []*calendar.Event, locations []WorkSite, userPrefs *UserPre } } } - fmt.Fprintf(debugOut, "nextEvent returning %d events\n", len(events)) + debugLog("nextEvent returning %d events\n", len(events)) return events } @@ -125,15 +124,15 @@ func blinkStateForEvent(next []*calendar.Event, priority int) CalendarState { } if (priority == 1 && blinkState.primaryFlash == 0 && blinkState.secondaryFlash > 0) || (priority == 2 && blinkState.primaryFlash > 0 && blinkState.secondaryFlash == 0) { - fmt.Fprintf(debugOut, "Swapping") + debugLog("Swapping") blinkState = SwapState(blinkState) } } - fmt.Fprintf(debugOut, "Event %v, time %v, delta %v, state %v\n", event.Summary, startTime, delta, blinkState.Name) + debugLog("Event %v, time %v, delta %v, state %v\n", event.Summary, startTime, delta, blinkState.Name) // Set priority. If priority is set, and the other light is flashing but the priority one isn't, swap them. } else { - fmt.Println(err) + errorLog("%v\n", err) break } } @@ -158,11 +157,11 @@ func fetchEvents(now time.Time, srv *calendar.Service, userPrefs *UserPrefs) ([] } for _, event := range events.Items { if event.EventType == "workingLocation" { - // The calendar event can return three or more working locations: + // The calendar event can return three or more working locations: // 1. The recurring one for the given day of the week // 2. The override for that particular day // 3. Any time overrides that are currently set for specific hours of the day. - // + // // The logic here isn't complicated enough to manage matching events to // a location, so instead, gather the latest all-date event and all // time overrides. Any event that matches one of those will have an @@ -170,7 +169,7 @@ func fetchEvents(now time.Time, srv *calendar.Service, userPrefs *UserPrefs) ([] isAllDay := (event.Start.DateTime == "") thisCreated, err := time.Parse(time.RFC3339, event.Created) if err != nil || (thisCreated.Before(locationCreated) && isAllDay) { - fmt.Fprintf(debugOut, "Skipping location event %v because it's before the current one\n", event.Summary) + debugLog("Skipping location event %v because it's before the current one\n", event.Summary) continue } locationProperties := event.WorkingLocationProperties @@ -186,10 +185,10 @@ func fetchEvents(now time.Time, srv *calendar.Service, userPrefs *UserPrefs) ([] location = WorkSite{SiteType: locationType, Name: locationString} locationCreated = thisCreated } else { - fmt.Fprintf(debugOut, "Location Override detected: calendar %v, location %v", calendar, location) + debugLog("Location Override detected: calendar %v, location %v", calendar, location) locations = append(locations, WorkSite{SiteType: locationType, Name: locationString}) } - fmt.Fprintf(debugOut, "Location detected: calendar %v, location %v\n", calendar, location) + debugLog("Location detected: calendar %v, location %v\n", calendar, location) } else if event.EventType == "outOfOffice" { // OOO events don't use an empty start time to indicate an all-day event. // Instead, check if the start is before our current window and the end @@ -197,22 +196,22 @@ func fetchEvents(now time.Time, srv *calendar.Service, userPrefs *UserPrefs) ([] eventStart, err1 := time.Parse(time.RFC3339, event.Start.DateTime) eventEnd, err2 := time.Parse(time.RFC3339, event.End.DateTime) if err1 != nil || err2 != nil { - fmt.Fprintf(debugOut, "Skipping event %v because of time parse errors: %v, %v\n", event.Summary, err1, err2) + debugLog("Skipping event %v because of time parse errors: %v, %v\n", event.Summary, err1, err2) } if eventStart.Before(now) && eventEnd.After(endTime) { - fmt.Fprintf(debugOut, "Skipping calendar %v due to OOO\n", calendar) + debugLog("Skipping calendar %v due to OOO\n", calendar) skip = true break } else { - fmt.Fprintf(debugOut, "Not applying OOO event %v to calendar %v\n", event, calendar) + debugLog("Not applying OOO event %v to calendar %v\n", event, calendar) } } } if !skip { if !locationCreated.IsZero() { - fmt.Fprintf(debugOut, "Adding final location %v\n", location) + debugLog("Adding final location %v\n", location) locations = append(locations, location) - fmt.Fprintf(debugOut, "Locations: %v\n", locations) + debugLog("Locations: %v\n", locations) } allEvents = append(allEvents, events.Items...) } @@ -223,15 +222,15 @@ func fetchEvents(now time.Time, srv *calendar.Service, userPrefs *UserPrefs) ([] seen := make(map[string]bool) for _, event := range allEvents { if seen[event.Id] { - fmt.Fprintf(debugOut, "Skipping duplicate event with ID %v\n", event.Id) + debugLog("Skipping duplicate event with ID %v\n", event.Id) continue } if event.EventType == "workingLocation" || event.EventType == "outOfOffice" { - fmt.Fprintf(debugOut, "Skipping working location/OOO event %v\n", event.Summary) + debugLog("Skipping working location/OOO event %v\n", event.Summary) continue } if event.Start.DateTime == "" { - fmt.Fprintf(debugOut, "Skipping all-day event %v\n", event.Summary) + debugLog("Skipping all-day event %v\n", event.Summary) continue } filtered = append(filtered, event) diff --git a/config.go b/config.go index 6c074bc..ed390b7 100644 --- a/config.go +++ b/config.go @@ -172,7 +172,7 @@ func makeWorkSite(location string) WorkSite { if len(split) > 1 { name = split[1] } - fmt.Fprintf(debugOut, "Work Site: type %v, name %v", siteType, name) + debugLog("Work Site: type %v, name %v", siteType, name) return WorkSite{SiteType: siteType, Name: name} } @@ -190,14 +190,14 @@ func readUserPrefs() *UserPrefs { defer file.Close() if err != nil { // Lack of a config file is not a fatal error. - fmt.Fprintf(debugOut, "Unable to read config file %v : %v\n", *configFileFlag, err) + debugLog("Unable to read config file %v : %v\n", *configFileFlag, err) return userPrefs } prefs := prefLayout{} decoder := json.NewDecoder(file) decoder.DisallowUnknownFields() err = decoder.Decode(&prefs) - fmt.Fprintf(debugOut, "Decoded prefs: %v\n", prefs) + debugLog("Decoded prefs: %v\n", prefs) if err != nil { log.Fatalf("Unable to parse config file %v", err) } @@ -217,7 +217,7 @@ func readUserPrefs() *UserPrefs { } userPrefs.Excludes = make(map[string]bool) for _, item := range prefs.Excludes { - fmt.Fprintf(debugOut, "Excluding item %v\n", item) + debugLog("Excluding item %v\n", item) userPrefs.Excludes[item] = true } userPrefs.ExcludePrefixes = prefs.ExcludePrefixes @@ -261,7 +261,7 @@ func readUserPrefs() *UserPrefs { for _, location := range prefs.WorkingLocations { userPrefs.WorkingLocations = append(userPrefs.WorkingLocations, makeWorkSite(location)) } - fmt.Fprintf(debugOut, "User prefs: %v\n", userPrefs) + debugLog("User prefs: %v\n", userPrefs) return userPrefs } diff --git a/network.go b/network.go index 96baaee..7d9683a 100644 --- a/network.go +++ b/network.go @@ -86,13 +86,13 @@ type handler struct { } func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - fmt.Fprintln(debugOut, "Starting HTTP handler") + debugLog("Starting HTTP handler\n") url := req.URL if url.Path == "/" { fmt.Fprintf(w, "Token received. You can close this window.") val := url.Query() code := val["code"][0] - fmt.Fprintf(debugOut, "Received code %v\n", code) + debugLog("Received code %v\n", code) go h.srv.Shutdown(context.Background()) h.rChan <- code } else {