summaryrefslogtreecommitdiff
path: root/src/pool-import
diff options
context:
space:
mode:
authorChristian Pointner <equinox@helsinki.at>2016-08-03 14:02:52 (GMT)
committerChristian Pointner <equinox@helsinki.at>2016-08-03 14:02:52 (GMT)
commit41d0e0fc33aea9c2c584ad0a2e0da1ab802a4a68 (patch)
tree286642704ea94d59375f021695853e4d91fe73ce /src/pool-import
inital commit
Diffstat (limited to 'src/pool-import')
-rw-r--r--src/pool-import/dbconfig.go64
-rw-r--r--src/pool-import/main.go77
-rw-r--r--src/pool-import/nulltime.go100
-rw-r--r--src/pool-import/rddb.go228
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
+}