gosuki/db.go

322 lines
6.3 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"
2017-11-13 18:14:59 +00:00
"log"
2017-11-17 14:18:53 +00:00
"path/filepath"
2017-11-13 18:14:59 +00:00
_ "github.com/mattn/go-sqlite3"
sqlite3 "github.com/mattn/go-sqlite3"
)
2017-11-17 14:18:53 +00:00
// Global cache database
var (
CACHE_DB *DB // Main in memory db, is synced with disc
)
const (
2017-11-17 14:18:53 +00:00
DB_FILENAME = "gomarks.db"
DB_MEMCACHE_PATH = "file:memcache?mode=memory&cache=shared"
DB_BUFFER_PATH = "file:buffer?mode=memory&cache=shared"
2017-11-16 14:43:09 +00:00
DB_BACKUP_HOOK = "sqlite_with_backup"
2017-11-13 18:14:59 +00:00
)
2017-11-17 14:18:53 +00:00
// DB SCHEMAS
2017-11-13 18:14:59 +00:00
const (
// metadata: name or title of resource
CREATE_LOCAL_DB_SCHEMA = `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
)`
CREATE_MEM_DB_SCHEMA = `CREATE TABLE if not exists bookmarks (
id integer PRIMARY KEY,
URL text NOT NULL,
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
)
2017-11-16 14:43:09 +00:00
var _sql3conns []*sqlite3.SQLiteConn // Only used for backup hook
2017-11-17 14:18:53 +00:00
var BACKUPHOOK_REGISTERED bool
2017-11-16 14:43:09 +00:00
type DB struct {
2017-11-17 14:18:53 +00:00
name string
path string
handle *sql.DB
backupHookOn bool
}
func (db DB) New(name string, path string) *DB {
return &DB{name, path, nil, false}
}
func (db *DB) Error() string {
errMsg := fmt.Sprintf("[error][db] name <%s>", db.name)
return errMsg
}
2017-11-17 14:18:53 +00:00
// Initialize a sqlite database
func (db *DB) Init() {
2017-11-16 14:46:06 +00:00
// TODO: Use context when making call from request/api
// `CACHE_DB` is a memory replica of disk db
// `bufferDB` is current working job db
var err error
2017-11-17 14:18:53 +00:00
if db.handle != nil {
//dbError = DBError()
logError(db, "already initialized")
return
}
// Create the memory cache db
db.handle, err = sql.Open("sqlite3", db.path)
debugPrint("db <%s> opend at at <%s>", db.name, db.path)
2017-11-17 14:18:53 +00:00
logPanic(err)
// Populate db schema
2017-11-17 14:18:53 +00:00
tx, err := db.handle.Begin()
logPanic(err)
stmt, err := tx.Prepare(CREATE_MEM_DB_SCHEMA)
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)
// Check if backup hook has been registered
//for _, val := range sql.Drivers() {
//debugPrint("Checking driver %s", val)
//if val == DB_BACKUP_HOOK {
//db.backupHookOn == true
//break
//}
//}
if !BACKUPHOOK_REGISTERED {
debugPrint("backup_hook: registering driver %s", DB_BACKUP_HOOK)
// 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-17 14:18:53 +00:00
//debugPrint("[HOOK] registering new connection")
2017-11-16 14:43:09 +00:00
_sql3conns = append(_sql3conns, conn)
2017-11-17 14:18:53 +00:00
//debugPrint("%v", _sql3conns)
2017-11-16 14:43:09 +00:00
return nil
},
})
2017-11-17 14:18:53 +00:00
BACKUPHOOK_REGISTERED = true
2017-11-16 14:43:09 +00:00
}
2017-11-17 14:18:53 +00:00
debugPrint("<%s> initialized", db.path)
}
func (db *DB) Close() {
2017-11-16 14:43:09 +00:00
debugPrint("Closing <%s>", db.name)
db.handle.Close()
}
func (db *DB) Count() int {
var count int
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
2017-11-17 14:18:53 +00:00
rows, err := db.handle.Query("select url, modified from bookmarks")
2017-11-16 14:43:09 +00:00
for rows.Next() {
err = rows.Scan(&url)
if err != nil {
return err
}
2017-11-17 14:18:53 +00:00
debugPrint("%s", url)
2017-11-16 14:43:09 +00:00
}
return nil
}
func (db *DB) isEmpty() (bool, error) {
var count int
row := db.handle.QueryRow("select count(*) from bookmarks")
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) {
debugPrint("Syncing <%s>(%d) to <%s>(%d)", src.name,
src.Count(),
dst.name,
dst.Count())
2017-11-16 14:43:09 +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()
2017-11-16 14:43:09 +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()
}
2017-11-17 14:18:53 +00:00
func (src *DB) FlushToDisk() error {
2017-11-13 18:14:59 +00:00
2017-11-17 14:18:53 +00:00
if !BACKUPHOOK_REGISTERED {
errMsg := fmt.Sprintf("%s, %s", src.path, "db backup hook is not initialized")
return errors.New(errMsg)
}
2017-11-17 14:18:53 +00:00
//debugPrint("[flush] openeing <%s>", src.path)
srcDb, err := sql.Open(DB_BACKUP_HOOK, src.path)
defer func() {
srcDb.Close()
_sql3conns = _sql3conns[:len(_sql3conns)-1]
}()
if err != nil {
return err
}
srcDb.Ping()
2017-11-17 14:18:53 +00:00
//debugPrint("[flush] opening <%s>", DB_FILENAME)
bkDb, err := sql.Open(DB_BACKUP_HOOK, DB_FILENAME)
defer func() {
bkDb.Close()
_sql3conns = _sql3conns[:len(_sql3conns)-1]
}()
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
// TODO: Initialize local db
func initDB() {
2017-11-13 18:14:59 +00:00
debugPrint("[NotImplemented] initialize local db if not exists or load it")
2017-11-17 14:18:53 +00:00
//debugPrint("Registered Drivers %v", sql.Drivers())
2017-11-13 18:14:59 +00:00
// If does not exit create new one in memory
// Then flush to disk
// If exists locally, load to memory
// Initialize memory db with schema
2017-11-17 14:18:53 +00:00
CACHE_DB = DB{}.New("memcache", DB_MEMCACHE_PATH)
CACHE_DB.Init()
debugPrint("Registered Drivers %v", sql.Drivers())
2017-11-17 14:18:53 +00:00
// Check and initialize local db as last step
// after loading the different browser bookmarks to cache
dbdir := getDefaultDBPath()
err := checkWriteable(dbdir)
logPanic(err)
dbpath := filepath.Join(dbdir, DB_FILENAME)
// If local db exists load it to CACHE_DB
var exists bool
if exists, err = checkFileExists(dbpath); exists {
logPanic(err)
debugPrint("[NOT IMPLEMENTED] preload existing local db")
} else {
logPanic(err)
// Else initialize it
initLocalDB(CACHE_DB, dbpath)
}
}
func initLocalDB(db *DB, dbpath string) {
debugPrint("Initializing local db at '%s'", dbpath)
debugPrint("%s flushing to disk", db.name)
err := db.FlushToDisk()
logPanic(err)
2017-11-17 14:18:53 +00:00
// DEBUG
//debugPrint("flushing again")
//time.Sleep(2 * time.Second)
//_ = db.FlushToDisk()
2017-11-13 18:14:59 +00:00
}
func testInMemoryDb(db *DB) {
2017-11-13 18:14:59 +00:00
debugPrint("test in memory")
_db, err := sql.Open("sqlite3", db.path)
defer _db.Close()
rows, err := _db.Query("select URL from bookmarks")
2017-11-13 18:14:59 +00:00
defer rows.Close()
logPanic(err)
var URL string
for rows.Next() {
rows.Scan(&URL)
log.Println(URL)
}
}