/
main.go
133 lines (114 loc) · 3.38 KB
/
main.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
124
125
126
127
128
129
130
131
132
133
package main
import (
"bufio"
"flag"
"fmt"
"io"
"log"
"net/smtp"
"os"
"os/signal"
"time"
"github.com/alecholmes/southwest_flight_watcher/app"
"github.com/alecholmes/southwest_flight_watcher/client"
)
const (
smtpPort = 587
searchesFlagStr = "searchesFile"
fromFlagStr = "from"
toFlagStr = "to"
smtpFlagStr = "smtp"
smtpPasswordFileStr = "smtpPasswordFile"
)
func main() {
searchesFlag := flag.String(searchesFlagStr, "", "filename of flight searches JSON")
fromFlag := flag.String(fromFlagStr, "", "email address from which updates are sent")
toFlag := flag.String(toFlagStr, "", "email address to which updates are sent")
smtpFlag := flag.String(smtpFlagStr, "", fmt.Sprintf("SMTP host for mail delivery. Port %i is used.", smtpPort))
smtpPasswordFileFlag := flag.String(smtpPasswordFileStr, "", "File containing SMTP password. Must have 0700 permissions.")
// Check all the flags
flag.Parse()
if *searchesFlag == "" {
fmt.Fprintf(os.Stderr, "%v flag must be set\n", searchesFlagStr)
return
}
if *fromFlag == "" {
fmt.Fprintf(os.Stderr, "%v flag must be set\n", fromFlagStr)
return
}
if *smtpFlag == "" {
fmt.Fprintf(os.Stderr, "%v flag must be set\n", smtpFlagStr)
return
}
if *smtpPasswordFileFlag == "" {
fmt.Fprintf(os.Stderr, "%v flag must be set\n", smtpPasswordFileStr)
return
}
// Load SMTP password
password, err := loadPassword(*smtpPasswordFileFlag)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to load password from %v: %v\n", *smtpPasswordFileFlag, err)
return
}
searches, err := app.FlightSearchesFromFile(*searchesFlag)
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid searches file contents: %v\n", err)
return
}
// Set up channel for OS signals, which are used to shutdown the app
sigChannel := make(chan os.Signal, 1)
signal.Notify(sigChannel, os.Interrupt, os.Kill)
// Create an email notifier
to := *toFlag
if len(to) == 0 {
to = *fromFlag
}
emailNotifier := &app.EmailFlightsNotifier{
SmtpAddress: fmt.Sprintf("%v:%v", *smtpFlag, smtpPort),
Auth: smtp.PlainAuth("", *fromFlag, password, *smtpFlag),
From: *fromFlag,
To: to,
}
notifier := app.SearchUpdateNotifierChain{&app.StdoutNotifier{}, emailNotifier}
// Create container for state with the function to update it
state := app.NewSearchStateUpdater(searches, app.NewFlightFetcher(client.NewClient(nil)), notifier)
logger := log.New(os.Stdout, "", log.LstdFlags)
updater := func() {
logger.Print("Updating flights")
if err := state.Update(); err != nil {
logger.Printf("Error updating flights: %v\n", err)
}
}
// Run immediately, and then every hour
// TODO(alec): Determine if sharing state variable across goroutines is bad in this case
ticker := time.NewTicker(1 * time.Hour)
go func() {
for _ = range ticker.C {
updater()
}
}()
updater()
// Keep running until signal to shutdown
<-sigChannel
ticker.Stop()
}
func loadPassword(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return "", err
}
if fileInfo.Mode() != 0600 {
return "", fmt.Errorf("Password file mode must be 0600, not %v", fileInfo.Mode())
}
reader := bufio.NewReader(file)
password, err := reader.ReadString('\n')
if err == nil || err == io.EOF {
return password, nil
}
return "", err
}