github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/store/store.go (about)

     1  // Package store abstracts the persistent storage used by elvish.
     2  package store
     3  
     4  import (
     5  	"database/sql"
     6  	"fmt"
     7  	"net/url"
     8  	"sync"
     9  
    10  	"github.com/elves/elvish/util"
    11  
    12  	_ "github.com/mattn/go-sqlite3" // enable the "sqlite3" SQL driver
    13  )
    14  
    15  var logger = util.GetLogger("[store] ")
    16  var initDB = map[string](func(*sql.DB) error){}
    17  
    18  // Store is the permanent storage backend for elvish. It is not thread-safe. In
    19  // particular, the store may be closed while another goroutine is still
    20  // accessing the store. To prevent bad things from happening, every time the
    21  // main goroutine spawns a new goroutine to operate on the store, it should call
    22  // Waits.Add(1) in the main goroutine before spawning another goroutine, and
    23  // call Waits.Done() in the spawned goroutine after the operation is finished.
    24  type Store struct {
    25  	db *sql.DB
    26  	// Waits is used for registering outstanding operations on the store.
    27  	waits sync.WaitGroup
    28  }
    29  
    30  // DefaultDB returns the default database for storage.
    31  func DefaultDB(dbname string) (*sql.DB, error) {
    32  	uri := "file:" + url.QueryEscape(dbname) +
    33  		"?mode=rwc&cache=shared&vfs=unix-dotfile"
    34  	db, err := sql.Open("sqlite3", uri)
    35  	if err == nil {
    36  		db.SetMaxOpenConns(1)
    37  	}
    38  	return db, err
    39  }
    40  
    41  // NewStore creates a new Store with the default database.
    42  func NewStore(dbname string) (*Store, error) {
    43  	db, err := DefaultDB(dbname)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	return NewStoreDB(db)
    48  }
    49  
    50  // NewStoreDB creates a new Store with a custom database. The database must be
    51  // a SQLite database.
    52  func NewStoreDB(db *sql.DB) (*Store, error) {
    53  	logger.Println("initializing store")
    54  	defer logger.Println("initialized store")
    55  	st := &Store{db, sync.WaitGroup{}}
    56  
    57  	if SchemaUpToDate(db) {
    58  		logger.Println("DB schema up to date")
    59  	} else {
    60  		for name, fn := range initDB {
    61  			err := fn(db)
    62  			if err != nil {
    63  				return nil, fmt.Errorf("failed to %s: %v", name, err)
    64  			}
    65  		}
    66  	}
    67  
    68  	return st, nil
    69  }
    70  
    71  // Waits returns a WaitGroup used to register outstanding storage requests when
    72  // making calls asynchronously.
    73  func (s *Store) Waits() *sync.WaitGroup {
    74  	return &s.waits
    75  }
    76  
    77  // Close waits for all outstanding operations to finish, and closes the
    78  // database.
    79  func (s *Store) Close() error {
    80  	if s == nil || s.db == nil {
    81  		return nil
    82  	}
    83  	s.waits.Wait()
    84  	return s.db.Close()
    85  }