gosuki/db.go

366 lines
7.5 KiB
Go
Raw Normal View History

2017-11-13 18:14:59 +00:00
package main
import (
"database/sql"
2017-11-17 14:18:53 +00:00
"errors"
"fmt"
"path/filepath"
2017-11-13 18:14:59 +00:00
sqlite3 "github.com/mattn/go-sqlite3"
)
2017-11-17 14:18:53 +00:00
// Global cache database
var (
2018-05-27 15:45:06 +00:00
CacheDB *DB // Main in memory db, is synced with disc
2017-11-19 19:46:24 +00:00
_sql3conns []*sqlite3.SQLiteConn // Only used for backup hook
backupHookRegistered bool // set to true once the backup hook is registered
2017-11-17 14:18:53 +00:00
)
const (
DB_FILENAME = "gomarks.db"
DBMemcacheFmt = "file:%s?mode=memory&cache=shared"
DBBufferFmt = "file:%s?mode=memory&cache=shared"
DB_BACKUP_HOOK = "sqlite_with_backup"
2017-11-13 18:14:59 +00:00
)
2017-11-17 19:19:00 +00:00
// Database schemas used for the creation of new databases
2017-11-13 18:14:59 +00:00
const (
// metadata: name or title of resource
QCreateLocalDbSchema = `CREATE TABLE if not exists bookmarks (
2017-11-13 18:14:59 +00:00
id integer PRIMARY KEY,
URL text NOT NULL UNIQUE,
metadata text default '',
tags text default '',
desc text default '',
2017-11-17 14:18:53 +00:00
modified integer default ?,
flags integer default 0
)`
QCreateMemDbSchema = `CREATE TABLE if not exists bookmarks (
id integer PRIMARY KEY,
2017-11-19 16:00:37 +00:00
URL text NOT NULL UNIQUE,
metadata text default '',
tags text default '',
desc text default '',
2017-11-17 14:18:53 +00:00
modified integer default (strftime('%s')),
flags integer default 0
)`
2017-11-13 18:14:59 +00:00
)
2018-05-27 15:56:50 +00:00
// DB encapsulates an sql.DB struct. All interactions with memory/buffer and
// disk databases are done through the DB object
type DB struct {
2018-05-27 15:55:27 +00:00
Name string
Path string
Handle *sql.DB
2017-11-17 14:18:53 +00:00
}
func (db DB) New(name string, path string) *DB {
2018-05-27 15:55:27 +00:00
return &DB{name, path, nil}
2017-11-17 14:18:53 +00:00
}
func (db *DB) Error() string {
2018-05-27 15:55:27 +00:00
errMsg := fmt.Sprintf("[error][db] name <%s>", db.Name)
2017-11-17 14:18:53 +00:00
return errMsg
}
2018-06-14 14:42:54 +00:00
// Initialize sqlite database for read only operations
func (db *DB) InitRO() {
var err error
if db.Handle != nil {
logErrorMsg(db, "already initialized")
return
}
// Create the sqlite connection
db.Handle, err = sql.Open("sqlite3", db.Path)
log.Debugf("<%s> opened at <%s>", db.Name, db.Path)
logPanic(err)
}
// Initialize a sqlite database with Gomark Schema
2017-11-17 14:18:53 +00:00
func (db *DB) Init() {
2017-11-16 14:46:06 +00:00
// TODO: Use context when making call from request/api
2017-11-19 16:00:37 +00:00
// `cacheDB` is a memory replica of disk db
2017-11-16 14:46:06 +00:00
var err error
2018-05-27 15:55:27 +00:00
if db.Handle != nil {
2017-11-19 16:00:37 +00:00
logErrorMsg(db, "already initialized")
2017-11-17 14:18:53 +00:00
return
}
// Create the memory cache db
2018-05-27 15:55:27 +00:00
db.Handle, err = sql.Open("sqlite3", db.Path)
2017-11-20 18:31:17 +00:00
//log.Debugf("db <%s> opend at at <%s>", db.name, db.path)
2018-05-27 15:55:27 +00:00
log.Debugf("<%s> opened at <%s>", db.Name, db.Path)
2017-11-17 14:18:53 +00:00
logPanic(err)
// Populate db schema
2018-05-27 15:55:27 +00:00
tx, err := db.Handle.Begin()
2017-11-17 14:18:53 +00:00
logPanic(err)
stmt, err := tx.Prepare(QCreateMemDbSchema)
2017-11-17 14:18:53 +00:00
logPanic(err)
2017-11-16 14:43:09 +00:00
2017-11-17 14:18:53 +00:00
_, err = stmt.Exec()
logPanic(err)
err = tx.Commit()
logPanic(err)
2017-11-19 19:46:24 +00:00
if !backupHookRegistered {
2017-11-20 18:31:17 +00:00
//log.Debugf("backup_hook: registering driver %s", DB_BACKUP_HOOK)
2017-11-17 14:18:53 +00:00
// Register the hook
2017-11-16 14:43:09 +00:00
sql.Register(DB_BACKUP_HOOK,
&sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
2017-11-20 18:31:17 +00:00
//log.Debugf("[HOOK] registering new connection")
2017-11-16 14:43:09 +00:00
_sql3conns = append(_sql3conns, conn)
2017-11-20 18:31:17 +00:00
//log.Debugf("%v", _sql3conns)
2017-11-16 14:43:09 +00:00
return nil
},
})
2017-11-19 19:46:24 +00:00
backupHookRegistered = true
2017-11-16 14:43:09 +00:00
}
2018-05-27 15:55:27 +00:00
log.Debugf("<%s> initialized", db.Name)
}
func (db *DB) Attach(attached *DB) {
2018-05-27 15:55:27 +00:00
stmtStr := fmt.Sprintf("ATTACH DATABASE '%s' AS '%s'", attached.Path, attached.Name)
_, err := db.Handle.Exec(stmtStr)
logPanic(err)
/////////////////
// For debug only
/////////////////
//var idx int
//var dt string
//var name string
//rows, err := db.handle.Query("PRAGMA database_list;")
//logPanic(err)
//for rows.Next() {
//err = rows.Scan(&idx, &dt, &name)
//logPanic(err)
//log.Debugf("pragmalist: %s", dt)
//}
}
func (db *DB) Close() {
2018-10-25 16:19:15 +00:00
log.Debugf("Closing DB <%s>", db.Name)
2018-05-27 15:55:27 +00:00
db.Handle.Close()
}
func (db *DB) Count() int {
var count int
2018-05-27 15:55:27 +00:00
row := db.Handle.QueryRow("select count(*) from bookmarks")
err := row.Scan(&count)
logPanic(err)
return count
}
2017-11-16 14:43:09 +00:00
func (db *DB) Print() error {
var url string
2018-05-27 15:55:27 +00:00
rows, err := db.Handle.Query("select url from bookmarks")
2017-11-16 14:43:09 +00:00
for rows.Next() {
err = rows.Scan(&url)
if err != nil {
return err
}
2017-11-20 18:31:17 +00:00
log.Debugf("%s", url)
2017-11-16 14:43:09 +00:00
}
return nil
}
func (db *DB) isEmpty() (bool, error) {
var count int
2018-05-27 15:55:27 +00:00
row := db.Handle.QueryRow("select count(*) from bookmarks")
2017-11-16 14:43:09 +00:00
err := row.Scan(&count)
if err != nil {
return false, err
}
if count > 0 {
return false, nil
}
return true, nil
}
2017-11-17 14:18:53 +00:00
func (src *DB) SyncTo(dst *DB) {
2018-05-27 15:55:27 +00:00
log.Debugf("Syncing <%s>(%d) to <%s>(%d)", src.Name,
src.Count(),
2018-05-27 15:55:27 +00:00
dst.Name,
dst.Count())
2018-05-27 15:55:27 +00:00
srcDb, err := sql.Open(DB_BACKUP_HOOK, src.Path)
2017-11-17 14:18:53 +00:00
defer func() {
srcDb.Close()
_sql3conns = _sql3conns[:len(_sql3conns)-1]
}()
logPanic(err)
srcDb.Ping()
2018-05-27 15:55:27 +00:00
dstDb, err := sql.Open(DB_BACKUP_HOOK, dst.Path)
2017-11-17 14:18:53 +00:00
defer func() {
dstDb.Close()
_sql3conns = _sql3conns[:len(_sql3conns)-1]
}()
logPanic(err)
dstDb.Ping()
2017-11-16 14:43:09 +00:00
bk, err := _sql3conns[1].Backup("main", _sql3conns[0], "main")
2017-11-17 14:18:53 +00:00
logPanic(err)
_, err = bk.Step(-1)
2017-11-17 14:18:53 +00:00
logPanic(err)
bk.Finish()
}
func (src *DB) SyncToDisk(dbpath string) error {
2018-05-27 15:55:27 +00:00
log.Debugf("Syncing <%s> to <%s>", src.Name, dbpath)
2017-11-13 18:14:59 +00:00
2017-11-19 19:46:24 +00:00
if !backupHookRegistered {
2018-05-27 15:55:27 +00:00
errMsg := fmt.Sprintf("%s, %s", src.Path, "db backup hook is not initialized")
2017-11-17 14:18:53 +00:00
return errors.New(errMsg)
}
2017-11-20 18:31:17 +00:00
//log.Debugf("[flush] openeing <%s>", src.path)
2018-05-27 15:55:27 +00:00
srcDb, err := sql.Open(DB_BACKUP_HOOK, src.Path)
defer flushSqliteCon(srcDb)
if err != nil {
return err
}
srcDb.Ping()
2017-11-20 18:31:17 +00:00
//log.Debugf("[flush] opening <%s>", DB_FILENAME)
dbUri := fmt.Sprintf("file:%s", dbpath)
bkDb, err := sql.Open(DB_BACKUP_HOOK, dbUri)
defer flushSqliteCon(bkDb)
if err != nil {
return err
}
bkDb.Ping()
bk, err := _sql3conns[1].Backup("main", _sql3conns[0], "main")
if err != nil {
return err
}
_, err = bk.Step(-1)
if err != nil {
return err
}
bk.Finish()
return nil
}
func (dst *DB) SyncFromDisk(dbpath string) error {
2017-11-19 19:46:24 +00:00
if !backupHookRegistered {
2018-05-27 15:55:27 +00:00
errMsg := fmt.Sprintf("%s, %s", dst.Path, "db backup hook is not initialized")
return errors.New(errMsg)
}
2018-05-27 15:55:27 +00:00
log.Debugf("Syncing <%s> to <%s>", dbpath, dst.Name)
2017-11-19 16:00:37 +00:00
dbUri := fmt.Sprintf("file:%s", dbpath)
srcDb, err := sql.Open(DB_BACKUP_HOOK, dbUri)
defer flushSqliteCon(srcDb)
if err != nil {
return err
}
srcDb.Ping()
2017-11-20 18:31:17 +00:00
//log.Debugf("[flush] opening <%s>", DB_FILENAME)
2018-05-27 15:55:27 +00:00
bkDb, err := sql.Open(DB_BACKUP_HOOK, dst.Path)
defer flushSqliteCon(bkDb)
if err != nil {
return err
}
bkDb.Ping()
2017-11-17 14:18:53 +00:00
bk, err := _sql3conns[1].Backup("main", _sql3conns[0], "main")
if err != nil {
return err
}
2017-11-13 18:14:59 +00:00
_, err = bk.Step(-1)
if err != nil {
return err
}
bk.Finish()
return nil
2017-11-13 18:14:59 +00:00
}
// TODO: Use context when making call from request/api
2017-11-17 14:18:53 +00:00
func initDB() {
// Initialize memory db with schema
cacheName := "memcache"
cachePath := fmt.Sprintf(DBMemcacheFmt, cacheName)
2018-05-27 15:45:06 +00:00
CacheDB = DB{}.New(cacheName, cachePath)
CacheDB.Init()
2017-11-17 14:18:53 +00:00
// Check and initialize local db as last step
// browser bookmarks should already be in cache
2017-11-17 14:18:53 +00:00
dbdir := getDefaultDBPath()
dbpath := filepath.Join(dbdir, DB_FILENAME)
// Verifiy that local db directory path is writeable
2017-11-17 14:18:53 +00:00
err := checkWriteable(dbdir)
logPanic(err)
2017-11-19 16:00:37 +00:00
// If local db exists load it to cacheDB
var exists bool
if exists, err = checkFileExists(dbpath); exists {
logPanic(err)
2017-11-20 18:31:17 +00:00
log.Debugf("localdb exists, preloading to cache")
2018-05-27 15:45:06 +00:00
CacheDB.SyncFromDisk(dbpath)
2017-11-19 16:00:37 +00:00
//_ = cacheDB.Print()
} else {
logPanic(err)
// Else initialize it
2018-05-27 15:45:06 +00:00
initLocalDB(CacheDB, dbpath)
}
}
//Initialize the local database file
func initLocalDB(db *DB, dbpath string) {
2017-11-19 16:00:37 +00:00
log.Infof("Initializing local db at '%s'", dbpath)
2018-05-27 15:55:27 +00:00
log.Debugf("%s flushing to disk", db.Name)
err := db.SyncToDisk(dbpath)
logPanic(err)
2017-11-13 18:14:59 +00:00
}
func flushSqliteCon(con *sql.DB) {
con.Close()
_sql3conns = _sql3conns[:len(_sql3conns)-1]
2017-11-19 16:00:37 +00:00
log.Debugf("Flushed sqlite conns %v", _sql3conns)
}