2020-10-11 12:58:27 +11:00
|
|
|
package sqliteStorage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2020-10-23 09:03:44 +11:00
|
|
|
"context"
|
2020-10-11 12:58:27 +11:00
|
|
|
"errors"
|
2020-10-13 09:36:58 +11:00
|
|
|
"fmt"
|
2020-10-11 12:58:27 +11:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"crawshaw.io/sqlite"
|
|
|
|
"crawshaw.io/sqlite/sqlitex"
|
|
|
|
"github.com/anacrolix/missinggo/v2/resource"
|
|
|
|
)
|
|
|
|
|
|
|
|
type conn = *sqlite.Conn
|
|
|
|
|
|
|
|
func initConn(conn conn) error {
|
|
|
|
return sqlitex.ExecScript(conn, `
|
2020-10-23 09:01:15 +11:00
|
|
|
create table if not exists blob(
|
2020-10-13 09:36:58 +11:00
|
|
|
name text,
|
|
|
|
last_used timestamp default (datetime('now')),
|
|
|
|
data blob,
|
|
|
|
primary key (name)
|
|
|
|
);
|
2020-10-11 12:58:27 +11:00
|
|
|
`)
|
|
|
|
}
|
|
|
|
|
2020-10-23 09:03:44 +11:00
|
|
|
// Emulates a pool from a single Conn.
|
|
|
|
type poolFromConn struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
conn conn
|
|
|
|
}
|
|
|
|
|
|
|
|
func (me *poolFromConn) Get(ctx context.Context) conn {
|
|
|
|
me.mu.Lock()
|
|
|
|
return me.conn
|
|
|
|
}
|
|
|
|
|
|
|
|
func (me *poolFromConn) Put(conn conn) {
|
|
|
|
if conn != me.conn {
|
|
|
|
panic("expected to same conn")
|
|
|
|
}
|
|
|
|
me.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2020-10-11 12:58:27 +11:00
|
|
|
func NewProvider(conn *sqlite.Conn) (*provider, error) {
|
|
|
|
err := initConn(conn)
|
2020-10-23 09:03:44 +11:00
|
|
|
return &provider{&poolFromConn{conn: conn}}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewProviderPool(pool *sqlitex.Pool) (*provider, error) {
|
|
|
|
conn := pool.Get(context.TODO())
|
|
|
|
defer pool.Put(conn)
|
|
|
|
err := initConn(conn)
|
|
|
|
return &provider{pool: pool}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
type pool interface {
|
|
|
|
Get(context.Context) conn
|
|
|
|
Put(conn)
|
2020-10-11 12:58:27 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
type provider struct {
|
2020-10-23 09:03:44 +11:00
|
|
|
pool pool
|
2020-10-11 12:58:27 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *provider) NewInstance(s string) (resource.Instance, error) {
|
|
|
|
return instance{s, p}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type instance struct {
|
|
|
|
location string
|
|
|
|
p *provider
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) withConn(with func(conn conn)) {
|
2020-10-23 09:03:44 +11:00
|
|
|
conn := i.p.pool.Get(context.TODO())
|
|
|
|
defer i.p.pool.Put(conn)
|
|
|
|
with(conn)
|
2020-10-11 12:58:27 +11:00
|
|
|
}
|
|
|
|
|
2020-10-23 09:03:44 +11:00
|
|
|
func (i instance) getConn() *sqlite.Conn {
|
|
|
|
return i.p.pool.Get(context.TODO())
|
2020-10-11 12:58:27 +11:00
|
|
|
}
|
|
|
|
|
2020-10-23 09:03:44 +11:00
|
|
|
func (i instance) putConn(conn *sqlite.Conn) {
|
|
|
|
i.p.pool.Put(conn)
|
2020-10-11 12:58:27 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) Readdirnames() (names []string, err error) {
|
|
|
|
prefix := i.location + "/"
|
|
|
|
i.withConn(func(conn conn) {
|
2020-10-23 09:01:15 +11:00
|
|
|
err = sqlitex.Exec(conn, "select name from blob where name like ?", func(stmt *sqlite.Stmt) error {
|
2020-10-11 12:58:27 +11:00
|
|
|
names = append(names, stmt.ColumnText(0)[len(prefix):])
|
|
|
|
return nil
|
|
|
|
}, prefix+"%")
|
|
|
|
})
|
2020-10-13 09:36:58 +11:00
|
|
|
//log.Printf("readdir %q gave %q", i.location, names)
|
2020-10-11 12:58:27 +11:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) getBlobRowid(conn conn) (rowid int64, err error) {
|
|
|
|
rows := 0
|
2020-10-23 09:01:15 +11:00
|
|
|
err = sqlitex.Exec(conn, "select rowid from blob where name=?", func(stmt *sqlite.Stmt) error {
|
2020-10-11 12:58:27 +11:00
|
|
|
rowid = stmt.ColumnInt64(0)
|
|
|
|
rows++
|
|
|
|
return nil
|
|
|
|
}, i.location)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if rows == 1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if rows == 0 {
|
|
|
|
err = errors.New("blob not found")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic(rows)
|
|
|
|
}
|
|
|
|
|
|
|
|
type connBlob struct {
|
|
|
|
*sqlite.Blob
|
|
|
|
onClose func()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (me connBlob) Close() error {
|
|
|
|
err := me.Blob.Close()
|
|
|
|
me.onClose()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) Get() (ret io.ReadCloser, err error) {
|
2020-10-23 09:03:44 +11:00
|
|
|
conn := i.getConn()
|
|
|
|
blob, err := i.openBlob(conn, false, true)
|
2020-10-11 12:58:27 +11:00
|
|
|
if err != nil {
|
2020-10-23 09:03:44 +11:00
|
|
|
i.putConn(conn)
|
2020-10-11 12:58:27 +11:00
|
|
|
return
|
|
|
|
}
|
|
|
|
var once sync.Once
|
|
|
|
return connBlob{blob, func() {
|
2020-10-23 09:03:44 +11:00
|
|
|
once.Do(func() { i.putConn(conn) })
|
2020-10-11 12:58:27 +11:00
|
|
|
}}, nil
|
|
|
|
}
|
|
|
|
|
2020-10-13 09:36:58 +11:00
|
|
|
func (i instance) openBlob(conn conn, write, updateAccess bool) (*sqlite.Blob, error) {
|
2020-10-11 12:58:27 +11:00
|
|
|
rowid, err := i.getBlobRowid(conn)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-23 09:03:44 +11:00
|
|
|
// This seems to cause locking issues with in-memory databases. Is it something to do with not
|
|
|
|
// having WAL?
|
2020-10-13 09:36:58 +11:00
|
|
|
if updateAccess {
|
2020-10-23 09:01:15 +11:00
|
|
|
err = sqlitex.Exec(conn, "update blob set last_used=datetime('now') where rowid=?", nil, rowid)
|
2020-10-13 09:36:58 +11:00
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("updating last_used: %w", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if conn.Changes() != 1 {
|
|
|
|
panic(conn.Changes())
|
|
|
|
}
|
|
|
|
}
|
2020-10-23 09:01:15 +11:00
|
|
|
return conn.OpenBlob("main", "blob", "data", rowid, write)
|
2020-10-11 12:58:27 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) Put(reader io.Reader) (err error) {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
_, err = io.Copy(&buf, reader)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
i.withConn(func(conn conn) {
|
2020-10-23 09:01:15 +11:00
|
|
|
err = sqlitex.Exec(conn, "insert or replace into blob(name, data) values(?, ?)", nil, i.location, buf.Bytes())
|
2020-10-11 12:58:27 +11:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type fileInfo struct {
|
|
|
|
size int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f fileInfo) Name() string {
|
|
|
|
panic("implement me")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f fileInfo) Size() int64 {
|
|
|
|
return f.size
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f fileInfo) Mode() os.FileMode {
|
|
|
|
panic("implement me")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f fileInfo) ModTime() time.Time {
|
|
|
|
panic("implement me")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f fileInfo) IsDir() bool {
|
|
|
|
panic("implement me")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f fileInfo) Sys() interface{} {
|
|
|
|
panic("implement me")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) Stat() (ret os.FileInfo, err error) {
|
|
|
|
i.withConn(func(conn conn) {
|
|
|
|
var blob *sqlite.Blob
|
2020-10-13 09:36:58 +11:00
|
|
|
blob, err = i.openBlob(conn, false, false)
|
2020-10-11 12:58:27 +11:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer blob.Close()
|
|
|
|
ret = fileInfo{blob.Size()}
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) ReadAt(p []byte, off int64) (n int, err error) {
|
|
|
|
i.withConn(func(conn conn) {
|
2020-10-27 11:08:08 +11:00
|
|
|
if false {
|
|
|
|
var blob *sqlite.Blob
|
|
|
|
blob, err = i.openBlob(conn, false, true)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer blob.Close()
|
|
|
|
if off >= blob.Size() {
|
|
|
|
err = io.EOF
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if off+int64(len(p)) > blob.Size() {
|
|
|
|
p = p[:blob.Size()-off]
|
|
|
|
}
|
|
|
|
n, err = blob.ReadAt(p, off)
|
|
|
|
} else {
|
|
|
|
gotRow := false
|
|
|
|
err = sqlitex.Exec(
|
|
|
|
conn,
|
|
|
|
"select substr(data, ?, ?) from blob where name=?",
|
|
|
|
func(stmt *sqlite.Stmt) error {
|
|
|
|
if gotRow {
|
|
|
|
panic("found multiple matching blobs")
|
|
|
|
} else {
|
|
|
|
gotRow = true
|
|
|
|
}
|
|
|
|
n = stmt.ColumnBytes(0, p)
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
off+1, len(p), i.location,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !gotRow {
|
|
|
|
err = errors.New("blob not found")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if n < len(p) {
|
|
|
|
err = io.EOF
|
|
|
|
}
|
2020-10-11 12:58:27 +11:00
|
|
|
}
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) WriteAt(bytes []byte, i2 int64) (int, error) {
|
|
|
|
panic("implement me")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i instance) Delete() (err error) {
|
|
|
|
i.withConn(func(conn conn) {
|
2020-10-23 09:01:15 +11:00
|
|
|
err = sqlitex.Exec(conn, "delete from blob where name=?", nil, i.location)
|
2020-10-11 12:58:27 +11:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|