DataCache and changes for game update
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.
This commit is contained in:
34
lib/datacache/datacacheinterface/datacacheinterface.go
Normal file
34
lib/datacache/datacacheinterface/datacacheinterface.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// i2_bot – Instinct PokememBro Bot
|
||||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||||
|
||||
package datacacheinterface
|
||||
|
||||
import (
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||||
)
|
||||
|
||||
// DataCacheInterface implements DataCache for importing via appcontext.
|
||||
type DataCacheInterface interface {
|
||||
Init()
|
||||
|
||||
AddPlayer(player *dbmapping.Player) (int, error)
|
||||
GetOrCreatePlayerByTelegramID(telegramID int) (*dbmapping.Player, error)
|
||||
GetPlayerByID(playerID int) (*dbmapping.Player, error)
|
||||
GetPlayerByTelegramID(telegramID int) (*dbmapping.Player, error)
|
||||
UpdatePlayerFields(player *dbmapping.Player) (*dbmapping.Player, error)
|
||||
UpdatePlayerTimestamp(playerID int) error
|
||||
|
||||
AddProfile(profile *dbmapping.Profile) (int, error)
|
||||
GetPlayersWithCurrentProfiles() map[int]*dbmapping.PlayerProfile
|
||||
GetProfileByID(profileID int) (*dbmapping.Profile, error)
|
||||
GetProfileByPlayerID(playerID int) (*dbmapping.Profile, error)
|
||||
|
||||
AddPokememe(pokememeData map[string]string, pokememeLocations map[string]string, pokememeElements map[string]string) (int, error)
|
||||
GetAllPokememes() map[int]*dbmapping.PokememeFull
|
||||
GetPokememeByID(pokememeID int) (*dbmapping.PokememeFull, error)
|
||||
GetPokememeByName(name string) (*dbmapping.PokememeFull, error)
|
||||
DeletePokememeByID(pokememeID int) error
|
||||
|
||||
GetLeagueBySymbol(symbol string) (*dbmapping.League, error)
|
||||
GetWeaponTypeByName(name string) (*dbmapping.Weapon, error)
|
||||
}
|
45
lib/datacache/elements.go
Normal file
45
lib/datacache/elements.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// i2_bot – Instinct PokememBro Bot
|
||||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||||
|
||||
package datacache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (dc *DataCache) initElements() {
|
||||
c.Log.Info("Initializing Elements storage...")
|
||||
dc.elements = make(map[int]*dbmapping.Element)
|
||||
}
|
||||
|
||||
func (dc *DataCache) loadElements() {
|
||||
c.Log.Info("Load current Elements data from database to DataCache...")
|
||||
elements := []dbmapping.Element{}
|
||||
err := c.Db.Select(&elements, "SELECT * FROM elements")
|
||||
if err != nil {
|
||||
// This is critical error and we need to stop immediately!
|
||||
c.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dc.elementsMutex.Lock()
|
||||
for i := range elements {
|
||||
dc.elements[elements[i].ID] = &elements[i]
|
||||
}
|
||||
c.Log.Info("Loaded elements in DataCache: " + strconv.Itoa(len(dc.elements)))
|
||||
dc.elementsMutex.Unlock()
|
||||
}
|
||||
|
||||
func (dc *DataCache) findElementIDBySymbol(symbol string) (int, error) {
|
||||
dc.elementsMutex.Lock()
|
||||
for i := range dc.elements {
|
||||
if dc.elements[i].Symbol == symbol {
|
||||
dc.elementsMutex.Unlock()
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
|
||||
dc.elementsMutex.Unlock()
|
||||
return 0, errors.New("There is no element with symbol = " + symbol)
|
||||
}
|
76
lib/datacache/exported.go
Normal file
76
lib/datacache/exported.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// i2_bot – Instinct PokememBro Bot
|
||||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||||
|
||||
package datacache
|
||||
|
||||
import (
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/appcontext"
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/datacache/datacacheinterface"
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
c *appcontext.Context
|
||||
)
|
||||
|
||||
// DataCache is a function-handling struct for package datacache.
|
||||
// Also, it's a data cache: it handles all data, powered by DataCache functions.
|
||||
type DataCache struct {
|
||||
// Players — users of bot
|
||||
players map[int]*dbmapping.Player
|
||||
playersMutex sync.Mutex
|
||||
// Profiles - game profiles, no matter, actual or not
|
||||
profiles map[int]*dbmapping.Profile
|
||||
profilesMutex sync.Mutex
|
||||
// Current profiles - actual profiles for players, mostly used by bot
|
||||
// Note: int in this array for player ID, not for profile ID
|
||||
currentProfiles map[int]*dbmapping.Profile
|
||||
currentProfilesMutex sync.Mutex
|
||||
// Pokememes
|
||||
pokememes map[int]*dbmapping.Pokememe
|
||||
pokememesMutex sync.Mutex
|
||||
// Pokememes with all supported data
|
||||
fullPokememes map[int]*dbmapping.PokememeFull
|
||||
fullPokememesMutex sync.Mutex
|
||||
|
||||
// Elements
|
||||
elements map[int]*dbmapping.Element
|
||||
elementsMutex sync.Mutex
|
||||
// Leagues
|
||||
leagues map[int]*dbmapping.League
|
||||
leaguesMutex sync.Mutex
|
||||
// Locations
|
||||
locations map[int]*dbmapping.Location
|
||||
locationsMutex sync.Mutex
|
||||
// Weapons
|
||||
weapons map[int]*dbmapping.Weapon
|
||||
weaponsMutex sync.Mutex
|
||||
}
|
||||
|
||||
// New is an initialization function for appcontext
|
||||
func New(ac *appcontext.Context) {
|
||||
c = ac
|
||||
dc := &DataCache{}
|
||||
c.RegisterDataCacheInterface(datacacheinterface.DataCacheInterface(dc))
|
||||
}
|
||||
|
||||
// Init is a initialization function for package
|
||||
func (dc *DataCache) Init() {
|
||||
c.Log.Info("Initializing DataCache...")
|
||||
|
||||
dc.initElements()
|
||||
dc.loadElements()
|
||||
dc.initLeagues()
|
||||
dc.loadLeagues()
|
||||
dc.initLocations()
|
||||
dc.loadLocations()
|
||||
dc.initWeapons()
|
||||
dc.loadWeapons()
|
||||
dc.initPokememes()
|
||||
dc.loadPokememes()
|
||||
dc.initPlayers()
|
||||
dc.loadPlayers()
|
||||
dc.initProfiles()
|
||||
dc.loadProfiles()
|
||||
}
|
48
lib/datacache/leagues.go
Normal file
48
lib/datacache/leagues.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// i2_bot – Instinct PokememBro Bot
|
||||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||||
|
||||
package datacache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (dc *DataCache) initLeagues() {
|
||||
c.Log.Info("Initializing Leagues storage...")
|
||||
dc.leagues = make(map[int]*dbmapping.League)
|
||||
}
|
||||
|
||||
func (dc *DataCache) loadLeagues() {
|
||||
c.Log.Info("Load current Leagues data from database to DataCache...")
|
||||
leagues := []dbmapping.League{}
|
||||
err := c.Db.Select(&leagues, "SELECT * FROM leagues")
|
||||
if err != nil {
|
||||
// This is critical error and we need to stop immediately!
|
||||
c.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dc.leaguesMutex.Lock()
|
||||
for i := range leagues {
|
||||
dc.leagues[leagues[i].ID] = &leagues[i]
|
||||
}
|
||||
c.Log.Info("Loaded leagues in DataCache: " + strconv.Itoa(len(dc.leagues)))
|
||||
dc.leaguesMutex.Unlock()
|
||||
}
|
||||
|
||||
// External functions
|
||||
|
||||
// GetLeagueBySymbol returns league from datacache by emoji
|
||||
func (dc *DataCache) GetLeagueBySymbol(symbol string) (*dbmapping.League, error) {
|
||||
dc.leaguesMutex.Lock()
|
||||
for i := range dc.leagues {
|
||||
if dc.leagues[i].Symbol == symbol {
|
||||
dc.leaguesMutex.Unlock()
|
||||
return dc.leagues[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
dc.leaguesMutex.Unlock()
|
||||
return nil, errors.New("There is no league with symbol = " + symbol)
|
||||
}
|
45
lib/datacache/locations.go
Normal file
45
lib/datacache/locations.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// i2_bot – Instinct PokememBro Bot
|
||||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||||
|
||||
package datacache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (dc *DataCache) initLocations() {
|
||||
c.Log.Info("Initializing Locations storage...")
|
||||
dc.locations = make(map[int]*dbmapping.Location)
|
||||
}
|
||||
|
||||
func (dc *DataCache) loadLocations() {
|
||||
c.Log.Info("Load current Locations data from database to DataCache...")
|
||||
locations := []dbmapping.Location{}
|
||||
err := c.Db.Select(&locations, "SELECT * FROM locations")
|
||||
if err != nil {
|
||||
// This is critical error and we need to stop immediately!
|
||||
c.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dc.locationsMutex.Lock()
|
||||
for i := range locations {
|
||||
dc.locations[locations[i].ID] = &locations[i]
|
||||
}
|
||||
c.Log.Info("Loaded locations in DataCache: " + strconv.Itoa(len(dc.locations)))
|
||||
dc.locationsMutex.Unlock()
|
||||
}
|
||||
|
||||
func (dc *DataCache) findLocationIDByName(name string) (int, error) {
|
||||
dc.locationsMutex.Lock()
|
||||
for i := range dc.locations {
|
||||
if dc.locations[i].Name == name {
|
||||
dc.locationsMutex.Unlock()
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
|
||||
dc.locationsMutex.Unlock()
|
||||
return 0, errors.New("There is no location with name = " + name)
|
||||
}
|
157
lib/datacache/players.go
Normal file
157
lib/datacache/players.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// i2_bot – Instinct PokememBro Bot
|
||||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||||
|
||||
package datacache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (dc *DataCache) initPlayers() {
|
||||
c.Log.Info("Initializing Players storage...")
|
||||
dc.players = make(map[int]*dbmapping.Player)
|
||||
}
|
||||
|
||||
func (dc *DataCache) loadPlayers() {
|
||||
c.Log.Info("Load current Players data from database to DataCache...")
|
||||
players := []dbmapping.Player{}
|
||||
err := c.Db.Select(&players, "SELECT * FROM players")
|
||||
if err != nil {
|
||||
// This is critical error and we need to stop immediately!
|
||||
c.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dc.playersMutex.Lock()
|
||||
for i := range players {
|
||||
dc.players[players[i].ID] = &players[i]
|
||||
}
|
||||
c.Log.Info("Loaded players in DataCache: " + strconv.Itoa(len(dc.players)))
|
||||
dc.playersMutex.Unlock()
|
||||
}
|
||||
|
||||
// External functions
|
||||
|
||||
// AddPlayer creates new player in database
|
||||
func (dc *DataCache) AddPlayer(player *dbmapping.Player) (int, error) {
|
||||
c.Log.Info("DataCache: Creating new user...")
|
||||
_, err := c.Db.NamedExec("INSERT INTO players VALUES(NULL, :telegram_id, :league_id, :status, :created_at, :updated_at)", &player)
|
||||
if err != nil {
|
||||
c.Log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
insertedPlayer := dbmapping.Player{}
|
||||
err = c.Db.Get(&insertedPlayer, c.Db.Rebind("SELECT * FROM players WHERE telegram_id=:telegram_id, league_id=:league_id, status=:status, created_at=:created_at, updated_at=:updated_at"), &player)
|
||||
if err != nil {
|
||||
c.Log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
dc.playersMutex.Lock()
|
||||
dc.players[insertedPlayer.ID] = &insertedPlayer
|
||||
dc.playersMutex.Unlock()
|
||||
|
||||
return insertedPlayer.ID, nil
|
||||
}
|
||||
|
||||
// GetOrCreatePlayerByTelegramID finds user by Telegram ID and creates one if not exist
|
||||
func (dc *DataCache) GetOrCreatePlayerByTelegramID(telegramID int) (*dbmapping.Player, error) {
|
||||
c.Log.Info("DataCache: Getting player with Telegram ID=", telegramID)
|
||||
|
||||
var player *dbmapping.Player
|
||||
dc.playersMutex.Lock()
|
||||
for i := range dc.players {
|
||||
if dc.players[i].TelegramID == telegramID {
|
||||
player = dc.players[i]
|
||||
}
|
||||
}
|
||||
dc.playersMutex.Unlock()
|
||||
|
||||
if player == nil {
|
||||
c.Log.Info("There is no such user, creating one...")
|
||||
newPlayer := dbmapping.Player{}
|
||||
newPlayer.TelegramID = telegramID
|
||||
newPlayer.LeagueID = 0
|
||||
newPlayer.Status = "nobody"
|
||||
newPlayer.CreatedAt = time.Now().UTC()
|
||||
newPlayer.UpdatedAt = time.Now().UTC()
|
||||
|
||||
newPlayerID, err := dc.AddPlayer(&newPlayer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
player = dc.players[newPlayerID]
|
||||
}
|
||||
|
||||
return player, nil
|
||||
}
|
||||
|
||||
// GetPlayerByID returns player from datacache by ID
|
||||
func (dc *DataCache) GetPlayerByID(playerID int) (*dbmapping.Player, error) {
|
||||
c.Log.Info("DataCache: Getting player with ID=", playerID)
|
||||
if dc.players[playerID] != nil {
|
||||
return dc.players[playerID], nil
|
||||
}
|
||||
|
||||
return nil, errors.New("There is no user with ID = " + strconv.Itoa(playerID))
|
||||
}
|
||||
|
||||
// GetPlayerByTelegramID returns player with such Telegram ID
|
||||
func (dc *DataCache) GetPlayerByTelegramID(telegramID int) (*dbmapping.Player, error) {
|
||||
c.Log.Info("DataCache: Getting player with Telegram ID=", telegramID)
|
||||
|
||||
var player *dbmapping.Player
|
||||
dc.playersMutex.Lock()
|
||||
for i := range dc.players {
|
||||
if dc.players[i].TelegramID == telegramID {
|
||||
player = dc.players[i]
|
||||
}
|
||||
}
|
||||
dc.playersMutex.Unlock()
|
||||
|
||||
if player != nil {
|
||||
return player, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("There is no user with Telegram ID = " + strconv.Itoa(telegramID))
|
||||
}
|
||||
|
||||
// UpdatePlayerFields writes new fields to player
|
||||
func (dc *DataCache) UpdatePlayerFields(player *dbmapping.Player) (*dbmapping.Player, error) {
|
||||
if dc.players[player.ID] != nil {
|
||||
_, err := c.Db.NamedExec("UPDATE `players` SET league_id=:league_id, status=:status WHERE id=:id", player)
|
||||
if err != nil {
|
||||
c.Log.Error(err.Error())
|
||||
return dc.players[player.ID], err
|
||||
}
|
||||
dc.playersMutex.Lock()
|
||||
dc.players[player.ID].LeagueID = player.LeagueID
|
||||
dc.players[player.ID].Status = player.Status
|
||||
dc.playersMutex.Unlock()
|
||||
return dc.players[player.ID], nil
|
||||
}
|
||||
|
||||
return nil, errors.New("There is no user with ID = " + strconv.Itoa(player.ID))
|
||||
}
|
||||
|
||||
// UpdatePlayerTimestamp writes current update time to player
|
||||
func (dc *DataCache) UpdatePlayerTimestamp(playerID int) error {
|
||||
if dc.players[playerID] != nil {
|
||||
dc.playersMutex.Lock()
|
||||
dc.players[playerID].UpdatedAt = time.Now().UTC()
|
||||
_, err := c.Db.NamedExec("UPDATE `players` SET updated_at=:updated_at WHERE id=:id", dc.players[playerID])
|
||||
if err != nil {
|
||||
c.Log.Error(err.Error())
|
||||
dc.playersMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
dc.playersMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("There is no user with ID = " + strconv.Itoa(playerID))
|
||||
}
|
297
lib/datacache/pokememes.go
Normal file
297
lib/datacache/pokememes.go
Normal file
@@ -0,0 +1,297 @@
|
||||
// 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
|
||||
}
|
118
lib/datacache/profiles.go
Normal file
118
lib/datacache/profiles.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// i2_bot – Instinct PokememBro Bot
|
||||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||||
|
||||
package datacache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (dc *DataCache) initProfiles() {
|
||||
c.Log.Info("Initializing Profiles storage...")
|
||||
dc.profiles = make(map[int]*dbmapping.Profile)
|
||||
dc.currentProfiles = make(map[int]*dbmapping.Profile)
|
||||
}
|
||||
|
||||
func (dc *DataCache) loadProfiles() {
|
||||
c.Log.Info("Load current Profiles data from database to DataCache...")
|
||||
profiles := []dbmapping.Profile{}
|
||||
err := c.Db.Select(&profiles, "SELECT * FROM profiles")
|
||||
if err != nil {
|
||||
// This is critical error and we need to stop immediately!
|
||||
c.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dc.profilesMutex.Lock()
|
||||
dc.currentProfilesMutex.Lock()
|
||||
for i := range profiles {
|
||||
dc.profiles[profiles[i].ID] = &profiles[i]
|
||||
|
||||
// Filling current profiles
|
||||
if dc.currentProfiles[profiles[i].PlayerID] != nil {
|
||||
if dc.currentProfiles[profiles[i].PlayerID].CreatedAt.Before(profiles[i].CreatedAt) {
|
||||
dc.currentProfiles[profiles[i].PlayerID] = &profiles[i]
|
||||
}
|
||||
}
|
||||
|
||||
dc.currentProfiles[profiles[i].PlayerID] = &profiles[i]
|
||||
}
|
||||
c.Log.Info("Loaded profiles in DataCache: " + strconv.Itoa(len(dc.profiles)))
|
||||
c.Log.Info("Loaded current profiles in DataCache: " + strconv.Itoa(len(dc.currentProfiles)))
|
||||
dc.profilesMutex.Unlock()
|
||||
dc.currentProfilesMutex.Unlock()
|
||||
}
|
||||
|
||||
// External functions
|
||||
|
||||
// AddProfile creates new profile in database
|
||||
func (dc *DataCache) AddProfile(profile *dbmapping.Profile) (int, error) {
|
||||
_, err := c.Db.NamedExec("INSERT INTO `profiles` VALUES(NULL, :player_id, :nickname, :telegram_nickname, :level_id, :pokeballs, :wealth, :pokememes_wealth, :exp, :egg_exp, :power, :weapon_id, :crystalls, :created_at)", &profile)
|
||||
if err != nil {
|
||||
c.Log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
insertedProfile := dbmapping.Profile{}
|
||||
err = c.Db.Get(&insertedProfile, c.Db.Rebind("SELECT * FROM profiles WHERE player_id=? AND created_at=?"), profile.PlayerID, profile.CreatedAt)
|
||||
if err != nil {
|
||||
c.Log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
dc.profilesMutex.Lock()
|
||||
dc.currentProfilesMutex.Lock()
|
||||
dc.profiles[insertedProfile.ID] = &insertedProfile
|
||||
dc.currentProfiles[insertedProfile.PlayerID] = &insertedProfile
|
||||
dc.currentProfilesMutex.Unlock()
|
||||
dc.profilesMutex.Unlock()
|
||||
|
||||
return insertedProfile.ID, nil
|
||||
}
|
||||
|
||||
// GetPlayersWithCurrentProfiles returns users with profiles, if exist
|
||||
func (dc *DataCache) GetPlayersWithCurrentProfiles() map[int]*dbmapping.PlayerProfile {
|
||||
dc.currentProfilesMutex.Lock()
|
||||
dc.playersMutex.Lock()
|
||||
|
||||
users := make(map[int]*dbmapping.PlayerProfile)
|
||||
|
||||
for i := range dc.players {
|
||||
user := dbmapping.PlayerProfile{}
|
||||
user.Player = *dc.players[i]
|
||||
if dc.players[i].LeagueID != 0 {
|
||||
user.League = *dc.leagues[dc.players[i].LeagueID]
|
||||
}
|
||||
|
||||
if dc.currentProfiles[dc.players[i].ID] != nil {
|
||||
user.HaveProfile = true
|
||||
user.Profile = *dc.currentProfiles[dc.players[i].ID]
|
||||
} else {
|
||||
user.HaveProfile = false
|
||||
}
|
||||
|
||||
users[dc.players[i].ID] = &user
|
||||
}
|
||||
|
||||
dc.currentProfilesMutex.Unlock()
|
||||
dc.playersMutex.Unlock()
|
||||
return users
|
||||
}
|
||||
|
||||
// GetProfileByID returns profile from datacache by ID
|
||||
func (dc *DataCache) GetProfileByID(profileID int) (*dbmapping.Profile, error) {
|
||||
if dc.profiles[profileID] != nil {
|
||||
return dc.profiles[profileID], nil
|
||||
}
|
||||
|
||||
return nil, errors.New("There is no profile with ID = " + strconv.Itoa(profileID))
|
||||
}
|
||||
|
||||
// GetProfileByPlayerID returns current profile for player
|
||||
func (dc *DataCache) GetProfileByPlayerID(playerID int) (*dbmapping.Profile, error) {
|
||||
if dc.currentProfiles[playerID] != nil {
|
||||
return dc.currentProfiles[playerID], nil
|
||||
}
|
||||
|
||||
return nil, errors.New("There is no profile for user with ID = " + strconv.Itoa(playerID))
|
||||
}
|
48
lib/datacache/weapons.go
Normal file
48
lib/datacache/weapons.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// i2_bot – Instinct PokememBro Bot
|
||||
// Copyright (c) 2018 Vladimir "fat0troll" Hodakov
|
||||
|
||||
package datacache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (dc *DataCache) initWeapons() {
|
||||
c.Log.Info("Initializing Weapons storage...")
|
||||
dc.weapons = make(map[int]*dbmapping.Weapon)
|
||||
}
|
||||
|
||||
func (dc *DataCache) loadWeapons() {
|
||||
c.Log.Info("Load current Weapons data from database to DataCache...")
|
||||
weapons := []dbmapping.Weapon{}
|
||||
err := c.Db.Select(&weapons, "SELECT * FROM weapons")
|
||||
if err != nil {
|
||||
// This is critical error and we need to stop immediately!
|
||||
c.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
dc.weaponsMutex.Lock()
|
||||
for i := range weapons {
|
||||
dc.weapons[weapons[i].ID] = &weapons[i]
|
||||
}
|
||||
c.Log.Info("Loaded weapon types in DataCache: " + strconv.Itoa(len(dc.weapons)))
|
||||
dc.weaponsMutex.Unlock()
|
||||
}
|
||||
|
||||
// External functions
|
||||
|
||||
// GetWeaponTypeByName returns weapon type from datacache by weapon name
|
||||
func (dc *DataCache) GetWeaponTypeByName(name string) (*dbmapping.Weapon, error) {
|
||||
dc.weaponsMutex.Lock()
|
||||
for i := range dc.weapons {
|
||||
if dc.weapons[i].Name == name {
|
||||
dc.weaponsMutex.Unlock()
|
||||
return dc.weapons[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
dc.weaponsMutex.Unlock()
|
||||
return nil, errors.New("There is no weapon type with name = " + name)
|
||||
}
|
Reference in New Issue
Block a user