Vladimir Hodakov
b8226d8aa8
Recent game update changed pokememes view in pokedeks, so we need to reflect it by updating parser. Introducing DataCache - a silver bullet for eliminating lags linked to database queries. Less queries, more in RAM, faster work. Needs testing in production environment.
298 lines
9.4 KiB
Go
298 lines
9.4 KiB
Go
// i2_bot – Instinct PokememBro Bot
|
||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||
|
||
package datacache
|
||
|
||
import (
|
||
"errors"
|
||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
func (dc *DataCache) initPokememes() {
|
||
c.Log.Info("Initializing Pokememes storage...")
|
||
dc.pokememes = make(map[int]*dbmapping.Pokememe)
|
||
dc.fullPokememes = make(map[int]*dbmapping.PokememeFull)
|
||
}
|
||
|
||
func (dc *DataCache) loadPokememes() {
|
||
c.Log.Info("Load current Pokememes data from database to DataCache...")
|
||
pokememes := []dbmapping.Pokememe{}
|
||
err := c.Db.Select(&pokememes, "SELECT * FROM pokememes")
|
||
if err != nil {
|
||
// This is critical error and we need to stop immediately!
|
||
c.Log.Fatal(err.Error())
|
||
}
|
||
pokememesElements := []dbmapping.PokememeElement{}
|
||
err = c.Db.Select(&pokememesElements, "SELECT * FROM pokememes_elements")
|
||
if err != nil {
|
||
// This is critical error and we need to stop immediately!
|
||
c.Log.Fatal(err)
|
||
}
|
||
pokememesLocations := []dbmapping.PokememeLocation{}
|
||
err = c.Db.Select(&pokememesLocations, "SELECT * FROM pokememes_locations")
|
||
if err != nil {
|
||
// This is critical error and we need to stop immediately!
|
||
c.Log.Fatal(err)
|
||
}
|
||
|
||
dc.pokememesMutex.Lock()
|
||
dc.fullPokememesMutex.Lock()
|
||
for i := range pokememes {
|
||
dc.pokememes[pokememes[i].ID] = &pokememes[i]
|
||
|
||
// Filling fullPokememes
|
||
fullPokememe := dbmapping.PokememeFull{}
|
||
elementsListed := []dbmapping.Element{}
|
||
locationsListed := []dbmapping.Location{}
|
||
|
||
for j := range pokememesLocations {
|
||
if pokememesLocations[j].PokememeID == pokememes[i].ID {
|
||
for l := range dc.locations {
|
||
if pokememesLocations[j].LocationID == dc.locations[l].ID {
|
||
locationsListed = append(locationsListed, *dc.locations[l])
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for k := range pokememesElements {
|
||
if pokememesElements[k].PokememeID == pokememes[i].ID {
|
||
for e := range dc.elements {
|
||
if pokememesElements[k].ElementID == dc.elements[e].ID {
|
||
elementsListed = append(elementsListed, *dc.elements[e])
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fullPokememe.Pokememe = pokememes[i]
|
||
fullPokememe.Elements = elementsListed
|
||
fullPokememe.Locations = locationsListed
|
||
|
||
dc.fullPokememes[pokememes[i].ID] = &fullPokememe
|
||
}
|
||
c.Log.Info("Loaded pokememes with all additional information in DataCache: " + strconv.Itoa(len(dc.fullPokememes)))
|
||
dc.pokememesMutex.Unlock()
|
||
dc.fullPokememesMutex.Unlock()
|
||
}
|
||
|
||
// External functions
|
||
|
||
// AddPokememe adds pokememe from parser
|
||
func (dc *DataCache) AddPokememe(pokememeData map[string]string, pokememeLocations map[string]string, pokememeElements map[string]string) (int, error) {
|
||
_, noerr := dc.GetPokememeByName(pokememeData["name"])
|
||
if noerr == nil {
|
||
return 0, errors.New("This pokememe already exists")
|
||
}
|
||
|
||
gradeInt := c.Statistics.GetPoints(pokememeData["grade"])
|
||
attackInt := c.Statistics.GetPoints(pokememeData["attack"])
|
||
hpInt := c.Statistics.GetPoints(pokememeData["hp"])
|
||
mpInt := c.Statistics.GetPoints(pokememeData["mp"])
|
||
defenceInt := attackInt
|
||
if pokememeData["defence"] != "" {
|
||
defenceInt = c.Statistics.GetPoints(pokememeData["defence"])
|
||
}
|
||
priceInt := c.Statistics.GetPoints(pokememeData["price"])
|
||
creatorID := c.Statistics.GetPoints(pokememeData["creator_id"])
|
||
|
||
if !(gradeInt != 0 && attackInt != 0 && hpInt != 0 && mpInt != 0 && defenceInt != 0 && priceInt != 0 && creatorID != 0) {
|
||
return 0, errors.New("Some of the required numerical values are empty")
|
||
}
|
||
|
||
pokememe := dbmapping.Pokememe{}
|
||
pokememe.Grade = gradeInt
|
||
pokememe.Name = pokememeData["name"]
|
||
pokememe.Description = pokememeData["description"]
|
||
pokememe.Attack = attackInt
|
||
pokememe.HP = hpInt
|
||
pokememe.MP = mpInt
|
||
pokememe.Defence = defenceInt
|
||
pokememe.Price = priceInt
|
||
if pokememeData["purchaseable"] == "true" {
|
||
pokememe.Purchaseable = true
|
||
} else {
|
||
pokememe.Purchaseable = false
|
||
}
|
||
pokememe.ImageURL = pokememeData["image"]
|
||
pokememe.PlayerID = creatorID
|
||
pokememe.CreatedAt = time.Now().UTC()
|
||
|
||
locations := []dbmapping.Location{}
|
||
elements := []dbmapping.Element{}
|
||
|
||
for i := range pokememeLocations {
|
||
locationID, err := dc.findLocationIDByName(pokememeLocations[i])
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
locations = append(locations, *dc.locations[locationID])
|
||
}
|
||
|
||
for i := range pokememeElements {
|
||
elementID, err := dc.findElementIDBySymbol(pokememeElements[i])
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
elements = append(elements, *dc.elements[elementID])
|
||
}
|
||
|
||
// All objects are prepared, let's fill database with it!
|
||
c.Log.Debug("Filling pokememe...")
|
||
_, err := c.Db.NamedExec("INSERT INTO pokememes VALUES(NULL, :grade, :name, :description, :attack, :hp, :mp, :defence, :price, :purchaseable, :image_url, :player_id, :created_at)", &pokememe)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
c.Log.Debug("Finding newly added pokememe...")
|
||
insertedPokememe := dbmapping.Pokememe{}
|
||
err = c.Db.Get(&insertedPokememe, c.Db.Rebind("SELECT * FROM pokememes WHERE grade=? AND name=?"), pokememe.Grade, pokememe.Name)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
// Now we creating locations and elements links
|
||
locationsAndElementsFilledSuccessfully := true
|
||
c.Log.Debug("Filling locations...")
|
||
for i := range locations {
|
||
link := dbmapping.PokememeLocation{}
|
||
link.PokememeID = insertedPokememe.ID
|
||
link.LocationID = locations[i].ID
|
||
link.CreatedAt = time.Now().UTC()
|
||
|
||
_, err := c.Db.NamedExec("INSERT INTO pokememes_locations VALUES(NULL, :pokememe_id, :location_id, :created_at)", &link)
|
||
if err != nil {
|
||
c.Log.Error(err.Error())
|
||
locationsAndElementsFilledSuccessfully = false
|
||
}
|
||
}
|
||
|
||
c.Log.Debug("Filling elements...")
|
||
for i := range elements {
|
||
link := dbmapping.PokememeElement{}
|
||
link.PokememeID = insertedPokememe.ID
|
||
link.ElementID = elements[i].ID
|
||
link.CreatedAt = time.Now().UTC()
|
||
|
||
_, err := c.Db.NamedExec("INSERT INTO pokememes_elements VALUES(NULL, :pokememe_id, :element_id, :created_at)", &link)
|
||
if err != nil {
|
||
c.Log.Error(err.Error())
|
||
locationsAndElementsFilledSuccessfully = false
|
||
}
|
||
}
|
||
|
||
if !locationsAndElementsFilledSuccessfully {
|
||
c.Log.Debug("All fucked up, removing what we have already added...")
|
||
// There is something fucked up. In normal state we're should never reach this code
|
||
_, err = c.Db.NamedExec("DELETE FROM pokememes_locations WHERE pokememe_id=:id", &insertedPokememe)
|
||
if err != nil {
|
||
c.Log.Error(err.Error())
|
||
}
|
||
_, err = c.Db.NamedExec("DELETE FROM pokememes_elements WHERE pokememe_id=:id", &insertedPokememe)
|
||
if err != nil {
|
||
c.Log.Error(err.Error())
|
||
}
|
||
_, err = c.Db.NamedExec("DELETE FROM pokememes where id=:id", &insertedPokememe)
|
||
if err != nil {
|
||
c.Log.Error(err.Error())
|
||
}
|
||
return 0, errors.New("Failed to add pokememe to database")
|
||
}
|
||
|
||
fullPokememe := dbmapping.PokememeFull{}
|
||
fullPokememe.Pokememe = insertedPokememe
|
||
fullPokememe.Locations = locations
|
||
fullPokememe.Elements = elements
|
||
|
||
// Filling data cache
|
||
dc.pokememesMutex.Lock()
|
||
dc.fullPokememesMutex.Lock()
|
||
dc.pokememes[insertedPokememe.ID] = &insertedPokememe
|
||
dc.fullPokememes[insertedPokememe.ID] = &fullPokememe
|
||
dc.pokememesMutex.Unlock()
|
||
dc.fullPokememesMutex.Unlock()
|
||
|
||
return insertedPokememe.ID, nil
|
||
}
|
||
|
||
// GetAllPokememes returns all pokememes
|
||
// Index in resulted map counts all pokememes ordered by grade and alphabetically
|
||
func (dc *DataCache) GetAllPokememes() map[int]*dbmapping.PokememeFull {
|
||
pokememes := make(map[int]*dbmapping.PokememeFull)
|
||
dc.fullPokememesMutex.Lock()
|
||
|
||
var keys []string
|
||
keysToIDs := make(map[string]int)
|
||
for i := range dc.fullPokememes {
|
||
keys = append(keys, strconv.Itoa(dc.fullPokememes[i].Pokememe.Grade)+"_"+dc.fullPokememes[i].Pokememe.Name)
|
||
keysToIDs[strconv.Itoa(dc.fullPokememes[i].Pokememe.Grade)+"_"+dc.fullPokememes[i].Pokememe.Name] = i
|
||
}
|
||
sort.Strings(keys)
|
||
|
||
idx := 0
|
||
for _, k := range keys {
|
||
pokememes[idx] = dc.fullPokememes[keysToIDs[k]]
|
||
idx++
|
||
}
|
||
|
||
dc.fullPokememesMutex.Unlock()
|
||
return pokememes
|
||
}
|
||
|
||
// GetPokememeByID returns pokememe with additional information by ID
|
||
func (dc *DataCache) GetPokememeByID(pokememeID int) (*dbmapping.PokememeFull, error) {
|
||
dc.fullPokememesMutex.Lock()
|
||
if dc.fullPokememes[pokememeID] != nil {
|
||
dc.fullPokememesMutex.Unlock()
|
||
return dc.fullPokememes[pokememeID], nil
|
||
}
|
||
dc.fullPokememesMutex.Unlock()
|
||
return nil, errors.New("There is no pokememe with ID = " + strconv.Itoa(pokememeID))
|
||
}
|
||
|
||
// GetPokememeByName returns pokememe from datacache by name
|
||
func (dc *DataCache) GetPokememeByName(name string) (*dbmapping.PokememeFull, error) {
|
||
dc.fullPokememesMutex.Lock()
|
||
for i := range dc.fullPokememes {
|
||
if strings.HasPrefix(dc.fullPokememes[i].Pokememe.Name, name) {
|
||
dc.fullPokememesMutex.Unlock()
|
||
return dc.fullPokememes[i], nil
|
||
}
|
||
}
|
||
dc.fullPokememesMutex.Unlock()
|
||
return nil, errors.New("There is no pokememe with name = " + name)
|
||
}
|
||
|
||
// DeletePokememeByID removes pokememe from database
|
||
func (dc *DataCache) DeletePokememeByID(pokememeID int) error {
|
||
pokememe, err := dc.GetPokememeByID(pokememeID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
_, err = c.Db.NamedExec("DELETE FROM pokememes_locations WHERE pokememe_id=:id", &pokememe.Pokememe)
|
||
if err != nil {
|
||
c.Log.Error(err.Error())
|
||
}
|
||
_, err = c.Db.NamedExec("DELETE FROM pokememes_elements WHERE pokememe_id=:id", &pokememe.Pokememe)
|
||
if err != nil {
|
||
c.Log.Error(err.Error())
|
||
}
|
||
_, err = c.Db.NamedExec("DELETE FROM pokememes where id=:id", &pokememe.Pokememe)
|
||
if err != nil {
|
||
c.Log.Error(err.Error())
|
||
}
|
||
|
||
dc.pokememesMutex.Lock()
|
||
dc.fullPokememesMutex.Lock()
|
||
delete(dc.pokememes, pokememe.Pokememe.ID)
|
||
delete(dc.fullPokememes, pokememe.Pokememe.ID)
|
||
dc.pokememesMutex.Unlock()
|
||
dc.fullPokememesMutex.Unlock()
|
||
return nil
|
||
}
|