diff --git a/api/mojang/queue/cycle.go b/api/mojang/queue/cycle.go deleted file mode 100644 index 1c47818..0000000 --- a/api/mojang/queue/cycle.go +++ /dev/null @@ -1,96 +0,0 @@ -package queue - -import ( - "strings" - "sync" - "time" - - "github.com/elyby/chrly/api/mojang" -) - -var once sync.Once -var jobsQueue = JobsQueue{} - -func ScheduleTexturesForUsername(username string) chan *mojang.SignedTexturesResponse { - once.Do(func() { - jobsQueue.New() - startQueue() - }) - - // TODO: prevent of adding the same username more than once - resultChan := make(chan *mojang.SignedTexturesResponse) - jobsQueue.Enqueue(&Job{username, resultChan}) - - return resultChan -} - -func startQueue() { - go func() { - for { - start := time.Now() - queueRound() - time.Sleep(time.Second - time.Since(start)) - } - }() -} - -func queueRound() { - if jobsQueue.IsEmpty() { - return - } - - jobs := jobsQueue.Dequeue(100) - var usernames []string - for _, job := range jobs { - usernames = append(usernames, job.Username) - } - - profiles, err := mojang.UsernamesToUuids(usernames) - switch err.(type) { - case *mojang.TooManyRequestsError: - for _, job := range jobs { - job.RespondTo <- nil - } - - return - case error: - panic(err) - } - - var wg sync.WaitGroup - for _, job := range jobs { - wg.Add(1) - go func() { - var result *mojang.SignedTexturesResponse - shouldCache := true - var uuid string - for _, profile := range profiles { - if strings.EqualFold(job.Username, profile.Name) { - uuid = profile.Id - break - } - } - - if uuid != "" { - result, err = mojang.UuidToTextures(uuid, true) - if err != nil { - if _, ok := err.(*mojang.TooManyRequestsError); !ok { - panic(err) - } - - shouldCache = false - } - } - - wg.Done() - - job.RespondTo <- result - - if shouldCache { - // TODO: store result to cache - } - }() - } - - wg.Wait() -} diff --git a/api/mojang/queue/jobs_structure.go b/api/mojang/queue/jobs_structure.go new file mode 100644 index 0000000..464e330 --- /dev/null +++ b/api/mojang/queue/jobs_structure.go @@ -0,0 +1,51 @@ +// Based on the implementation from https://flaviocopes.com/golang-data-structure-queue/ + +package queue + +import ( + "sync" + + "github.com/elyby/chrly/api/mojang" +) + +type jobItem struct { + Username string + RespondTo chan *mojang.SignedTexturesResponse +} + +type jobsQueue struct { + items []*jobItem + lock sync.RWMutex +} + +func (s *jobsQueue) New() *jobsQueue { + s.items = []*jobItem{} + return s +} + +func (s *jobsQueue) Enqueue(t *jobItem) { + s.lock.Lock() + s.items = append(s.items, t) + s.lock.Unlock() +} + +func (s *jobsQueue) Dequeue(n int) []*jobItem { + s.lock.Lock() + if n > s.Size() { + n = s.Size() + } + + items := s.items[0:n] + s.items = s.items[n:len(s.items)] + s.lock.Unlock() + + return items +} + +func (s *jobsQueue) IsEmpty() bool { + return len(s.items) == 0 +} + +func (s *jobsQueue) Size() int { + return len(s.items) +} diff --git a/api/mojang/queue/queue_test.go b/api/mojang/queue/jobs_structure_test.go similarity index 63% rename from api/mojang/queue/queue_test.go rename to api/mojang/queue/jobs_structure_test.go index 279921a..0234181 100644 --- a/api/mojang/queue/queue_test.go +++ b/api/mojang/queue/jobs_structure_test.go @@ -10,9 +10,9 @@ func TestEnqueue(t *testing.T) { assert := testify.New(t) s := createQueue() - s.Enqueue(&Job{Username: "username1"}) - s.Enqueue(&Job{Username: "username2"}) - s.Enqueue(&Job{Username: "username3"}) + s.Enqueue(&jobItem{Username: "username1"}) + s.Enqueue(&jobItem{Username: "username2"}) + s.Enqueue(&jobItem{Username: "username3"}) assert.Equal(3, s.Size()) } @@ -21,10 +21,10 @@ func TestDequeueN(t *testing.T) { assert := testify.New(t) s := createQueue() - s.Enqueue(&Job{Username: "username1"}) - s.Enqueue(&Job{Username: "username2"}) - s.Enqueue(&Job{Username: "username3"}) - s.Enqueue(&Job{Username: "username4"}) + s.Enqueue(&jobItem{Username: "username1"}) + s.Enqueue(&jobItem{Username: "username2"}) + s.Enqueue(&jobItem{Username: "username3"}) + s.Enqueue(&jobItem{Username: "username4"}) items := s.Dequeue(2) assert.Len(items, 2) @@ -39,8 +39,8 @@ func TestDequeueN(t *testing.T) { assert.True(s.IsEmpty()) } -func createQueue() JobsQueue { - s := JobsQueue{} +func createQueue() jobsQueue { + s := jobsQueue{} s.New() return s diff --git a/api/mojang/queue/queue.go b/api/mojang/queue/queue.go index ea3df5d..e5d4506 100644 --- a/api/mojang/queue/queue.go +++ b/api/mojang/queue/queue.go @@ -1,51 +1,95 @@ -// Based on the implementation from https://flaviocopes.com/golang-data-structure-queue/ - package queue import ( + "strings" "sync" + "time" "github.com/elyby/chrly/api/mojang" ) -type Job struct { - Username string - RespondTo chan *mojang.SignedTexturesResponse +var onFirstCall sync.Once +var queue = jobsQueue{} + +func ScheduleTexturesForUsername(username string) (resultChan chan *mojang.SignedTexturesResponse) { + onFirstCall.Do(func() { + queue.New() + startQueue() + }) + + // TODO: prevent of adding the same username more than once + queue.Enqueue(&jobItem{username, resultChan}) + + return } -type JobsQueue struct { - items []*Job - lock sync.RWMutex +func startQueue() { + go func() { + for { + start := time.Now() + queueRound() + time.Sleep(time.Second - time.Since(start)) + } + }() } -func (s *JobsQueue) New() *JobsQueue { - s.items = []*Job{} - return s -} - -func (s *JobsQueue) Enqueue(t *Job) { - s.lock.Lock() - s.items = append(s.items, t) - s.lock.Unlock() -} - -func (s *JobsQueue) Dequeue(n int) []*Job { - s.lock.Lock() - if n > s.Size() { - n = s.Size() +func queueRound() { + if queue.IsEmpty() { + return } - items := s.items[0:n] - s.items = s.items[n:len(s.items)] - s.lock.Unlock() + jobs := queue.Dequeue(100) + var usernames []string + for _, job := range jobs { + usernames = append(usernames, job.Username) + } - return items -} + profiles, err := mojang.UsernamesToUuids(usernames) + switch err.(type) { + case *mojang.TooManyRequestsError: + for _, job := range jobs { + job.RespondTo <- nil + } -func (s *JobsQueue) IsEmpty() bool { - return len(s.items) == 0 -} + return + case error: + panic(err) + } -func (s *JobsQueue) Size() int { - return len(s.items) + var wg sync.WaitGroup + for _, job := range jobs { + wg.Add(1) + go func() { + var result *mojang.SignedTexturesResponse + shouldCache := true + var uuid string + for _, profile := range profiles { + if strings.EqualFold(job.Username, profile.Name) { + uuid = profile.Id + break + } + } + + if uuid != "" { + result, err = mojang.UuidToTextures(uuid, true) + if err != nil { + if _, ok := err.(*mojang.TooManyRequestsError); !ok { + panic(err) + } + + shouldCache = false + } + } + + wg.Done() + + job.RespondTo <- result + + if shouldCache { + // TODO: store result to cache + } + }() + } + + wg.Wait() }