// // pool-import // // Copyright (C) 2016 Christian Pointner // // This file is part of pool-import. // // pool-import is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // pool-import is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with pool-import. If not, see . // package main import ( "database/sql" "fmt" "regexp" "strconv" "strings" "time" "github.com/ziutek/mymysql/godrv" ) var ( mysqlTableNameRe = regexp.MustCompile(`^[_0-9a-zA-Z-]+$`) ) const ( DB_VERSION = 182 defaultCartTitle = "[new cart]" ) type CutListEntry struct { Number uint Evergreen bool Description string Duration time.Duration Imported NullTime NumPlayed uint LastPlayed NullTime } type CartListEntry struct { Number uint Exists bool Artist string Title string Album string Cuts []CutListEntry } type PoolListEntry struct { Group string Description string } type getPoolCartListResult struct { carts map[uint]CartListEntry err error } type getPoolCartListRequest struct { pool PoolListEntry response chan<- getPoolCartListResult } type db struct { conf *config dbh *sql.DB getPoolCartListChan chan getPoolCartListRequest getPoolCartsStmt *sql.Stmt quit chan bool done chan bool } func (d *db) init() (err error) { godrv.Register("SET CHARACTER SET utf8;") dsn := fmt.Sprintf("tcp:%s:3306*%s/%s/%s", d.conf.dbHost, d.conf.dbDb, d.conf.dbUser, d.conf.dbPasswd) if d.dbh, err = sql.Open("mymysql", dsn); err != nil { return } var dbver int err = d.dbh.QueryRow("select DB from VERSION;").Scan(&dbver) if err != nil { err = fmt.Errorf("fetching version: %s", err) return } if dbver != DB_VERSION { err = fmt.Errorf("version mismatch is %d, should be %d", dbver, DB_VERSION) return } if d.getPoolCartsStmt, err = d.dbh.Prepare("select CART.NUMBER,CART.ARTIST,CART.TITLE,CART.ALBUM,CUTS.CUT_NAME,CUTS.EVERGREEN,CUTS.DESCRIPTION,CUTS.LENGTH,CUTS.ORIGIN_DATETIME,CUTS.PLAY_COUNTER,CUTS.LAST_PLAY_DATETIME from CUTS,CART where CUTS.CART_NUMBER = CART.NUMBER and CART.GROUP_NAME = ?;"); err != nil { return } return } func (d *db) getPoolCartList(pool PoolListEntry) (result getPoolCartListResult) { var rows *sql.Rows if rows, result.err = d.getPoolCartsStmt.Query(pool.Group); result.err != nil { return } defer rows.Close() result.carts = make(map[uint]CartListEntry) for rows.Next() { var cut CutListEntry var cart, length uint var cutName, evergreen string var artist, title, album sql.NullString if result.err = rows.Scan(&cart, &artist, &title, &album, &cutName, &evergreen, &cut.Description, &length, &cut.Imported, &cut.NumPlayed, &cut.LastPlayed); result.err != nil { return } parts := strings.Split(cutName, "_") if len(parts) == 2 { if cn, converr := strconv.ParseUint(parts[1], 10, 32); converr != nil { continue } else { cut.Number = uint(cn) } } else { continue } switch evergreen { case "Y": cut.Evergreen = true default: cut.Evergreen = false } cut.Duration = time.Duration(length) * time.Millisecond if c, exists := result.carts[cart]; !exists { c = CartListEntry{cart, true, artist.String, title.String, album.String, nil} c.Cuts = append(c.Cuts, cut) result.carts[cart] = c } else { c.Cuts = append(c.Cuts, cut) } } result.err = rows.Err() return } func (d *db) dispatchRequests() { defer func() { d.done <- true }() for { select { case <-d.quit: return case req := <-d.getPoolCartListChan: req.response <- d.getPoolCartList(req.pool) } } } // ********************************************************* // Public Interface type DB struct { getPoolCartListChan chan<- getPoolCartListRequest } func (d *DB) GetPoolCartList(pool PoolListEntry) (map[uint]CartListEntry, error) { resCh := make(chan getPoolCartListResult) req := getPoolCartListRequest{} req.pool = pool req.response = resCh d.getPoolCartListChan <- req res := <-resCh if res.err != nil { return nil, res.err } return res.carts, nil } func (d *db) GetInterface() *DB { ch := &DB{} ch.getPoolCartListChan = d.getPoolCartListChan return ch } func (d *db) Cleanup() { d.quit <- true <-d.done close(d.quit) close(d.done) if d.dbh != nil { d.dbh.Close() } if d.getPoolCartsStmt != nil { d.getPoolCartsStmt.Close() } } func NewDB(configfile string) (d *db, err error) { d = new(db) if d.conf, err = newConfig(configfile); err != nil { return } d.quit = make(chan bool) d.done = make(chan bool) d.getPoolCartListChan = make(chan getPoolCartListRequest, 10) if err = d.init(); err != nil { return } go d.dispatchRequests() return }