-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
196 lines (157 loc) · 5.01 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package main
import (
_ "embed"
"errors"
"fmt"
"log"
"math/rand"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/gen2brain/beeep"
"github.com/getlantern/systray"
"github.com/weldeondotwav/btw/config"
)
var (
ErrNoReminders = errors.New("no data in file to read")
Config *config.AppConfig // the user's config, as read from their config file
StartupDelay = time.Minute // delay before the first reminder is sent after the app starts
// app icon
// https://github.com/microsoft/Windows-classic-samples/blob/44d192fd7ec6f2422b7d023891c5f805ada2c811/Samples/Win7Samples/begin/sdkdiff/sdkdiff.ico
//go:embed assets/icon.ico
iconData []byte
// The content that we stick in the reminders file if we create a new one
DefaultRemindersFileContent = "# Lines starting with # or empty lines are ignored\n\nclean living room\ncheck mail"
)
func main() {
systray.Run(onReady, onExit)
}
func onReady() {
// build our systray app
systray.SetIcon(iconData)
systray.SetTitle("btw")
systray.SetTooltip("btw: reminders")
// With the systray library, adding a menu item returns a pointer to the item, which we can then use to listen for events via channels
mRemindNow := systray.AddMenuItem("Remind me now", "Sends an on-demand random reminder")
mEditFile := systray.AddMenuItem("Open reminders file", "Opens the reminders file in the default text editor")
mEditConfig := systray.AddMenuItem("Open app config", "Opens the application config in the default text editor")
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "Close the program")
loadConfig()
// event handlers (the way this works is so nice)
go func() {
for {
select {
case <-mRemindNow.ClickedCh:
fmt.Println("Reminder requested!")
sendRandomReminder()
case <-mEditFile.ClickedCh:
openFileWithDefault(Config.RemindersFilePath)
case <-mEditConfig.ClickedCh:
openFileWithDefault(config.DefaultPath())
case <-mQuit.ClickedCh:
systray.Quit()
return
}
}
}()
fmt.Println("Startup Delay: ", StartupDelay)
time.Sleep(StartupDelay)
go func() {
for {
sendRandomReminder()
time.Sleep(Config.ReminderPeriod)
}
}()
}
func onExit() {
fmt.Println("btw closing")
}
// openFileWithDefault opens the file at path with the default application for that file type
func openFileWithDefault(path string) {
openCmd := exec.Command("cmd", "/c", filepath.Clean(path))
openCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
openCmd.Start()
}
// sendRandomReminder selects a random item from the users reminders list and sends it as a tray notification
func sendRandomReminder() {
reminders, err := getReminders()
if err != nil {
log.Fatal("Failed to read reminders: ", err)
}
reminderToShow := pick(reminders)
fmt.Println("showing reminder: ", reminderToShow)
err = beeep.Notify("btw", reminderToShow, "")
if err != nil {
fmt.Println("ERROR: Failed to send reminder: ", err)
}
}
// getReminders returns all the lines in the reminders file as a string array. contains the logic for parsing the file
func getReminders() ([]string, error) {
fileData, err := os.ReadFile(Config.RemindersFilePath)
if err != nil {
return nil, err
}
if len(fileData) == 0 {
return nil, ErrNoReminders
}
fileDataSplit := strings.Split(string(fileData), "\n")
// we'll filter out lines that we don't need
filteredLines := make([]string, 0)
for _, v := range fileDataSplit {
if len(v) < 1 {
continue // ignore empty lines
}
if v[0] == '#' ||
strings.TrimSpace(v) == "" {
continue // ignore comments
} else {
filteredLines = append(filteredLines, v) // line is good, add it to the list
}
}
return filteredLines, nil
}
// pick returns a random item from the input list choices
func pick(choices []string) string {
i := rand.Intn(len(choices))
return choices[i]
}
// Loads the user config, creating it if it doesn't exist. The read config is stored in the Config var
func loadConfig() {
conf, err := config.Read()
if err != nil {
fmt.Println("ERROR: Failed to read config:", err)
if errors.Is(err, os.ErrNotExist) {
fmt.Println("Creating default config...")
defaultConfig := config.NewDefaultConfig()
err = defaultConfig.Save()
if err != nil {
log.Fatal("Failed to save config! ", err)
}
conf = &defaultConfig
} else {
log.Fatal("Unhandled error while loading user config: ", err)
}
}
fmt.Println("Loaded config")
Config = conf // save the config object to our global var
// Also create the reminders file if it doesn't exist
_, err = os.Stat(conf.RemindersFilePath)
if err != nil {
fmt.Println("Error when checking for reminders file", err)
// need to check the err first frfr
fmt.Println("Reminders file not found, creating it at ", conf.RemindersFilePath)
f, err := os.Create(conf.RemindersFilePath)
if err != nil {
log.Fatal("Failed to create reminders file: ", err)
}
defer f.Close()
_, err = f.WriteString(DefaultRemindersFileContent)
if err != nil {
fmt.Println("ERROR: Failed to write default template to new reminders file: ", err)
}
}
}