2013-02-18 20:44:00 -05:00
package main
import (
2013-02-20 23:43:01 -05:00
"asink"
2013-02-18 20:44:00 -05:00
"code.google.com/p/goconf/conf"
"database/sql"
"errors"
_ "github.com/mattn/go-sqlite3"
"strconv"
2013-03-10 23:13:30 -04:00
"sync"
2013-02-18 20:44:00 -05:00
)
2013-03-10 23:13:30 -04:00
type AsinkDB struct {
2013-03-18 07:17:44 -04:00
db * sql . DB
2013-03-10 23:13:30 -04:00
lock sync . Mutex
}
func GetAndInitDB ( config * conf . ConfigFile ) ( * AsinkDB , error ) {
2013-02-18 20:44:00 -05:00
dbLocation , err := config . GetString ( "local" , "dblocation" )
if err != nil {
return nil , errors . New ( "Error: database location not specified in config file." )
}
2013-08-13 21:44:15 -04:00
db , err := sql . Open ( "sqlite3" , "file:" + dbLocation + "?cache=shared&mode=rwc" )
2013-02-18 20:44:00 -05:00
if err != nil {
return nil , err
}
//make sure the events table is created
tx , err := db . Begin ( )
if err != nil {
return nil , err
}
rows , err := tx . Query ( "SELECT name FROM sqlite_master WHERE type='table' AND name='events';" )
if err != nil {
return nil , err
}
if ! rows . Next ( ) {
//if this is false, it means no rows were returned
2013-08-23 00:09:03 -04:00
tx . Exec ( "CREATE TABLE events (id INTEGER, localid INTEGER PRIMARY KEY ASC, type INTEGER, localstatus INTEGER, path TEXT, hash TEXT, predecessor TEXT, timestamp INTEGER, permissions INTEGER);" )
2013-02-18 20:44:00 -05:00
// tx.Exec("CREATE INDEX IF NOT EXISTS localididx on events (localid)")
tx . Exec ( "CREATE INDEX IF NOT EXISTS ididx on events (id);" )
tx . Exec ( "CREATE INDEX IF NOT EXISTS pathidx on events (path);" )
}
err = tx . Commit ( )
if err != nil {
return nil , err
}
2013-03-10 23:13:30 -04:00
ret := new ( AsinkDB )
ret . db = db
return ret , nil
2013-02-18 20:44:00 -05:00
}
2013-03-10 23:13:30 -04:00
func ( adb * AsinkDB ) DatabaseAddEvent ( e * asink . Event ) ( err error ) {
adb . lock . Lock ( )
tx , err := adb . db . Begin ( )
2013-02-18 20:44:00 -05:00
if err != nil {
return err
}
2013-03-10 23:13:30 -04:00
//make sure the transaction gets rolled back on error, and the database gets unlocked
defer func ( ) {
if err != nil {
tx . Rollback ( )
}
adb . lock . Unlock ( )
} ( )
2013-08-23 00:09:03 -04:00
result , err := tx . Exec ( "INSERT INTO events (id, type, localstatus, path, hash, predecessor, timestamp, permissions) VALUES (?,?,?,?,?,?,?,?);" , e . Id , e . Type , e . LocalStatus , e . Path , e . Hash , e . Predecessor , e . Timestamp , e . Permissions )
2013-02-18 20:44:00 -05:00
if err != nil {
return err
}
id , err := result . LastInsertId ( )
if err != nil {
return err
}
err = tx . Commit ( )
if err != nil {
return err
}
e . LocalId = id
e . InDB = true
return nil
}
2013-03-10 23:13:30 -04:00
func ( adb * AsinkDB ) DatabaseUpdateEvent ( e * asink . Event ) ( err error ) {
2013-02-18 20:44:00 -05:00
if ! e . InDB {
return errors . New ( "Attempting to update an event in the database which hasn't been previously added." )
}
2013-03-10 23:13:30 -04:00
adb . lock . Lock ( )
tx , err := adb . db . Begin ( )
2013-02-18 20:44:00 -05:00
if err != nil {
return err
}
2013-03-10 23:13:30 -04:00
//make sure the transaction gets rolled back on error, and the database gets unlocked
defer func ( ) {
if err != nil {
tx . Rollback ( )
}
adb . lock . Unlock ( )
} ( )
2013-08-23 00:09:03 -04:00
result , err := tx . Exec ( "UPDATE events SET id=?, type=?, localstatus=?, path=?, hash=?, prececessor=?, timestamp=?, permissions=? WHERE localid=?;" , e . Id , e . Type , e . LocalStatus , e . Path , e . Hash , e . Predecessor , e . Timestamp , e . Permissions , e . LocalId )
2013-02-18 20:44:00 -05:00
if err != nil {
return err
}
rows , err := result . RowsAffected ( )
if err != nil {
return err
}
if rows != 1 {
return errors . New ( "Updated " + strconv . Itoa ( int ( rows ) ) + " row(s) when intending to update 1 event row." )
}
err = tx . Commit ( )
if err != nil {
return err
}
return nil
}
2013-03-17 23:02:51 -04:00
//returns nil if no such event exists
2013-08-13 21:44:15 -04:00
func ( adb * AsinkDB ) DatabaseLatestEventForPath ( path string ) ( event * asink . Event , err error ) {
2013-03-17 23:02:51 -04:00
adb . lock . Lock ( )
//make sure the database gets unlocked
defer adb . lock . Unlock ( )
2013-08-23 00:09:03 -04:00
row := adb . db . QueryRow ( "SELECT id, localid, type, localstatus, path, hash, predecessor, timestamp, permissions FROM events WHERE path == ? ORDER BY timestamp DESC LIMIT 1;" , path )
2013-03-17 23:02:51 -04:00
2013-08-13 21:44:15 -04:00
event = new ( asink . Event )
2013-08-23 00:09:03 -04:00
err = row . Scan ( & event . Id , & event . LocalId , & event . Type , & event . LocalStatus , & event . Path , & event . Hash , & event . Predecessor , & event . Timestamp , & event . Permissions )
2013-08-13 21:44:15 -04:00
switch {
case err == sql . ErrNoRows :
return nil , nil
case err != nil :
return nil , err
default :
2013-03-17 23:02:51 -04:00
return event , nil
}
}
2013-08-13 21:51:19 -04:00
//returns nil if no such event exists
func ( adb * AsinkDB ) DatabaseLatestRemoteEvent ( ) ( event * asink . Event , err error ) {
adb . lock . Lock ( )
//make sure the database gets unlocked
defer adb . lock . Unlock ( )
2013-08-23 00:09:03 -04:00
row := adb . db . QueryRow ( "SELECT id, localid, type, localstatus, path, hash, predecessor, timestamp, permissions FROM events WHERE id > 0 ORDER BY id DESC LIMIT 1;" )
2013-08-13 21:51:19 -04:00
event = new ( asink . Event )
2013-08-23 00:09:03 -04:00
err = row . Scan ( & event . Id , & event . LocalId , & event . Type , & event . LocalStatus , & event . Path , & event . Hash , & event . Predecessor , & event . Timestamp , & event . Permissions )
2013-08-13 21:51:19 -04:00
switch {
case err == sql . ErrNoRows :
return nil , nil
case err != nil :
return nil , err
default :
return event , nil
}
}