This commit is contained in:
ErickSkrauch 2019-04-15 01:32:22 +03:00
parent d2d6d07fa6
commit 879a33344b
4 changed files with 137 additions and 138 deletions

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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

View File

@ -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()
}