-
-
Notifications
You must be signed in to change notification settings - Fork 60
/
users.go
150 lines (127 loc) · 4.09 KB
/
users.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
package slackdump
// In this file: user related code.
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"runtime/trace"
"time"
"errors"
"github.com/slack-go/slack"
"github.com/rusq/slackdump/v2/internal/encio"
"github.com/rusq/slackdump/v2/internal/network"
"github.com/rusq/slackdump/v2/types"
)
// GetUsers retrieves all users either from cache or from the API.
func (sd *Session) GetUsers(ctx context.Context) (types.Users, error) {
// TODO: validate that the cache is from the same workspace, it can be done by team ID.
ctx, task := trace.NewTask(ctx, "GetUsers")
defer task.End()
if sd.options.NoUserCache {
return types.Users{}, nil
}
users, err := sd.loadUserCache(sd.options.UserCacheFilename, sd.wspInfo.TeamID, sd.options.MaxUserCacheAge)
if err != nil {
if os.IsNotExist(err) {
sd.l().Println(" caching users for the first time")
} else {
sd.l().Printf(" %s: it will be recreated.", err)
}
users, err = sd.fetchUsers(ctx)
if err != nil {
return nil, err
}
if err := sd.saveUserCache(sd.options.UserCacheFilename, sd.wspInfo.TeamID, users); err != nil {
trace.Logf(ctx, "error", "saving user cache to %q, error: %s", sd.options.UserCacheFilename, err)
sd.l().Printf("error saving user cache to %q: %s, but nevermind, let's continue", sd.options.UserCacheFilename, err)
}
}
return users, err
}
// fetchUsers fetches users from the API.
func (sd *Session) fetchUsers(ctx context.Context) (types.Users, error) {
var (
users []slack.User
)
if err := network.WithRetry(ctx, network.NewLimiter(network.Tier2, sd.options.Tier2Burst, int(sd.options.Tier2Boost)), sd.options.Tier2Retries, func() error {
var err error
users, err = sd.client.GetUsersContext(ctx)
return err
}); err != nil {
trace.Logf(ctx, "error", "GetUsers error=%s", err)
return nil, err
}
// BUG: as of 201902 there's a bug in slack module, the invalid_auth error
// is not propagated properly, so we'll check for number of users. There
// should be at least one (slackbot).
if len(users) == 0 {
return nil, errors.New("couldn't fetch users")
}
return users, nil
}
// loadUsers tries to load the users from the file
func (sd *Session) loadUserCache(filename string, suffix string, maxAge time.Duration) (types.Users, error) {
filename = sd.makeCacheFilename(filename, suffix)
if err := checkCacheFile(filename, maxAge); err != nil {
return nil, err
}
f, err := encio.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open %s: %w", filename, err)
}
defer f.Close()
uu, err := readUsers(f)
if err != nil {
return nil, fmt.Errorf("failed to decode users from %s: %w", filename, err)
}
return uu, nil
}
func readUsers(r io.Reader) (types.Users, error) {
dec := json.NewDecoder(r)
var uu = make(types.Users, 0, 500) // 500 users. reasonable?
for {
var u slack.User
if err := dec.Decode(&u); err != nil {
if err == io.EOF {
break
}
return nil, err
}
uu = append(uu, u)
}
return uu, nil
}
func (sd *Session) saveUserCache(filename string, suffix string, uu types.Users) error {
filename = sd.makeCacheFilename(filename, suffix)
f, err := encio.Create(filename)
if err != nil {
return fmt.Errorf("failed to create file %s: %w", filename, err)
}
defer f.Close()
enc := json.NewEncoder(f)
for _, u := range uu {
if err := enc.Encode(u); err != nil {
return fmt.Errorf("failed to encode data for %s: %w", filename, err)
}
}
return nil
}
// makeCacheFilename converts filename.ext to filename-suffix.ext.
func (sd *Session) makeCacheFilename(filename, suffix string) string {
ne := filenameSplit(filename)
return filepath.Join(sd.options.CacheDir, filenameJoin(nameExt{ne[0] + "-" + suffix, ne[1]}))
}
type nameExt [2]string
// filenameSplit splits the "path/to/filename.ext" into nameExt{"path/to/filename", ".ext"}
func filenameSplit(filename string) nameExt {
ext := filepath.Ext(filename)
name := filename[:len(filename)-len(ext)]
return nameExt{name, ext}
}
// filenameJoin combines nameExt{"path/to/filename", ".ext"} to "path/to/filename.ext".
func filenameJoin(split nameExt) string {
return split[0] + split[1]
}