chirpnest/obj.go
2020-02-05 11:07:55 +08:00

881 lines
20 KiB
Go

package main
import (
"bytes"
"compress/zlib"
"encoding/json"
"errors"
"log"
"strconv"
"sync"
"time"
)
var (
cacheObjCastle *sync.Map
cacheObjGuild *sync.Map
cacheObjUser *sync.Map
cacheObjMsg *sync.Map
cacheObjType map[string]int64
cacheObjSubType map[string]int64
cacheObjJob map[int64]Job
muxObjJob sync.Mutex
)
func initCache(initDB bool) {
var err error
log.Println("Caching obj_type ..")
err = loadObjType()
logOnError(err, "initCache : caching obj_type")
log.Println("Caching obj_sub_type ..")
err = loadObjSubType()
logOnError(err, "initCache : caching obj_sub_type")
log.Println("Caching guilds ..")
err = loadObjGuild()
logOnError(err, "initCache : caching guilds")
log.Println("Caching users ..")
err = loadObjUser()
logOnError(err, "initCache : caching user")
log.Println("Caching items ..")
err = loadObjItem()
logOnError(err, "initCache : caching items")
log.Println("Filling message parsing rules...")
resetMsgParsingRules()
msgParsingRules, err = loadMsgParsingRules()
logOnError(err, "initCache : message parsing rules")
log.Println("Caching messages ..")
err = loadObjMsg()
logOnError(err, "initCache : caching msgs")
log.Println("Caching jobs ..")
err = loadObjJob()
logOnError(err, "initCache : caching jobs")
if initDB {
_, _ = addObjCastle(`🦌`, `Deerhorn`)
_, _ = addObjCastle(`🐉`, `Dragonscale`)
_, _ = addObjCastle(`🦅`, `Highnest`)
_, _ = addObjCastle(`🌑`, `Moonlight`)
_, _ = addObjCastle(`🥔`, `Potato`)
_, _ = addObjCastle(`🦈`, `Sharkteeth`)
_, _ = addObjCastle(`🐺`, `Wolfpack`)
_, _ = addObjCastle(``, `Unknown`)
_, _ = addObjGuild(``, `No Guild`)
}
log.Println("Caching castles ..")
err = loadObjCastle()
logOnError(err, "initCache : caching castles")
log.Println("Caching clients ..")
err = loadClients()
log.Println("Recreating SQL views ..")
initDBViews()
if initDB {
}
}
func loadObjType() error {
var obj []ObjType
b, err := Asset("data/code_obj_type.json")
logOnError(err, "loadObjType : load data/code_obj_type.json")
if err != nil {
return err
}
err = json.Unmarshal(b, &obj)
logOnError(err, "loadObjType : Unmarshal")
if err != nil {
return err
}
cacheObjType = make(map[string]int64)
for _, v := range obj {
id, err := codeObjTypeId(v.IntlId)
if err == nil {
cacheObjType[v.IntlId] = id
} else {
err = insertObjType(v.IntlId, v.Name)
logOnError(err, "loadObjType : insertObjType")
if err == nil {
id, err = codeObjTypeId(v.IntlId)
if err == nil {
cacheObjType[v.IntlId] = id
} else {
logOnError(err, "loadObjType : codeObjTypeId")
}
}
}
}
return nil
}
func loadObjSubType() error {
var obj []ObjSubType
b, err := Asset("data/code_obj_sub_type.json")
logOnError(err, "loadObjSubType : load data/code_obj_sub_type.json")
if err != nil {
return err
}
err = json.Unmarshal(b, &obj)
logOnError(err, "loadObjSubType : Unmarshal")
if err != nil {
return err
}
cacheObjSubType = make(map[string]int64)
for _, v := range obj {
id, err := codeObjSubTypeId(v.IntlId)
if err == nil {
cacheObjSubType[v.IntlId] = id
} else {
err = insertObjSubType(v.IntlId, v.Name, v.ObjType)
logOnError(err, "loadObjSubType : insertObjSubType")
if err == nil {
id, err = codeObjSubTypeId(v.IntlId)
if err == nil {
cacheObjSubType[v.IntlId] = id
} else {
logOnError(err, "loadObjSubType : codeObjSubTypeId")
}
}
}
}
return nil
}
func codeObjTypeId(intlId string) (int64, error) {
var objTypeId int64
stmt, err := db.Prepare(`SELECT c.id FROM code_obj_type c WHERE c.intl_id = ?`)
if err != nil {
return 0, err
}
defer stmt.Close()
err = stmt.QueryRow(intlId).Scan(&objTypeId)
if err != nil {
return 0, err
}
return objTypeId, nil
}
func codeObjSubTypeId(intlId string) (int64, error) {
var objSubTypeId int64
stmt, err := db.Prepare(`SELECT c.id FROM code_obj_sub_type c WHERE c.intl_id = ?`)
if err != nil {
return 0, err
}
defer stmt.Close()
err = stmt.QueryRow(intlId).Scan(&objSubTypeId)
if err != nil {
return 0, err
}
return objSubTypeId, nil
}
func getObjTypeId(objId int64) (int64, error) {
var objTypeId int64
stmt, err := db.Prepare(`SELECT o.obj_type_id FROM obj o WHERE o.id = ?`)
if err != nil {
return 0, err
}
defer stmt.Close()
err = stmt.QueryRow(objId).Scan(&objTypeId)
if err != nil {
return 0, err
}
return objTypeId, nil
}
func getObjSubTypeId(objId int64) (int64, error) {
var objSubTypeId int64
stmt, err := db.Prepare(`SELECT o.obj_sub_type_id FROM obj o WHERE o.id = ?`)
if err != nil {
return 0, err
}
defer stmt.Close()
err = stmt.QueryRow(objId).Scan(&objSubTypeId)
if err != nil {
return 0, err
}
return objSubTypeId, nil
}
func setObjSubTypeId(objId int64, objSubTypeID64 int64) error {
stmt, err := db.Prepare(`UPDATE obj o SET o.obj_sub_type_id = ? WHERE o.id = ?;`)
logOnError(err, "setObjSubTypeId : prepare update")
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(objSubTypeID64, objId)
logOnError(err, "setObjSubTypeId : exec update ("+strconv.FormatInt(objId, 10)+", "+strconv.FormatInt(objSubTypeID64, 10)+")")
return err
}
func objAddName(objID64 int64, name string) error {
stmt, err := db.Prepare(`INSERT INTO obj_name (obj_id, name, priority)
SELECT ? obj_id, ? name, (SELECT MAX(priority) + 1 FROM obj_name WHERE obj_id = ?) priority FROM DUAL;`)
_, err = stmt.Exec(objID64, name, objID64)
return err
}
func addObjMsg(m ChatWarsMessage) (int64, error) {
tx, err := db.Begin()
logOnError(err, "addObjMsg : start transaction")
if err != nil {
return 0, err
}
res, err := tx.Exec(`INSERT INTO obj (obj_type_id, obj_sub_type_id)
VALUES (` + strconv.FormatInt(cacheObjType[`msg`], 10) + `,` + strconv.FormatInt(cacheObjSubType[`msg`], 10) + `);`)
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjMsg : rollback insert obj")
return 0, err
}
objId, err := res.LastInsertId()
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjMsg : rollback get lastInsertId")
return 0, err
}
stmt, err := tx.Prepare(`INSERT INTO obj_msg (obj_id, msg_id, chat_id, user_id, sender_user_id, date, text)
VALUES (?, ?, ?, ?, ?, ?, ?);`)
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjMsg : rollback prepare insert obj_msg")
return 0, err
}
defer stmt.Close()
_, err = stmt.Exec(objId, m.ID64, m.ChatID64, m.TGUserID64, m.TGSenderUserID64, m.Date, m.Text)
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjMsg : rollback exec insert obj_msg")
return 0, err
}
if len(m.Callbacks) > 0 {
stmt2, err := tx.Prepare(`INSERT INTO obj_msg_callback (obj_id, name, data)
VALUES (?, ?, ?);`)
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjMsg : rollback prepare insert obj_msg_callback")
return 0, err
}
defer stmt2.Close()
for _, c := range m.Callbacks {
_, err = stmt2.Exec(objId, c.Name, c.Data)
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjMsg : rollback exec insert obj_msg_callback")
return 0, err
}
}
}
err = tx.Commit()
if err != nil {
return 0, err
}
msg := new(ChatWarsMessage)
msg.ObjID64 = objId
msg.TGUserID64 = m.TGUserID64
msg.TGSenderUserID64 = m.TGSenderUserID64
msg.Date = m.Date
msg.ID64 = m.ID64
msg.ChatID64 = m.ChatID64
msg.Text = m.Text
msg.Callbacks = []ChatWarsCallback{}
for _, c := range m.Callbacks {
cb := ChatWarsCallback{
Name: c.Name,
Data: c.Data,
}
msg.Callbacks = append(msg.Callbacks, cb)
}
cacheObjMsg.Store(objId, *msg)
return objId, nil
}
func getObjMsg(objId int64) (*ChatWarsMessage, error) {
var (
name string
data []byte
)
if v, ok := cacheObjMsg.Load(objId); ok {
m := v.(ChatWarsMessage)
return &m, nil
}
var m *ChatWarsMessage
stmt, err := db.Prepare(`SELECT om.msg_id, om.chat_id, om.user_id, om.sender_user_id, om.date, om.text FROM obj_msg om WHERE om.obj_id = ?`)
if err != nil {
return m, err
}
defer stmt.Close()
m = new(ChatWarsMessage)
m.Callbacks = []ChatWarsCallback{}
err = stmt.QueryRow(objId).Scan(&m.ID64, &m.ChatID64, &m.TGUserID64, &m.TGSenderUserID64, &m.Date, &m.Text)
if err != nil {
return m, err
}
m.ObjID64 = objId
stmt2, err := db.Prepare(`SELECT omc.name, omc.data FROM obj_msg_callback omc WHERE omc.obj_id = ?`)
if err != nil {
return m, err
}
defer stmt2.Close()
rows, err := stmt2.Query(objId)
logOnError(err, "getObjMsg : Query callbacks")
for rows.Next() {
err = rows.Scan(&name, &data)
logOnError(err, "getObjMsg : scan next val")
cb := ChatWarsCallback{
Name: name,
Data: data,
}
m.Callbacks = append(m.Callbacks, cb)
}
cacheObjMsg.Store(objId, *m)
return m, nil
}
func loadObjMsg() error {
cacheObjMsg = new(sync.Map)
return nil
}
func delObj(objId int64) error {
objSubTypeId, err := getObjSubTypeId(objId)
if err != nil {
return err
}
if objSubTypeId != cacheObjSubType[`msg_auction_announce`] {
return errors.New("Can only delete cacheObjSubType[`msg_auction_announce`].")
}
cacheObjMsg.Delete(objId) // better delete from cache before, worst case we reload after
stmt, err := db.Prepare(`DELETE FROM obj WHERE id = ?`)
if err != nil {
return err
}
defer stmt.Close()
res, err := stmt.Exec(objId)
if err != nil {
return err
}
count, err := res.RowsAffected()
if err != nil {
return err
}
if count > 1 {
return errors.New("More than one row impacted.")
} else if count == 0 {
return errors.New("No row impacted.")
}
return nil
}
func addObjCastle(logo string, name string) (int64, error) {
tx, err := db.Begin()
logOnError(err, "addObjCastle : start transaction")
if err != nil {
return 0, err
}
res, err := tx.Exec(`INSERT INTO obj (obj_type_id, obj_sub_type_id)
VALUES (` + strconv.FormatInt(cacheObjType[`castle`], 10) + `,` + strconv.FormatInt(cacheObjSubType[`castle`], 10) + `);`)
logOnError(err, "addObjCastle : exec insert obj")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjCastle : rollback insert obj")
return 0, err
}
objId, err := res.LastInsertId()
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjCastle : rollback get lastInsertId")
return 0, err
}
stmt, err := tx.Prepare(`INSERT INTO obj_castle (obj_id, logo, name)
VALUES (?, ?, ?);`)
logOnError(err, "addObjCastle : prepare insert obj_castle")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjCastle : rollback prepare insert obj_castle")
return 0, err
}
defer stmt.Close()
_, err = stmt.Exec(objId, logo, name)
logOnError(err, "addObjCastle : exec insert obj_castle")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjCastle : rollback exec insert obj_castle")
return 0, err
}
err = tx.Commit()
logOnError(err, "addObjCastle : commit")
if err != nil {
return 0, err
}
return objId, nil
}
func getObjCastleID(s string) int64 {
if v, ok := cacheObjCastle.Load(s); ok {
c := v.(ChatWarsCastle)
return c.ObjID64
} else {
v, _ := cacheObjCastle.Load(``)
c := v.(ChatWarsCastle)
return c.ObjID64
}
}
func loadObjCastle() error {
var (
id int64
logo string
name string
)
cacheObjCastle = new(sync.Map)
castles, err := db.Query(`SELECT oc.obj_id, oc.logo, oc.name FROM obj_castle oc;`)
if err != nil {
return err
}
defer castles.Close()
for castles.Next() {
err = castles.Scan(&id, &logo, &name)
if err != nil {
return err
}
c := new(ChatWarsCastle)
c.ObjID64 = id
c.Logo = logo
c.Name = name
cacheObjCastle.Store(logo, *c)
cacheObjCastle.Store(name, *c)
}
return nil
}
func addObjGuild(tag string, name string) (int64, error) {
tx, err := db.Begin()
logOnError(err, "addObjGuild : start transaction")
if err != nil {
return 0, err
}
res, err := tx.Exec(`INSERT INTO obj (obj_type_id, obj_sub_type_id)
VALUES (` + strconv.FormatInt(cacheObjType[`guild`], 10) + `,` + strconv.FormatInt(cacheObjSubType[`guild`], 10) + `);`)
logOnError(err, "addObjGuild : exec insert obj")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjGuild : rollback insert obj")
return 0, err
}
objId, err := res.LastInsertId()
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjGuild : rollback get lastInsertId")
return 0, err
}
stmt, err := tx.Prepare(`INSERT INTO obj_guild (obj_id, tag, name, chat_id, deposit_chat_id)
VALUES (?, ?, ?, NULL, NULL);`)
logOnError(err, "addObjGuild : prepare insert obj_guild")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjGuild : rollback prepare insert obj_guild")
return 0, err
}
defer stmt.Close()
_, err = stmt.Exec(objId, tag, name)
logOnError(err, "addObjGuild : exec insert obj_guild")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjGuild : rollback exec insert obj_guild")
return 0, err
}
err = tx.Commit()
logOnError(err, "addObjGuild : commit")
if err != nil {
return 0, err
}
return objId, nil
}
func getObjGuildID(s string) int64 {
if v, ok := cacheObjGuild.Load(s); ok {
g := v.(ChatWarsGuild)
return g.ObjID64
} else {
objID64, err := addObjGuild(s, ``)
logOnError(err, "getObjGuildID")
g := new(ChatWarsGuild)
g.ObjID64 = objID64
g.Tag = s
g.Name = ``
cacheObjGuild.Store(s, *g)
return objID64
}
}
func loadObjGuild() error {
var (
id int64
tag string
name string
)
cacheObjGuild = new(sync.Map)
guilds, err := db.Query(`SELECT og.obj_id, og.tag, og.name FROM obj_guild og;`)
if err != nil {
return err
}
defer guilds.Close()
for guilds.Next() {
err = guilds.Scan(&id, &tag, &name)
if err != nil {
return err
}
g := new(ChatWarsGuild)
g.ObjID64 = id
g.Tag = tag
g.Name = name
cacheObjGuild.Store(tag, *g)
}
return nil
}
func addObjUser(name string) (int64, error) {
tx, err := db.Begin()
logOnError(err, "addObjUser : start transaction")
if err != nil {
return 0, err
}
res, err := tx.Exec(`INSERT INTO obj (obj_type_id, obj_sub_type_id)
VALUES (` + strconv.FormatInt(cacheObjType[`user`], 10) + `,` + strconv.FormatInt(cacheObjSubType[`user`], 10) + `);`)
logOnError(err, "addObjUser : exec insert obj")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjUser : rollback insert obj")
return 0, err
}
objId, err := res.LastInsertId()
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjUser : rollback get lastInsertId")
return 0, err
}
stmt, err := tx.Prepare(`INSERT INTO obj_user (obj_id, name)
VALUES (?, ?);`)
logOnError(err, "addObjUser : prepare insert obj_user")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjUser : rollback prepare insert obj_user")
return 0, err
}
defer stmt.Close()
_, err = stmt.Exec(objId, name)
logOnError(err, "addObjUser : exec insert obj_user")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjUser : rollback exec insert obj_user")
return 0, err
}
err = tx.Commit()
logOnError(err, "addObjUser : commit")
if err != nil {
return 0, err
}
return objId, nil
}
func getObjUserID(s string) int64 {
if v, ok := cacheObjUser.Load(s); ok {
u := v.(ChatWarsUser)
return u.ObjID64
} else {
objID64, err := addObjUser(s)
logOnError(err, "getObjUserID")
u := new(ChatWarsUser)
u.ObjID64 = objID64
u.Name = s
cacheObjUser.Store(s, *u)
return objID64
}
}
func loadObjUser() error {
var (
id int64
name string
)
cacheObjUser = new(sync.Map)
users, err := db.Query(`SELECT ou.obj_id, ou.name FROM obj_user ou;`)
if err != nil {
return err
}
defer users.Close()
for users.Next() {
err = users.Scan(&id, &name)
if err != nil {
return err
}
u := new(ChatWarsUser)
u.ObjID64 = id
u.Name = name
cacheObjUser.Store(name, *u)
}
return nil
}
func getObjMsgDate(objID64 int64) (time.Time, error) {
m, err := getObjMsg(objID64)
if err != nil {
return time.Now(), err
} else {
return m.Date, nil
}
}
func addObjXP(userID64 int64, expNow int64, expLvl int64, level int64, date time.Time) (int64, error) {
tx, err := db.Begin()
logOnError(err, "addObjXP : start transaction")
if err != nil {
return 0, err
}
res, err := tx.Exec(`INSERT INTO obj (obj_type_id, obj_sub_type_id)
VALUES (` + strconv.FormatInt(cacheObjType[`xp`], 10) + `,` + strconv.FormatInt(cacheObjSubType[`xp`], 10) + `);`)
logOnError(err, "addObjXP : exec insert obj")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjXP : rollback insert obj")
return 0, err
}
objId, err := res.LastInsertId()
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjXP : rollback get lastInsertId")
return 0, err
}
stmt, err := tx.Prepare(`INSERT INTO obj_xp (obj_id, user_id, val, target, level, date)
VALUES (?, ?, ?, ?, ?, ?);`)
logOnError(err, "addObjXP : prepare insert obj_xp")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjXP : rollback prepare insert obj_xp")
return 0, err
}
defer stmt.Close()
_, err = stmt.Exec(objId, userID64, expNow, expLvl, level, date)
logOnError(err, "addObjXP : exec insert obj_xp")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjXP : rollback exec insert obj_xp")
return 0, err
}
err = tx.Commit()
logOnError(err, "addObjXP : commit")
if err != nil {
return 0, err
}
return objId, nil
}
func addObjQuest(userID64 int64, questTypeID64 int64, duration time.Duration, date time.Time) (int64, error) {
tx, err := db.Begin()
logOnError(err, "addObjQuest : start transaction")
if err != nil {
return 0, err
}
res, err := tx.Exec(`INSERT INTO obj (obj_type_id, obj_sub_type_id)
VALUES (` + strconv.FormatInt(cacheObjType[`quest`], 10) + `,` + strconv.FormatInt(questTypeID64, 10) + `);`)
logOnError(err, "addObjQuest : exec insert obj")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjQuest : rollback insert obj")
return 0, err
}
objId, err := res.LastInsertId()
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjQuest : rollback get lastInsertId")
return 0, err
}
stmt, err := tx.Prepare(`INSERT INTO obj_quest (obj_id, user_id, duration, date, exp, gold)
VALUES (?, ?, ?, ?, 0, 0);`)
logOnError(err, "addObjQuest : prepare insert obj_quest")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjQuest : rollback prepare insert obj_quest")
return 0, err
}
defer stmt.Close()
_, err = stmt.Exec(objId, userID64, duration, date)
logOnError(err, "addObjQuest : exec insert obj_quest")
if err != nil {
err2 := tx.Rollback()
logOnError(err2, "addObjQuest : rollback exec insert obj_quest")
return 0, err
}
err = tx.Commit()
logOnError(err, "addObjQuest : commit")
if err != nil {
return 0, err
}
return objId, nil
}
func loadObjJob() error {
var (
id int64
type_id int64
trigger int64
timeout time.Time
user int64
zpayload []byte
zipped int
)
muxObjJob.Lock()
cacheObjJob = make(map[int64]Job)
muxObjJob.Unlock()
jobs, err := db.Query(`SELECT o.id, o.obj_sub_type_id, oj.trigger_id, oj.timeout, oj.user_id, oj.zipped, oj.payload FROM obj o, obj_job oj WHERE o.id = oj.obj_id;`)
if err != nil {
return err
}
defer jobs.Close()
for jobs.Next() {
err = jobs.Scan(&id, &type_id, &trigger, &timeout, &user, &zipped, &zpayload)
if err != nil {
return err
}
j := new(Job)
j.ID64 = id
j.JobTypeID64 = type_id
j.Trigger = trigger
j.Timeout = timeout
j.UserID64 = user
if zipped > 0 {
zb := bytes.NewReader(zpayload)
zr, err := zlib.NewReader(zb)
if err != nil {
logOnError(err, "loadObjJob : zlib.NewReader")
continue
}
b := new(bytes.Buffer)
b.ReadFrom(zr)
payload := b.Bytes()
j.Payload = payload
} else {
j.Payload = zpayload
}
muxObjJob.Lock()
cacheObjJob[id] = *j
muxObjJob.Unlock()
}
return nil
}