From ae5d7dec2e3c28bee1cffb2392a95b1848bcefbe Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sat, 19 Mar 2016 14:02:46 -0400 Subject: [PATCH] Migrate to sqlx; modularize counters --- bot/bot.go | 5 +- main.go | 3 +- plugins/admin.go | 3 +- plugins/beers.go | 10 ++- plugins/counter/counter.go | 151 +++++++++++++++++++++++++------------ plugins/downtime.go | 9 ++- plugins/factoid.go | 15 ++-- plugins/first.go | 7 +- plugins/remember.go | 4 +- 9 files changed, 134 insertions(+), 73 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index 6e2a974..c1c8832 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/config" _ "github.com/mattn/go-sqlite3" @@ -35,7 +36,7 @@ type Bot struct { // TODO: I think it'd be nice to use https://github.com/jmoiron/sqlx so that // the select/update/etc statements could be simplified with struct // marshalling. - DB *sql.DB + DB *sqlx.DB DBVersion int64 logIn chan Message @@ -98,7 +99,7 @@ type Variable struct { // NewBot creates a Bot for a given connection and set of handlers. func NewBot(config *config.Config, connector Connector) *Bot { - sqlDB, err := sql.Open("sqlite3", config.DB.File) + sqlDB, err := sqlx.Open("sqlite3", config.DB.File) if err != nil { log.Fatal(err) } diff --git a/main.go b/main.go index 8f9e96d..a383f83 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/velour/catbase/config" "github.com/velour/catbase/irc" "github.com/velour/catbase/plugins" + "github.com/velour/catbase/plugins/counter" "github.com/velour/catbase/slack" ) @@ -39,7 +40,7 @@ func main() { b.AddHandler("talker", plugins.NewTalkerPlugin(b)) b.AddHandler("dice", plugins.NewDicePlugin(b)) b.AddHandler("beers", plugins.NewBeersPlugin(b)) - b.AddHandler("counter", plugins.NewCounterPlugin(b)) + b.AddHandler("counter", counter.NewCounterPlugin(b)) b.AddHandler("remember", plugins.NewRememberPlugin(b)) b.AddHandler("skeleton", plugins.NewSkeletonPlugin(b)) b.AddHandler("your", plugins.NewYourPlugin(b)) diff --git a/plugins/admin.go b/plugins/admin.go index 9472949..9ef3227 100644 --- a/plugins/admin.go +++ b/plugins/admin.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" ) @@ -17,7 +18,7 @@ import ( type AdminPlugin struct { Bot *bot.Bot - DB *sql.DB + DB *sqlx.DB } // NewAdminPlugin creates a new AdminPlugin with the Plugin interface diff --git a/plugins/beers.go b/plugins/beers.go index f16b76d..0550e7e 100644 --- a/plugins/beers.go +++ b/plugins/beers.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" ) @@ -22,7 +23,7 @@ import ( type BeersPlugin struct { Bot *bot.Bot - db *sql.DB + db *sqlx.DB } type userBeers struct { @@ -74,7 +75,7 @@ func NewBeersPlugin(bot *bot.Bot) *BeersPlugin { return &p } -func (u *userBeers) Save(db *sql.DB) error { +func (u *userBeers) Save(db *sqlx.DB) error { if !u.saved { res, err := db.Exec(`insert into beers ( nick, @@ -102,7 +103,7 @@ func (u *userBeers) Save(db *sql.DB) error { return nil } -func getUserBeers(db *sql.DB, nick string) *userBeers { +func getUserBeers(db *sqlx.DB, nick string) *userBeers { ub := userBeers{saved: true} err := db.QueryRow(`select id, nick, count, lastDrunk from beers where nick = ?`, nick).Scan( @@ -401,7 +402,8 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) { } func (p *BeersPlugin) checkUntappd(channel string) { - if p.Bot.Config.Untappd.Token == "" { + token := p.Bot.Config.Untappd.Token + if token == "" || token == "" { log.Println("No Untappd token, cannot enable plugin.") return } diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index c67168c..f1be7dd 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -8,6 +8,7 @@ import ( "log" "strings" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" ) @@ -15,16 +16,78 @@ import ( type CounterPlugin struct { Bot *bot.Bot - DB *sql.DB + DB *sqlx.DB } type Item struct { + *sqlx.DB + ID int64 Nick string Item string Count int } +func GetItems(db *sqlx.DB, nick string) ([]Item, error) { + var items []Item + err := db.Select(&items, `select * from counter where nick = ?`, nick) + if err != nil { + return nil, err + } + // Don't forget to embed the DB into all of that shiz + for _, i := range items { + i.DB = db + } + return items, nil +} + +func GetItem(db *sqlx.DB, nick, itemName string) (Item, error) { + var item Item + item.DB = db + log.Printf("GetItem db: %#v", db) + err := db.Get(&item, `select * from counter where nick = ? and item= ?`, + nick, itemName) + switch err { + case sql.ErrNoRows: + item.ID = -1 + item.Nick = nick + item.Item = itemName + case nil: + default: + return Item{}, err + } + log.Printf("Got item %s.%s: %#v", nick, itemName, item) + return item, nil +} + +func (i *Item) Create() error { + res, err := i.Exec(`insert into counter (nick, item, count) values (?, ?, ?);`, + i.Nick, i.Item, i.Count) + id, _ := res.LastInsertId() + // hackhackhack? + i.ID = id + return err +} + +func (i *Item) Update(delta int) error { + i.Count += delta + if i.Count == 0 && i.ID != -1 { + return i.Delete() + } + if i.ID == -1 { + i.Create() + } + log.Printf("Updating item: %#v, delta: %d", i, delta) + _, err := i.Exec(`update counter set count = ? where id = ?`, i.Count, i.ID) + return err +} + +func (i *Item) Delete() error { + _, err := i.Exec(`delete from counter where id = ?`, i.ID) + i.ID = -1 + return err +} + // NewCounterPlugin creates a new CounterPlugin with the Plugin interface func NewCounterPlugin(bot *bot.Bot) *CounterPlugin { if bot.DBVersion == 1 { @@ -63,32 +126,31 @@ func (p *CounterPlugin) Message(message bot.Message) bool { if parts[1] == "me" { subject = strings.ToLower(nick) } else { - subject = parts[1] + subject = strings.ToLower(parts[1]) } // pull all of the items associated with "subject" - rows, err := p.DB.Query(`select * from counter where nick = ?`, subject) + items, err := GetItems(p.DB, subject) if err != nil { - log.Fatal(err) + log.Fatalf("Error retrieving items for %s: %s", subject, err) + p.Bot.SendMessage(channel, "Something went wrong finding that counter;") + return true } resp := fmt.Sprintf("%s has the following counters:", subject) count := 0 - for rows.Next() { + for _, it := range items { count += 1 - var it Item - rows.Scan(&it.Nick, &it.Item, &it.Count) - if count > 1 { - resp = fmt.Sprintf("%s, ", resp) + resp += ", " } - resp = fmt.Sprintf("%s %s: %d", resp, it.Item, it.Count) + resp += fmt.Sprintf(" %s: %d", it.Item, it.Count) if count > 20 { - resp = fmt.Sprintf("%s, and a few others", resp) + resp += ", and a few others" break } } - resp = fmt.Sprintf("%s.", resp) + resp += "." if count == 0 { p.Bot.SendMessage(channel, fmt.Sprintf("%s has no counters.", subject)) @@ -101,7 +163,15 @@ func (p *CounterPlugin) Message(message bot.Message) bool { subject := strings.ToLower(nick) itemName := strings.ToLower(parts[1]) - if _, err := p.DB.Exec(`delete from counter where nick = ? and item = ?`, subject, itemName); err != nil { + it, err := GetItem(p.DB, subject, itemName) + if err != nil { + log.Printf("Error getting item to remove %s.%s: %s", subject, itemName, err) + p.Bot.SendMessage(channel, "Something went wrong removing that counter;") + return true + } + err = it.Delete() + if err != nil { + log.Printf("Error removing item %s.%s: %s", subject, itemName, err) p.Bot.SendMessage(channel, "Something went wrong removing that counter;") return true } @@ -126,17 +196,15 @@ func (p *CounterPlugin) Message(message bot.Message) bool { } var item Item - err := p.DB.QueryRow(`select nick, item, count from counter - where nick = ? and item = ?`, subject, itemName).Scan( - &item.Nick, &item.Item, &item.Count, - ) + item, err := GetItem(p.DB, subject, itemName) switch { case err == sql.ErrNoRows: p.Bot.SendMessage(channel, fmt.Sprintf("I don't think %s has any %s.", subject, itemName)) return true case err != nil: - log.Println(err) + log.Printf("Error retrieving item count for %s.%s: %s", + subject, itemName, err) return true } @@ -145,6 +213,7 @@ func (p *CounterPlugin) Message(message bot.Message) bool { return true } else if len(parts) == 1 { + // Need to have at least 3 characters to ++ or -- if len(parts[0]) < 3 { return false } @@ -159,13 +228,26 @@ func (p *CounterPlugin) Message(message bot.Message) bool { if strings.HasSuffix(parts[0], "++") { // ++ those fuckers - item := p.update(subject, itemName, 1) + item, err := GetItem(p.DB, subject, itemName) + if err != nil { + log.Printf("Error finding item %s.%s: %s.", subject, itemName, err) + // Item ain't there, I guess + return false + } + log.Printf("About to update item: %#v", item) + item.Update(1) p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } else if strings.HasSuffix(parts[0], "--") { // -- those fuckers - item := p.update(subject, itemName, -1) + item, err := GetItem(p.DB, subject, itemName) + if err != nil { + log.Printf("Error finding item %s.%s: %s.", subject, itemName, err) + // Item ain't there, I guess + return false + } + item.Update(-1) p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true @@ -175,35 +257,6 @@ func (p *CounterPlugin) Message(message bot.Message) bool { return false } -func (p *CounterPlugin) update(subject, itemName string, delta int) Item { - var item Item - err := p.DB.QueryRow(`select id, nick, item, count from counter - where nick = ? and item = ?;`, subject, itemName).Scan( - &item.ID, &item.Nick, &item.Item, &item.Count, - ) - switch { - case err == sql.ErrNoRows: - // insert it - res, err := p.DB.Exec(`insert into counter (nick, item, count) - values (?, ?, ?)`, subject, itemName, delta) - if err != nil { - log.Println(err) - } - id, err := res.LastInsertId() - return Item{id, subject, itemName, delta} - case err != nil: - log.Println(err) - return item - default: - item.Count += delta - _, err := p.DB.Exec(`update counter set count = ? where id = ?`, item.Count, item.ID) - if err != nil { - log.Println(err) - } - return item - } -} - // LoadData imports any configuration data into the plugin. This is not // strictly necessary other than the fact that the Plugin interface demands it // exist. This may be deprecated at a later date. diff --git a/plugins/downtime.go b/plugins/downtime.go index a240dab..3fe5b24 100644 --- a/plugins/downtime.go +++ b/plugins/downtime.go @@ -5,6 +5,7 @@ package plugins import ( "database/sql" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" ) @@ -20,7 +21,7 @@ import ( type DowntimePlugin struct { Bot *bot.Bot - db *sql.DB + db *sqlx.DB } type idleEntry struct { @@ -29,7 +30,7 @@ type idleEntry struct { lastSeen time.Time } -func (entry idleEntry) saveIdleEntry(db *sql.DB) error { +func (entry idleEntry) saveIdleEntry(db *sqlx.DB) error { var err error if entry.id.Valid { log.Println("Updating downtime for: ", entry) @@ -44,7 +45,7 @@ func (entry idleEntry) saveIdleEntry(db *sql.DB) error { return err } -func getIdleEntryByNick(db *sql.DB, nick string) (idleEntry, error) { +func getIdleEntryByNick(db *sqlx.DB, nick string) (idleEntry, error) { var id sql.NullInt64 var lastSeen sql.NullInt64 err := db.QueryRow(`select id, max(lastSeen) from downtime @@ -66,7 +67,7 @@ func getIdleEntryByNick(db *sql.DB, nick string) (idleEntry, error) { }, nil } -func getAllIdleEntries(db *sql.DB) (idleEntries, error) { +func getAllIdleEntries(db *sqlx.DB) (idleEntries, error) { rows, err := db.Query(`select id, nick, max(lastSeen) from downtime group by nick`) if err != nil { diff --git a/plugins/factoid.go b/plugins/factoid.go index 1d66f86..b45e3fa 100644 --- a/plugins/factoid.go +++ b/plugins/factoid.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" ) @@ -31,7 +32,7 @@ type factoid struct { count int } -func (f *factoid) save(db *sql.DB) error { +func (f *factoid) save(db *sqlx.DB) error { var err error if f.id.Valid { // update @@ -82,7 +83,7 @@ func (f *factoid) save(db *sql.DB) error { return err } -func (f *factoid) delete(db *sql.DB) error { +func (f *factoid) delete(db *sqlx.DB) error { var err error if f.id.Valid { _, err = db.Exec(`delete from factoid where id=?`, f.id) @@ -91,7 +92,7 @@ func (f *factoid) delete(db *sql.DB) error { return err } -func getFacts(db *sql.DB, fact string) ([]*factoid, error) { +func getFacts(db *sqlx.DB, fact string) ([]*factoid, error) { var fs []*factoid rows, err := db.Query(`select id, @@ -129,7 +130,7 @@ func getFacts(db *sql.DB, fact string) ([]*factoid, error) { return fs, err } -func getSingle(db *sql.DB) (*factoid, error) { +func getSingle(db *sqlx.DB) (*factoid, error) { var f factoid var tmpCreated int64 var tmpAccessed int64 @@ -158,7 +159,7 @@ func getSingle(db *sql.DB) (*factoid, error) { return &f, err } -func getSingleFact(db *sql.DB, fact string) (*factoid, error) { +func getSingleFact(db *sqlx.DB, fact string) (*factoid, error) { var f factoid var tmpCreated int64 var tmpAccessed int64 @@ -194,7 +195,7 @@ type FactoidPlugin struct { Bot *bot.Bot NotFound []string LastFact *factoid - db *sql.DB + db *sqlx.DB } // NewFactoidPlugin creates a new FactoidPlugin with the Plugin interface @@ -229,7 +230,7 @@ func NewFactoidPlugin(botInst *bot.Bot) *FactoidPlugin { for _, channel := range botInst.Config.Channels { go p.factTimer(channel) - go func(ch) { + go func(ch string) { // Some random time to start up time.Sleep(time.Duration(15) * time.Second) if ok, fact := p.findTrigger(p.Bot.Config.StartupFact); ok { diff --git a/plugins/first.go b/plugins/first.go index 88daed6..15ad9b0 100644 --- a/plugins/first.go +++ b/plugins/first.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" ) @@ -18,7 +19,7 @@ import ( type FirstPlugin struct { First *FirstEntry Bot *bot.Bot - db *sql.DB + db *sqlx.DB } type FirstEntry struct { @@ -31,7 +32,7 @@ type FirstEntry struct { } // Insert or update the first entry -func (fe *FirstEntry) save(db *sql.DB) error { +func (fe *FirstEntry) save(db *sqlx.DB) error { if _, err := db.Exec(`insert into first (day, time, body, nick) values (?, ?, ?, ?)`, fe.day.Unix(), @@ -73,7 +74,7 @@ func NewFirstPlugin(b *bot.Bot) *FirstPlugin { } } -func getLastFirst(db *sql.DB) (*FirstEntry, error) { +func getLastFirst(db *sqlx.DB) (*FirstEntry, error) { // Get last first entry var id sql.NullInt64 var day sql.NullInt64 diff --git a/plugins/remember.go b/plugins/remember.go index bbd2248..ed6e43f 100644 --- a/plugins/remember.go +++ b/plugins/remember.go @@ -3,13 +3,13 @@ package plugins import ( - "database/sql" "fmt" "log" "math/rand" "strings" "time" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" ) @@ -19,7 +19,7 @@ import ( type RememberPlugin struct { Bot *bot.Bot Log map[string][]bot.Message - db *sql.DB + db *sqlx.DB } // NewRememberPlugin creates a new RememberPlugin with the Plugin interface