55 "fmt"
66 "io"
77 "log"
8+ "runtime"
89 "sort"
910
1011 "github.com/slack-go/slack"
@@ -25,6 +26,7 @@ type SlackDumper struct {
2526
2627type options struct {
2728 dumpfiles bool
29+ workers int
2830}
2931
3032var allChanTypes = []string {"mpim" , "im" , "public_channel" , "private_channel" }
@@ -42,12 +44,28 @@ func DumpFiles(b bool) Option {
4244 }
4345}
4446
47+ const defNumWorkers = 4 // seems reasonable
48+
49+ // NumWorkers allows to set the number of file download workers. n should be in
50+ // range [1, NumCPU]. If not in range, will be reset to a defNumWorkers number,
51+ // which seems reasonable.
52+ func NumWorkers (n int ) Option {
53+ return func (sd * SlackDumper ) {
54+ if n < 1 || runtime .NumCPU () < n {
55+ n = defNumWorkers
56+ }
57+ sd .options .workers = n
58+ }
59+ }
60+
4561// New creates new client and populates the internal cache of users and channels
4662// for lookups.
4763func New (ctx context.Context , token string , cookie string , opts ... Option ) (* SlackDumper , error ) {
48- var err error
4964 sd := & SlackDumper {
5065 client : slack .New (token , slack .OptionCookie (cookie )),
66+ options : options {
67+ workers : defNumWorkers ,
68+ },
5169 }
5270 for _ , opt := range opts {
5371 opt (sd )
@@ -59,6 +77,7 @@ func New(ctx context.Context, token string, cookie string, opts ...Option) (*Sla
5977
6078 go func () {
6179 defer close (errC )
80+
6281 var err error
6382 chanTypes := allChanTypes
6483 log .Println ("> caching channels, might take a while..." )
@@ -73,7 +92,7 @@ func New(ctx context.Context, token string, cookie string, opts ...Option) (*Sla
7392 return nil , fmt .Errorf ("error fetching users: %s" , err )
7493 }
7594
76- if err = <- errC ; err != nil {
95+ if err : = <- errC ; err != nil {
7796 return nil , fmt .Errorf ("error fetching channels: %s" , err )
7897 }
7998
@@ -93,43 +112,36 @@ func (sd *SlackDumper) IsDeletedUser(id string) bool {
93112
94113// DumpMessages fetches messages from the conversation identified by channelID.
95114func (sd * SlackDumper ) DumpMessages (ctx context.Context , channelID string ) (* Channel , error ) {
115+ var filesC = make (chan * slack.File , 20 )
96116
97- params := & slack.GetConversationHistoryParameters {
98- ChannelID : channelID ,
99- }
100-
101- filesC := make (chan * slack.File , 20 )
102- done := make (chan bool )
103- errC := make (chan error , 1 )
104-
105- if sd .options .dumpfiles {
106- go func () {
107- errC <- sd .fileDownloader (channelID , filesC , done )
108- }()
117+ dlDoneC , err := sd .fileDownloader (channelID , filesC )
118+ if err != nil {
119+ return nil , err
109120 }
110121
111122 limiter := newLimiter (tier3 )
112- var messages []Message
113123
124+ var (
125+ messages []Message
126+ cursor string
127+ )
114128 for i := 1 ; ; i ++ {
115- select {
116- case err := <- errC :
117- // stop the goroutine gracefully if it's running
118- close (filesC )
119- <- done
120- return nil , err
121- default :
122- }
123- resp , err := sd .client .GetConversationHistoryContext (ctx , params )
129+ resp , err := sd .client .GetConversationHistoryContext (
130+ ctx ,
131+ & slack.GetConversationHistoryParameters {
132+ ChannelID : channelID ,
133+ Cursor : cursor ,
134+ },
135+ )
124136 if err != nil {
125137 return nil , err
126138 }
127- chunk := sd .convertMsgs (resp .Messages )
128139
140+ chunk := sd .convertMsgs (resp .Messages )
129141 if err := sd .populateThreads (ctx , chunk , channelID , limiter ); err != nil {
130142 return nil , err
131143 }
132- sd .extractFiles (filesC , chunk )
144+ sd .pipeFiles (filesC , chunk )
133145 messages = append (messages , chunk ... )
134146
135147 log .Printf ("request #%d, fetched: %d, total: %d\n " ,
@@ -139,7 +151,7 @@ func (sd *SlackDumper) DumpMessages(ctx context.Context, channelID string) (*Cha
139151 break
140152 }
141153
142- params . Cursor = resp .ResponseMetaData .NextCursor
154+ cursor = resp .ResponseMetaData .NextCursor
143155
144156 limiter .Wait (ctx )
145157 }
@@ -150,7 +162,7 @@ func (sd *SlackDumper) DumpMessages(ctx context.Context, channelID string) (*Cha
150162
151163 if sd .options .dumpfiles {
152164 close (filesC )
153- <- done
165+ <- dlDoneC
154166 }
155167
156168 return & Channel {Messages : messages , ID : channelID }, nil
@@ -165,21 +177,22 @@ func (sd *SlackDumper) convertMsgs(sm []slack.Message) []Message {
165177 return msgs
166178}
167179
168- // extractFiles scans the messages and sends all the files discovered to the filesC.
169- func (sd * SlackDumper ) extractFiles (filesC chan <- * slack.File , msgs []Message ) {
180+ // pipeFiles scans the messages and sends all the files discovered to the filesC.
181+ func (sd * SlackDumper ) pipeFiles (filesC chan <- * slack.File , msgs []Message ) {
170182 if ! sd .options .dumpfiles {
171183 return
172184 }
173185 // place files in download queue
174- chunk := sd .filesFromMessages (msgs )
175- for i := range chunk {
176- filesC <- & chunk [i ]
186+ fileChunk := sd .filesFromMessages (msgs )
187+ for i := range fileChunk {
188+ filesC <- & fileChunk [i ]
177189 }
178190}
179191
180192// populateThreads scans the message slice for threads, if and when it
181193// discovers the message with ThreadTimestamp, it fetches all messages in that
182194// thread updating them to the msgs slice.
195+ //
183196// ref: https://api.slack.com/messaging/retrieving
184197func (sd * SlackDumper ) populateThreads (ctx context.Context , msgs []Message , channelID string , l * rate.Limiter ) error {
185198 for i := range msgs {
0 commit comments