mirror of
https://github.com/elyby/chrly.git
synced 2024-12-25 14:39:55 +05:30
142 lines
2.9 KiB
Go
142 lines
2.9 KiB
Go
package mojang
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"ely.by/chrly/internal/utils"
|
|
)
|
|
|
|
type BatchUuidsProvider struct {
|
|
UsernamesToUuidsEndpoint func(usernames []string) ([]*ProfileInfo, error)
|
|
batch int
|
|
delay time.Duration
|
|
fireOnFull bool
|
|
|
|
queue *utils.Queue[*job]
|
|
fireChan chan any
|
|
stopChan chan any
|
|
onFirstCall sync.Once
|
|
}
|
|
|
|
func NewBatchUuidsProvider(
|
|
endpoint func(usernames []string) ([]*ProfileInfo, error),
|
|
batchSize int,
|
|
awaitDelay time.Duration,
|
|
fireOnFull bool,
|
|
) *BatchUuidsProvider {
|
|
return &BatchUuidsProvider{
|
|
UsernamesToUuidsEndpoint: endpoint,
|
|
stopChan: make(chan any),
|
|
batch: batchSize,
|
|
delay: awaitDelay,
|
|
fireOnFull: fireOnFull,
|
|
queue: utils.NewQueue[*job](),
|
|
fireChan: make(chan any),
|
|
}
|
|
}
|
|
|
|
type job struct {
|
|
Username string
|
|
Ctx context.Context
|
|
ResultChan chan<- *jobResult
|
|
}
|
|
|
|
type jobResult struct {
|
|
Profile *ProfileInfo
|
|
Error error
|
|
}
|
|
|
|
func (p *BatchUuidsProvider) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) {
|
|
resultChan := make(chan *jobResult)
|
|
n := p.queue.Enqueue(&job{username, ctx, resultChan})
|
|
if p.fireOnFull && n%p.batch == 0 {
|
|
p.fireChan <- struct{}{}
|
|
}
|
|
|
|
p.onFirstCall.Do(p.startQueue)
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case result := <-resultChan:
|
|
return result.Profile, result.Error
|
|
}
|
|
}
|
|
|
|
func (p *BatchUuidsProvider) StopQueue() {
|
|
close(p.stopChan)
|
|
}
|
|
|
|
func (p *BatchUuidsProvider) startQueue() {
|
|
go func() {
|
|
for {
|
|
t := time.NewTimer(p.delay)
|
|
select {
|
|
case <-p.stopChan:
|
|
return
|
|
case <-t.C:
|
|
go p.fireRequest()
|
|
case <-p.fireChan:
|
|
t.Stop()
|
|
go p.fireRequest()
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (p *BatchUuidsProvider) fireRequest() {
|
|
jobs := make([]*job, 0, p.batch)
|
|
n := p.batch
|
|
for {
|
|
foundJobs, left := p.queue.Dequeue(n)
|
|
for i := range foundJobs {
|
|
if foundJobs[i].Ctx.Err() != nil {
|
|
// If the job context has already ended, its result will be returned in the GetUuid method
|
|
close(foundJobs[i].ResultChan)
|
|
|
|
foundJobs[i] = foundJobs[len(foundJobs)-1]
|
|
foundJobs = foundJobs[:len(foundJobs)-1]
|
|
}
|
|
}
|
|
|
|
jobs = append(jobs, foundJobs...)
|
|
if len(jobs) != p.batch && left != 0 {
|
|
n = p.batch - len(jobs)
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
if len(jobs) == 0 {
|
|
return
|
|
}
|
|
|
|
usernames := make([]string, len(jobs))
|
|
for i, job := range jobs {
|
|
usernames[i] = job.Username
|
|
}
|
|
|
|
profiles, err := p.UsernamesToUuidsEndpoint(usernames)
|
|
for _, job := range jobs {
|
|
response := &jobResult{}
|
|
if err == nil {
|
|
// The profiles in the response aren't ordered, so we must search each username over full array
|
|
for _, profile := range profiles {
|
|
if strings.EqualFold(job.Username, profile.Name) {
|
|
response.Profile = profile
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
response.Error = err
|
|
}
|
|
|
|
job.ResultChan <- response
|
|
close(job.ResultChan)
|
|
}
|
|
}
|