Skip to content

Commit

Permalink
add legacy emoji dump back, as it is faster
Browse files Browse the repository at this point in the history
  • Loading branch information
rusq committed Nov 23, 2024
1 parent 1cac682 commit 80d4729
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 30 deletions.
18 changes: 14 additions & 4 deletions cmd/slackdump/internal/diag/edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package diag
import (
"context"
"encoding/json"
"log/slog"
"os"

"github.com/rusq/slackdump/v3/auth"
Expand Down Expand Up @@ -59,11 +60,20 @@ func runEdge(ctx context.Context, cmd *base.Command, args []string) error {
// }

lg.Info("*** AdminEmojiList test ***")
emojis, err := cl.AdminEmojiList(ctx)
if err != nil {
return err
var allEmoji edge.EmojiResult

var iter = 0
for res, err := range cl.AdminEmojiList(ctx) {
if err != nil {
return err
}
slog.Info("got emojis", "count", len(res.Emoji), "disabled", len(res.DisabledEmoji), "iter", iter)
iter++
allEmoji.Emoji = append(allEmoji.Emoji, res.Emoji...)
allEmoji.DisabledEmoji = append(allEmoji.DisabledEmoji, res.DisabledEmoji...)
}
if err := save("emoji.json", emojis); err != nil {

if err := save("emoji.json", allEmoji); err != nil {
return err
}

Expand Down
24 changes: 23 additions & 1 deletion cmd/slackdump/internal/emoji/emoji.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"

"github.com/rusq/fsadapter"
"github.com/rusq/slackdump/v3"
"github.com/rusq/slackdump/v3/auth"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/bootstrap"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/emoji/emojidl"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
Expand All @@ -24,6 +26,7 @@ var CmdEmoji = &base.Command{

type options struct {
ignoreErrors bool
fullInfo bool
}

// emoji specific flags
Expand All @@ -34,6 +37,7 @@ var cmdFlags = options{
func init() {
CmdEmoji.Wizard = wizard
CmdEmoji.Flag.BoolVar(&cmdFlags.ignoreErrors, "ignore-errors", true, "ignore download errors (skip failed emojis)")
CmdEmoji.Flag.BoolVar(&cmdFlags.fullInfo, "full-info", true, "fetch emojis using Edge API to get full emoji information, including usernames")
}

func run(ctx context.Context, cmd *base.Command, args []string) error {
Expand All @@ -48,14 +52,32 @@ func run(ctx context.Context, cmd *base.Command, args []string) error {
base.SetExitStatus(base.SApplicationError)
return err
}
if cmdFlags.fullInfo {
return runEdge(ctx, fsa, prov)
} else {
return runLegacy(ctx, fsa)
}
}

func runLegacy(ctx context.Context, fsa fsadapter.FS) error {
sess, err := bootstrap.SlackdumpSession(ctx, slackdump.WithFilesystem(fsa))
if err != nil {
base.SetExitStatus(base.SApplicationError)
return err
}

return emojidl.DlFS(ctx, sess, fsa, cmdFlags.ignoreErrors)
}

func runEdge(ctx context.Context, fsa fsadapter.FS, prov auth.Provider) error {
sess, err := edge.New(ctx, prov)
if err != nil {
base.SetExitStatus(base.SApplicationError)
return err
}
defer sess.Close()

if err := emojidl.DlFS(ctx, sess, fsa, cmdFlags.ignoreErrors); err != nil {
if err := emojidl.DlEdgeFS(ctx, sess, fsa, cmdFlags.ignoreErrors); err != nil {
base.SetExitStatus(base.SApplicationError)
return fmt.Errorf("application error: %s", err)
}
Expand Down
161 changes: 161 additions & 0 deletions cmd/slackdump/internal/emoji/emojidl/emoedge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Package emojidl provides functions to dump the all slack emojis for a workspace.
// It skips the "alias" emojis, so only original an emoji with an original name
// is present. If you need to find the alias - lookup the index.json. The
// directory structure is the following:
//
// .
// +- emojis
// | +- foo.png
// | +- bar.png
// : :
// | +- baz.png
// +- index.json
//
// Where index.json contains the emoji index, and *.png files under emojis
// directory are individual emojis.
package emojidl

import (
"context"
"encoding/json"
"errors"
"fmt"
"iter"
"sync"

"github.com/rusq/fsadapter"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg"
"github.com/rusq/slackdump/v3/internal/edge"
)

//go:generate mockgen -source emoji.go -destination emoji_mock_test.go -package emojidl
type EdgeEmojiLister interface {
AdminEmojiList(ctx context.Context) iter.Seq2[edge.EmojiResult, error]
}

// DlEdgeFS downloads the emojis and saves them to the fsa. It spawns numWorker
// goroutines for getting the files. It will call fetchFn for each emoji.
func DlEdgeFS(ctx context.Context, sess EdgeEmojiLister, fsa fsadapter.FS, failFast bool) error {
lg := cfg.Log.With("in", "fetch", "dir", emojiDir, "numWorkers", numWorkers, "failFast", failFast)

var (
emojiC = make(chan edge.Emoji)
totalC = make(chan int)
genErrC = make(chan error)
resultC = make(chan edgeResult)
)

// Async download pipeline.

// 1. generator, send emojis into the emojiC channel.
go func() {
var once sync.Once
defer close(totalC)

defer close(emojiC)

for chunk, err := range sess.AdminEmojiList(ctx) {
if err != nil {
genErrC <- err
return
}
lg.DebugContext(ctx, "got emojis", "count", len(chunk.Emoji), "disabled", len(chunk.DisabledEmoji), "total", chunk.Total)
once.Do(func() { totalC <- chunk.Total }) // send total count once.
for _, emoji := range chunk.Emoji {
select {
case <-ctx.Done():
return
case emojiC <- emoji:
}
}
}
}()

// 2. Download workers, download the emojis.
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
worker2(ctx, fsa, emojiC, resultC)
wg.Done()
}()
}
// 3. Sentinel, closes the result channel once all workers are finished.
go func() {
wg.Wait()
close(resultC)
}()

// 4. Result processor, receives download results and logs any errors that
// may have occurred.
var (
count = 0
total = <-totalC
)
var emojis = make(map[string]edge.Emoji, total)
LOOP:
for {
select {

case genErr := <-genErrC:
if genErr != nil {
return fmt.Errorf("failed to get emoji list: %w", genErr)
}
case res, more := <-resultC:
if !more {
break LOOP
}
if res.err != nil {
if errors.Is(res.err, context.Canceled) {
return res.err
}
if failFast {
return fmt.Errorf("failed: %q: %w", res.emoji.Name, res.err)
}
lg.WarnContext(ctx, "failed", "name", res.emoji.Name, "error", res.err)
}
emojis[res.emoji.Name] = res.emoji // to resemble the legacy code.
count++
lg.InfoContext(ctx, "downloaded", "count", count, "total", total, "name", res.emoji.Name)
}
}
out, err := fsa.Create("index.json")
if err != nil {
return err
}
defer out.Close()
if err := json.NewEncoder(out).Encode(emojis); err != nil {
return err
}

return nil
}

type edgeResult struct {
emoji edge.Emoji
skipped bool
err error
}

// worker is the function that runs in a separate goroutine and downloads emoji
// received from emojiC. The result of the operation is sent to resultC channel.
// fn is called for each received emoji.
func worker2(ctx context.Context, fsa fsadapter.FS, emojiC <-chan edge.Emoji, resultC chan<- edgeResult) {
for {
select {
case <-ctx.Done():
resultC <- edgeResult{err: ctx.Err()}
return
case em, more := <-emojiC:
if !more {
return
}
if em.IsAlias != 0 {
resultC <- edgeResult{emoji: em, skipped: true}
break
}
err := fetchFn(ctx, fsa, emojiDir, em.Name, em.URL)
resultC <- edgeResult{emoji: em, err: err}
}
}
}
17 changes: 8 additions & 9 deletions cmd/slackdump/internal/emoji/emojidl/emoji.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (

"github.com/rusq/fsadapter"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg"
"github.com/rusq/slackdump/v3/internal/edge"
)

const (
Expand All @@ -39,13 +38,13 @@ const (
var fetchFn = fetchEmoji

//go:generate mockgen -source emoji.go -destination emoji_mock_test.go -package emojidl
type emojidumper interface {
AdminEmojiList(ctx context.Context) (edge.EmojiResult, error)
type EmojiDumper interface {
DumpEmojis(ctx context.Context) (map[string]string, error)
}

// DlFS downloads all emojis from the workspace and saves them to the fsa.
func DlFS(ctx context.Context, sess emojidumper, fsa fsadapter.FS, failFast bool) error {
emojis, err := sess.AdminEmojiList(ctx)
func DlFS(ctx context.Context, sess EmojiDumper, fsa fsadapter.FS, failFast bool) error {
emojis, err := sess.DumpEmojis(ctx)
if err != nil {
return fmt.Errorf("error during emoji dump: %w", err)
}
Expand All @@ -63,7 +62,7 @@ func DlFS(ctx context.Context, sess emojidumper, fsa fsadapter.FS, failFast bool

// fetch downloads the emojis and saves them to the fsa. It spawns numWorker
// goroutines for getting the files. It will call fetchFn for each emoji.
func fetch(ctx context.Context, fsa fsadapter.FS, emojis edge.EmojiResult, failFast bool) error {
func fetch(ctx context.Context, fsa fsadapter.FS, emojis map[string]string, failFast bool) error {
lg := cfg.Log.With("in", "fetch", "dir", emojiDir, "numWorkers", numWorkers, "failFast", failFast)

var (
Expand All @@ -76,11 +75,11 @@ func fetch(ctx context.Context, fsa fsadapter.FS, emojis edge.EmojiResult, failF
// 1. generator, send emojis into the emojiC channel.
go func() {
defer close(emojiC)
for _, e := range emojis.Emoji {
for name, uri := range emojis {
select {
case <-ctx.Done():
return
case emojiC <- emoji{e.Name, e.URL}:
case emojiC <- emoji{name, uri}:
}
}
}()
Expand All @@ -103,7 +102,7 @@ func fetch(ctx context.Context, fsa fsadapter.FS, emojis edge.EmojiResult, failF
// 4. Result processor, receives download results and logs any errors that
// may have occurred.
var (
total = len(emojis.Emoji)
total = len(emojis)
count = 0
)
lg = lg.With("total", total)
Expand Down
8 changes: 7 additions & 1 deletion internal/edge/edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"net/http"
"net/url"
"os"
"runtime/trace"
"strings"
"time"

Expand Down Expand Up @@ -255,11 +256,16 @@ func (cl *Client) ParseResponse(req any, r *http.Response) error {
// if it receives another rate limit error, it returns slack.RateLimitedError
// to let the caller handle it.
func do(ctx context.Context, cl *http.Client, req *http.Request) (*http.Response, error) {
ctx, task := trace.NewTask(ctx, "edge.do")
defer task.End()

lg := slog.Default()
req.Header.Set("Accept-Language", "en-NZ,en-AU;q=0.9,en;q=0.8")
req.Header.Set("User-Agent", slackauth.DefaultUserAgent)

rgn := trace.StartRegion(ctx, "http.Do")
resp, err := cl.Do(req)
rgn.End()
if err != nil {
return nil, err
}
Expand All @@ -268,7 +274,7 @@ func do(ctx context.Context, cl *http.Client, req *http.Request) (*http.Response
if err != nil {
return nil, err
}
lg.Debug("got rate limited, waiting", "delay", wait)
lg.InfoContext(ctx, "got rate limited, waiting", "delay", wait)

time.Sleep(wait)
resp, err = cl.Do(req)
Expand Down
Loading

0 comments on commit 80d4729

Please sign in to comment.