-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
144 lines (109 loc) · 3.4 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
package main
import (
"bytes"
"flag"
"fmt"
"os"
"os/signal"
"time"
"github.com/go-yaml/yaml"
"github.com/ptgott/todoist-backups/config"
"github.com/ptgott/todoist-backups/gdrive"
"github.com/ptgott/todoist-backups/todoist"
"github.com/rs/zerolog/log"
)
type Config struct {
General config.General `yaml:"general"`
GoogleDrive gdrive.Config `yaml:"google_drive"`
}
// For LimitReaders: 5MB
const maxResponseBodyBytes int64 = 5e6
const help = `
You must provide a -config flag with the path to a config file.
The config file must include the following options in YAML format:
general:
todoist_api_key: the API key retrieved from Todoist
backup_interval: How often to conduct the backup. A duration string like 1m,
4h, or 3d.
google_drive:
credentials_path: path to a Google Workspace credentials file, which you
can export for the service account that you created for this app. This must
be a service account credentials file, rather than an OAuth2.0 token.
folder_name: name of the Google Drive directory you want to write
backups to.
The Todoist backup job will be limited to this directory.
Note that this directory must be shared with the service account you create
for Todoist backups. The service account's email address will be provided
on creation.
You can optionally use the -oneshot flag to create a single backup without
running the job as a daemon.
`
func runBackup(c Config) {
ab, err := todoist.GetAvailableBackups(c.General.TodoistAPIKey)
if err != nil {
log.Fatal().Err(err).Msg("Unable to grab the available backups from Todoist")
}
u, err := todoist.LatestAvailableBackup(ab)
if err != nil {
log.Fatal().Err(err).Msg("Unable to determine the latest available backup from Todoist")
}
var buf bytes.Buffer
if err := todoist.GetBackup(&buf, c.General.TodoistAPIKey, u.URL, maxResponseBodyBytes); err != nil {
log.Fatal().Err(err).Msg("Unable to retrieve the latest Todoist backup")
}
if err := gdrive.UploadFile(
&buf,
u.Version,
c.GoogleDrive,
); err != nil {
log.Fatal().Err(err).Msg("Unable to upload a file to Google Drive")
}
log.Info().Msg("Todoist backup succeeded")
}
func main() {
g := make(chan os.Signal, 1)
signal.Notify(g, os.Interrupt)
oneshot := flag.Bool("oneshot", false, "whether to run one backup and exit")
cf := flag.String("config", "", "the path to a configuration file")
flag.Parse()
if *cf == "" {
fmt.Print(help)
os.Exit(1)
}
f, err := os.Open(*cf)
if err != nil {
log.Fatal().Str("filepath", *cf).Err(err).Msg("Could not open the config file")
}
var c Config
if err := yaml.NewDecoder(f).Decode(&c); err != nil {
log.Fatal().Err(err).Msg("Could not parse your config file")
}
if err := c.General.Validate(); err != nil {
log.Fatal().Err(err).Msg("Invalid config")
}
if err := c.GoogleDrive.Validate(); err != nil {
log.Fatal().Err(err).Msg("Invalid Google Drive config")
}
dur, err := time.ParseDuration(c.General.BackupInterval)
if err != nil {
log.Fatal().Err(err).Msg("Could not parse the backup interval")
}
// Run the first backup right away so we can identify issues
log.Info().Msg("running initial backup")
runBackup(c)
if *oneshot {
log.Info().Msg("oneshot selected, exiting")
os.Exit(0)
}
k := time.NewTicker(dur)
for {
select {
case <-k.C:
log.Info().Msg("running periodic backup")
runBackup(c)
case <-g:
log.Info().Msg("Received interrupt. Stopping.")
os.Exit(0)
}
}
}