probo/store/memory/memory.go

358 lines
8.6 KiB
Go

package memory
import (
"errors"
"fmt"
"strings"
"sync"
"git.andreafazzi.eu/andrea/probo/client"
"git.andreafazzi.eu/andrea/probo/hasher"
"git.andreafazzi.eu/andrea/probo/models"
"github.com/google/uuid"
)
type MemoryProboCollectorStore struct {
quizzes map[string]*models.Quiz
collections map[string]*models.Collection
questionsHashes map[string]*models.Question
answersHashes map[string]*models.Answer
quizzesHashes map[string]*models.Quiz
hasher hasher.Hasher
// A mutex is used to synchronize read/write access to the map
lock sync.RWMutex
}
func NewMemoryProboCollectorStore(hasher hasher.Hasher) *MemoryProboCollectorStore {
s := new(MemoryProboCollectorStore)
s.hasher = hasher
s.questionsHashes = make(map[string]*models.Question)
s.answersHashes = make(map[string]*models.Answer)
s.quizzesHashes = make(map[string]*models.Quiz)
s.quizzes = make(map[string]*models.Quiz)
s.collections = make(map[string]*models.Collection)
return s
}
func (s *MemoryProboCollectorStore) getQuizFromHash(hash string) *models.Quiz {
s.lock.RLock()
defer s.lock.RUnlock()
quiz, ok := s.quizzesHashes[hash]
if ok {
return quiz
}
return nil
}
func (s *MemoryProboCollectorStore) getQuizFromID(id string) *models.Quiz {
s.lock.RLock()
defer s.lock.RUnlock()
quiz, ok := s.quizzes[id]
if ok {
return quiz
}
return nil
}
func (s *MemoryProboCollectorStore) getCollectionFromID(id string) *models.Collection {
s.lock.RLock()
defer s.lock.RUnlock()
collection, ok := s.collections[id]
if ok {
return collection
}
return nil
}
func (s *MemoryProboCollectorStore) getQuestionFromHash(hash string) *models.Question {
s.lock.RLock()
defer s.lock.RUnlock()
question, ok := s.questionsHashes[hash]
if ok {
return question
}
return nil
}
func (s *MemoryProboCollectorStore) getAnswerFromHash(hash string) *models.Answer {
s.lock.RLock()
defer s.lock.RUnlock()
answer, ok := s.answersHashes[hash]
if ok {
return answer
}
return nil
}
func (s *MemoryProboCollectorStore) createQuizFromHash(id string, hash string, quiz *models.Quiz) *models.Quiz {
s.lock.Lock()
defer s.lock.Unlock()
quiz.ID = id
quiz.Hash = hash
s.quizzesHashes[hash] = quiz
s.quizzes[id] = quiz
return quiz
}
func (s *MemoryProboCollectorStore) createQuestionFromHash(hash string, question *models.Question) *models.Question {
s.lock.Lock()
defer s.lock.Unlock()
s.questionsHashes[hash] = question
return question
}
func (s *MemoryProboCollectorStore) createAnswerFromHash(hash string, answer *models.Answer) *models.Answer {
s.lock.Lock()
defer s.lock.Unlock()
s.answersHashes[hash] = answer
return answer
}
func (s *MemoryProboCollectorStore) deleteQuiz(id string) (*models.Quiz, error) {
s.lock.Lock()
defer s.lock.Unlock()
quiz := s.quizzes[id]
if quiz == nil {
return nil, fmt.Errorf("Trying to delete a quiz that doesn't exist in memory (ID: %v)", id)
}
s.quizzes[id] = nil
s.quizzesHashes[quiz.Hash] = nil
return quiz, nil
}
func (s *MemoryProboCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
result := make([]*models.Quiz, 0)
for id := range s.quizzes {
result = append(result, s.getQuizFromID(id))
}
return result, nil
}
func (s *MemoryProboCollectorStore) ReadQuizByHash(hash string) (*models.Quiz, error) {
quiz := s.getQuizFromHash(hash)
if quiz == nil {
return nil, fmt.Errorf("Quiz with hash %s was not found in the store.", hash)
}
return quiz, nil
}
func (s *MemoryProboCollectorStore) CalculateQuizHash(quiz *client.Quiz) string {
hashes := s.hasher.QuizHashes(quiz)
return hashes[len(hashes)-1]
}
func (s *MemoryProboCollectorStore) parseTextForTags(text string, tags *[]*models.Tag) string {
// Trim the following chars
trimChars := "*:.,/\\@()[]{}<>"
// Split the text into words
words := strings.Fields(text)
for _, word := range words {
// If the word starts with '#', it is considered as a tag
if strings.HasPrefix(word, "#") {
// Check if the tag already exists in the tags slice
exists := false
for _, tag := range *tags {
if tag.Name == word {
exists = true
break
}
}
// If the tag does not exist in the tags slice, add it
if !exists {
*tags = append(*tags, &models.Tag{Name: strings.TrimRight(word, trimChars)})
}
}
}
return text
}
func (s *MemoryProboCollectorStore) createOrUpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error) {
if r.Quiz == nil {
return nil, false, errors.New("A request was made passing a nil quiz object")
}
hashes := s.hasher.QuizHashes(r.Quiz)
quizHash := hashes[len(hashes)-1]
quiz := s.getQuizFromHash(quizHash)
if quiz != nil { // Quiz is already present in the store
return quiz, false, nil
}
if id != "" { // we're updating a quiz
quiz = s.getQuizFromID(id)
if quiz == nil { // Quiz is not present in the store
return nil, false, fmt.Errorf("Quiz ID %v doesn't exist in the store!", id)
}
} else {
if r.Meta != nil {
if r.Meta.ID != "" {
id = r.Meta.ID
} else {
id = uuid.New().String()
}
} else {
id = uuid.New().String()
}
quiz = new(models.Quiz)
}
if quiz.Tags == nil {
quiz.Tags = make([]*models.Tag, 0)
}
questionHash := hashes[0]
q := s.getQuestionFromHash(questionHash)
if q == nil { // if the question is not in the store then we should add it
q = s.createQuestionFromHash(questionHash, &models.Question{
ID: uuid.New().String(),
Text: s.parseTextForTags(r.Quiz.Question.Text, &quiz.Tags),
})
}
// Populate Question field
quiz.Question = q
// Reset answer slice
quiz.Answers = make([]*models.Answer, 0)
for i, answer := range r.Quiz.Answers {
answerHash := hashes[i+1]
a := s.getAnswerFromHash(answerHash)
if a == nil { // if the answer is not in the store add it
a = s.createAnswerFromHash(answerHash, &models.Answer{
ID: uuid.New().String(),
Text: s.parseTextForTags(answer.Text, &quiz.Tags),
})
}
if answer.Correct {
quiz.Correct = a
}
quiz.Answers = append(quiz.Answers, a)
}
return s.createQuizFromHash(id, quizHash, quiz), true, nil
}
func (s *MemoryProboCollectorStore) CreateQuiz(r *client.CreateUpdateQuizRequest) (*models.Quiz, error) {
q, _, err := s.createOrUpdateQuiz(r, "")
return q, err
}
func (s *MemoryProboCollectorStore) UpdateQuiz(r *client.CreateUpdateQuizRequest, id string) (*models.Quiz, bool, error) {
return s.createOrUpdateQuiz(r, id)
}
func (s *MemoryProboCollectorStore) DeleteQuiz(id string) (*models.Quiz, error) {
return s.deleteQuiz(id)
}
func (s *MemoryProboCollectorStore) ReadAllCollections() ([]*models.Collection, error) {
result := make([]*models.Collection, 0)
for id := range s.collections {
result = append(result, s.getCollectionFromID(id))
}
return result, nil
}
func (s *MemoryProboCollectorStore) CreateCollection(r *client.CreateUpdateCollectionRequest) (*models.Collection, error) {
q, _, err := s.createOrUpdateCollection(r, "")
return q, err
}
func (s *MemoryProboCollectorStore) UpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, bool, error) {
return s.createOrUpdateCollection(r, id)
}
func (s *MemoryProboCollectorStore) ReadCollectionByID(id string) (*models.Collection, error) {
collection := s.getCollectionFromID(id)
if collection == nil {
return nil, fmt.Errorf("Collection ID %v not found in the store", collection.ID)
}
return collection, nil
}
func (s *MemoryProboCollectorStore) createOrUpdateCollection(r *client.CreateUpdateCollectionRequest, id string) (*models.Collection, bool, error) {
var collection *models.Collection
if r.Collection == nil {
return nil, false, errors.New("A request was made passing a nil collection object")
}
if id != "" { // we're updating a collection
collection = s.getCollectionFromID(id)
if collection == nil { // Quiz is not present in the store
return nil, false, fmt.Errorf("Collection ID %v doesn't exist in the store!", id)
}
} else {
id = uuid.New().String()
collection = new(models.Collection)
}
collection.Name = r.Collection.Name
collection.Query = r.Collection.Query
collection.IDs = s.query(collection.Query)
return s.createCollectionFromID(id, collection), true, nil
}
func (s *MemoryProboCollectorStore) query(query string) []string {
s.lock.Lock()
defer s.lock.Unlock()
result := make([]string, 0)
for id, quiz := range s.quizzes {
for _, tag := range quiz.Tags {
if query == tag.Name {
result = append(result, id)
break
}
}
}
return result
}
func (s *MemoryProboCollectorStore) createCollectionFromID(id string, collection *models.Collection) *models.Collection {
s.lock.Lock()
defer s.lock.Unlock()
collection.ID = id
s.collections[id] = collection
return collection
}