cache/api.go (view raw)
1package cache
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "log"
9 "net/http"
10 "os"
11
12 "path/filepath"
13 "time"
14
15 "golang.org/x/oauth2"
16 "golang.org/x/oauth2/google"
17 "google.golang.org/api/drive/v3"
18 "google.golang.org/api/option"
19 "google.golang.org/api/sheets/v4"
20)
21
22type GoogleAPI struct {
23 Ctx context.Context
24 Client *http.Client
25 spreadsheetId string
26 spreadsheetRange string
27}
28
29type Entry struct {
30 FileID string `json:"id"`
31 Month string `json:"date"`
32 Name string `json:"name"`
33 FileName string `json:"filename"`
34 FilePath string `json:"content"`
35}
36
37// Retrieve a token, saves the token, then returns the generated client.
38func getClient(config *oauth2.Config) *http.Client {
39 // The file token.json stores the user's access and refresh tokens, and is
40 // created automatically when the authorization flow completes for the first
41 // time.
42 tokFile := "token.json"
43 tok, err := tokenFromFile(tokFile)
44 if err != nil {
45 tok = getTokenFromWeb(config)
46 saveToken(tokFile, tok)
47 }
48 return config.Client(context.Background(), tok)
49}
50
51// Request a token from the web, then returns the retrieved token.
52func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
53 authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
54 fmt.Printf("Go to the following link in your browser then type the "+
55 "authorization code: \n%v\n", authURL)
56
57 var authCode string
58 if _, err := fmt.Scan(&authCode); err != nil {
59 log.Fatalf("Unable to read authorization code %v", err)
60 }
61
62 tok, err := config.Exchange(context.TODO(), authCode)
63 if err != nil {
64 log.Fatalf("Unable to retrieve token from web %v", err)
65 }
66 return tok
67}
68
69// Retrieves a token from a local file.
70func tokenFromFile(file string) (*oauth2.Token, error) {
71 f, err := os.Open(file)
72 if err != nil {
73 return nil, err
74 }
75 defer f.Close()
76 tok := &oauth2.Token{}
77 err = json.NewDecoder(f).Decode(tok)
78 return tok, err
79}
80
81// Saves a token to a file path.
82func saveToken(path string, token *oauth2.Token) {
83 fmt.Printf("Saving credential file to: %s\n", path)
84 f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
85 if err != nil {
86 log.Fatalf("Unable to cache oauth token: %v", err)
87 }
88 defer f.Close()
89 json.NewEncoder(f).Encode(token)
90}
91
92func initGoogleAPI(spreadsheetId string, spreadsheetRange string) *GoogleAPI {
93 ctx := context.Background()
94 b, err := os.ReadFile("credentials.json")
95 if err != nil {
96 log.Fatalf("Unable to read client secret file: %v", err)
97 }
98
99 // If modifying these scopes, delete your previously saved token.json.
100 config, err := google.ConfigFromJSON(b, drive.DriveReadonlyScope, sheets.SpreadsheetsReadonlyScope)
101 if err != nil {
102 log.Fatalf("Unable to parse client secret file to config: %v", err)
103 }
104 client := getClient(config)
105 return &GoogleAPI{ctx, client, spreadsheetId, spreadsheetRange}
106}
107
108func getEntries(googleApi *GoogleAPI) ([]Entry, error) {
109 srv, err := sheets.NewService(googleApi.Ctx, option.WithHTTPClient(googleApi.Client))
110 if err != nil {
111 log.Fatalf("Unable to retrieve Sheets client: %v", err)
112 return nil, err
113 }
114
115 resp, err := srv.Spreadsheets.Values.Get(googleApi.spreadsheetId, googleApi.spreadsheetRange).Do()
116 if err != nil {
117 log.Fatalf("Unable to retrieve data from sheet: %v", err)
118 return nil, err
119 }
120
121 values := resp.Values
122 rows := make([]Entry, len(values))
123 for i, row := range values {
124 dateString := row[0].(string)
125 date, err := time.Parse("02/01/2006 15.04.05", dateString)
126 if err != nil {
127 log.Println("Error while parsing the following time and date string:", dateString)
128 date = time.Now()
129 }
130 rows[i] = Entry{
131 FileID: row[3].(string)[33:],
132 Month: date.Format("2006-01"),
133 Name: row[1].(string),
134 FileName: "",
135 FilePath: "",
136 }
137 }
138 return rows, nil
139}
140
141func getFile(googleApi *GoogleAPI, fileID string, CachePath string) (string, error) {
142 srv, err := drive.NewService(googleApi.Ctx, option.WithHTTPClient(googleApi.Client))
143 if err != nil {
144 log.Fatalf("Unable to retrieve Drive client: %v", err)
145 }
146
147 fileInfo, err := srv.Files.Get(fileID).Fields("name").Do()
148 if err != nil {
149 return "", err
150 }
151
152 fileName := fileID + filenameSeparator + fileInfo.Name
153 filePath := filepath.Join(CachePath, fileName)
154 out, err := os.Create(filePath)
155 if err != nil {
156 return "", err
157 }
158 defer out.Close()
159
160 res, err := srv.Files.Get(fileID).Download()
161 if err != nil {
162 return "", err
163 }
164 defer res.Body.Close()
165
166 _, err = io.Copy(out, res.Body)
167 if err != nil {
168 return "", err
169 }
170
171 return fileName, nil
172}