diff options
author | Christian Pointner <equinox@helsinki.at> | 2016-08-03 14:02:52 (GMT) |
---|---|---|
committer | Christian Pointner <equinox@helsinki.at> | 2016-08-03 14:02:52 (GMT) |
commit | 41d0e0fc33aea9c2c584ad0a2e0da1ab802a4a68 (patch) | |
tree | 286642704ea94d59375f021695853e4d91fe73ce /src |
inital commit
Diffstat (limited to 'src')
-rw-r--r-- | src/pool-import/dbconfig.go | 64 | ||||
-rw-r--r-- | src/pool-import/main.go | 77 | ||||
-rw-r--r-- | src/pool-import/nulltime.go | 100 | ||||
-rw-r--r-- | src/pool-import/rddb.go | 228 |
4 files changed, 469 insertions, 0 deletions
diff --git a/src/pool-import/dbconfig.go b/src/pool-import/dbconfig.go new file mode 100644 index 0000000..b4733fc --- /dev/null +++ b/src/pool-import/dbconfig.go @@ -0,0 +1,64 @@ +// +// pool-import +// +// Copyright (C) 2016 Christian Pointner <equinox@helsinki.at> +// +// 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 <http://www.gnu.org/licenses/>. +// + +package main + +import ( + "github.com/vaughan0/go-ini" +) + +type config struct { + configfile string + dbHost string + dbUser string + dbPasswd string + dbDb string +} + +func getIniValue(file ini.File, section string, key string, dflt string) string { + value, ok := file.Get(section, key) + if ok { + return value + } + return dflt +} + +func (self *config) readConfigFile() error { + file, err := ini.LoadFile(self.configfile) + if err != nil { + return err + } + + self.dbHost = getIniValue(file, "mySQL", "Hostname", "localhost") + self.dbUser = getIniValue(file, "mySQL", "Loginname", "rivendell") + self.dbPasswd = getIniValue(file, "mySQL", "Password", "letmein") + self.dbDb = getIniValue(file, "mySQL", "Database", "rivendell") + return nil +} + +func newConfig(configfile string) (conf *config, err error) { + conf = new(config) + conf.configfile = configfile + if err = conf.readConfigFile(); err != nil { + return + } + return +} diff --git a/src/pool-import/main.go b/src/pool-import/main.go new file mode 100644 index 0000000..dfa6ab5 --- /dev/null +++ b/src/pool-import/main.go @@ -0,0 +1,77 @@ +// +// pool-import +// +// Copyright (C) 2016 Christian Pointner <equinox@helsinki.at> +// +// 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 <http://www.gnu.org/licenses/>. +// + +package main + +import ( + "log" + "os" + + "code.helsinki.at/rhrd-go/rddb" + "code.helsinki.at/rhrd-go/rhimport" +) + +const ( + NEW_RD_CONF = "/etc/rd.conf" + OLD_RD_CONF = "rd.conf" +) + +func main() { + if len(os.Args) < 3 { + log.Fatal("Usage: pool-import <old pool group> <new pool group>") + } + old_group := os.Args[1] + new_group := os.Args[2] + + impconf, err := rhimport.NewConfig(NEW_RD_CONF, "http://localhost/rd-bin/rdxport.cgi", "/tmp", "snd/") + if err != nil { + log.Fatal("Error parsing configuration file:", err) + } + + newdb, err := rddb.NewDB(NEW_RD_CONF) + if err != nil { + log.Fatal("Error initializing NEW Rivdenll DB:", err) + } + defer newdb.Cleanup() + + stdlog := log.New(os.Stderr, "[std] ", log.LstdFlags) + dbglog := log.New(os.Stderr, "[dbg] ", log.LstdFlags) + sessions, err := rhimport.NewSessionStore(impconf, newdb.GetInterface(), stdlog, dbglog) + if err != nil { + log.Fatal("Error initializing Session Store:", err) + } + defer sessions.Cleanup() + + olddb, err := NewDB(OLD_RD_CONF) + if err != nil { + log.Fatal("Error initializing OLD Rivdenll DB:", err) + } + defer olddb.Cleanup() + olddbi := olddb.GetInterface() + + pool := PoolListEntry{old_group, ""} + carts, err := olddbi.GetPoolCartList(pool) + if err != nil { + log.Fatal("Error fetching Pool cart list:", err) + } + + log.Printf("will import: %d carts from old:%s -> new:%s", len(carts), old_group, new_group) +} diff --git a/src/pool-import/nulltime.go b/src/pool-import/nulltime.go new file mode 100644 index 0000000..c87d886 --- /dev/null +++ b/src/pool-import/nulltime.go @@ -0,0 +1,100 @@ +// +// pool-import +// +// Copyright (C) 2016 Christian Pointner <equinox@helsinki.at> +// +// 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 <http://www.gnu.org/licenses/>. +// + +package main + +import ( + "database/sql/driver" + "fmt" + "time" +) + +// +// This is from github.com/go-sql-driver/mysql +// + +const ( + timeFormat = "2006-01-02 15:04:05.999999" +) + +// This NullTime implementation is not driver-specific +type NullTime struct { + Time time.Time + Valid bool // Valid is true if Time is not NULL +} + +// Scan implements the Scanner interface. +// The value type must be time.Time or string / []byte (formatted time-string), +// otherwise Scan fails. +func (nt *NullTime) Scan(value interface{}) (err error) { + if value == nil { + nt.Time, nt.Valid = time.Time{}, false + return + } + + switch v := value.(type) { + case time.Time: + nt.Time, nt.Valid = v, true + return + case []byte: + nt.Time, err = parseDateTime(string(v), time.UTC) + nt.Valid = (err == nil) + return + case string: + nt.Time, err = parseDateTime(v, time.UTC) + nt.Valid = (err == nil) + return + } + + nt.Valid = false + return fmt.Errorf("Can't convert %T to time.Time", value) +} + +// Value implements the driver Valuer interface. +func (nt NullTime) Value() (driver.Value, error) { + if !nt.Valid { + return nil, nil + } + return nt.Time, nil +} + +func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { + base := "0000-00-00 00:00:00.0000000" + switch len(str) { + case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM" + if str == base[:len(str)] { + return + } + t, err = time.Parse(timeFormat[:len(str)], str) + default: + err = fmt.Errorf("invalid time string: %s", str) + return + } + + // Adjust location + if err == nil && loc != time.UTC { + y, mo, d := t.Date() + h, mi, s := t.Clock() + t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil + } + + return +} diff --git a/src/pool-import/rddb.go b/src/pool-import/rddb.go new file mode 100644 index 0000000..7d2e4c2 --- /dev/null +++ b/src/pool-import/rddb.go @@ -0,0 +1,228 @@ +// +// pool-import +// +// Copyright (C) 2016 Christian Pointner <equinox@helsinki.at> +// +// 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 <http://www.gnu.org/licenses/>. +// + +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 +} |