2021-05-04 19:51:42 +10:00
|
|
|
package sqliteStorage
|
|
|
|
|
|
|
|
import (
|
2021-05-05 11:47:39 +10:00
|
|
|
"errors"
|
2021-05-04 19:51:42 +10:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"crawshaw.io/sqlite"
|
|
|
|
"crawshaw.io/sqlite/sqlitex"
|
|
|
|
"github.com/anacrolix/torrent/metainfo"
|
|
|
|
"github.com/anacrolix/torrent/storage"
|
|
|
|
)
|
|
|
|
|
|
|
|
type NewDirectStorageOpts struct {
|
2021-05-05 21:36:36 +10:00
|
|
|
NewConnOpts
|
|
|
|
InitDbOpts
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// A convenience function that creates a connection pool, resource provider, and a pieces storage
|
|
|
|
// ClientImpl and returns them all with a Close attached.
|
|
|
|
func NewDirectStorage(opts NewDirectStorageOpts) (_ storage.ClientImplCloser, err error) {
|
2021-05-05 21:36:36 +10:00
|
|
|
conn, err := newConn(opts.NewConnOpts)
|
2021-05-04 19:51:42 +10:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-05-05 21:36:36 +10:00
|
|
|
journalMode := "delete"
|
|
|
|
if opts.Memory {
|
|
|
|
journalMode = "off"
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
2021-05-05 21:36:36 +10:00
|
|
|
err = initConn(conn, InitConnOpts{
|
|
|
|
SetJournalMode: journalMode,
|
|
|
|
MmapSizeOk: true,
|
|
|
|
MmapSize: 1 << 25,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = initDatabase(conn, opts.InitDbOpts)
|
2021-05-04 19:51:42 +10:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return &client{
|
2021-05-05 21:36:36 +10:00
|
|
|
conn: conn,
|
2021-05-05 10:02:15 +10:00
|
|
|
blobs: make(map[string]*sqlite.Blob),
|
2021-05-04 19:51:42 +10:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type client struct {
|
2021-05-05 10:02:15 +10:00
|
|
|
l sync.Mutex
|
|
|
|
conn conn
|
|
|
|
blobs map[string]*sqlite.Blob
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (storage.TorrentImpl, error) {
|
|
|
|
return torrent{c}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) Close() error {
|
2021-05-05 10:02:15 +10:00
|
|
|
for _, b := range c.blobs {
|
|
|
|
b.Close()
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
2021-05-05 21:36:36 +10:00
|
|
|
return c.conn.Close()
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
type torrent struct {
|
|
|
|
c *client
|
|
|
|
}
|
|
|
|
|
|
|
|
func rowidForBlob(c conn, name string, length int64) (rowid int64, err error) {
|
|
|
|
err = sqlitex.Exec(c, "select rowid from blob where name=?", func(stmt *sqlite.Stmt) error {
|
|
|
|
rowid = stmt.ColumnInt64(0)
|
|
|
|
return nil
|
|
|
|
}, name)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if rowid != 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = sqlitex.Exec(c, "insert into blob(name, data) values(?, zeroblob(?))", nil, name, length)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rowid = c.LastInsertRowID()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t torrent) Piece(p metainfo.Piece) storage.PieceImpl {
|
|
|
|
t.c.l.Lock()
|
|
|
|
defer t.c.l.Unlock()
|
|
|
|
name := p.Hash().HexString()
|
2021-05-05 10:02:15 +10:00
|
|
|
return piece{t.c.conn, &t.c.l, name, t.c.blobs, p.Length()}
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t torrent) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type piece struct {
|
|
|
|
conn conn
|
|
|
|
l *sync.Mutex
|
2021-05-05 10:02:15 +10:00
|
|
|
name string
|
|
|
|
blobs map[string]*sqlite.Blob
|
2021-05-04 19:51:42 +10:00
|
|
|
length int64
|
|
|
|
}
|
|
|
|
|
2021-05-05 11:47:39 +10:00
|
|
|
func (p2 piece) doAtIoWithBlob(
|
|
|
|
atIo func(*sqlite.Blob) func([]byte, int64) (int, error),
|
|
|
|
p []byte,
|
|
|
|
off int64,
|
|
|
|
) (n int, err error) {
|
2021-05-04 19:51:42 +10:00
|
|
|
p2.l.Lock()
|
|
|
|
defer p2.l.Unlock()
|
2021-05-05 21:36:36 +10:00
|
|
|
//defer p2.blobWouldExpire()
|
2021-05-05 11:47:39 +10:00
|
|
|
n, err = atIo(p2.getBlob())(p, off)
|
|
|
|
var se sqlite.Error
|
|
|
|
if !errors.As(err, &se) || se.Code != sqlite.SQLITE_ABORT {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p2.blobWouldExpire()
|
|
|
|
return atIo(p2.getBlob())(p, off)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p2 piece) ReadAt(p []byte, off int64) (n int, err error) {
|
|
|
|
return p2.doAtIoWithBlob(func(blob *sqlite.Blob) func([]byte, int64) (int, error) {
|
|
|
|
return blob.ReadAt
|
|
|
|
}, p, off)
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p2 piece) WriteAt(p []byte, off int64) (n int, err error) {
|
2021-05-05 11:47:39 +10:00
|
|
|
return p2.doAtIoWithBlob(func(blob *sqlite.Blob) func([]byte, int64) (int, error) {
|
|
|
|
return blob.WriteAt
|
|
|
|
}, p, off)
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p2 piece) MarkComplete() error {
|
|
|
|
p2.l.Lock()
|
|
|
|
defer p2.l.Unlock()
|
|
|
|
err := sqlitex.Exec(p2.conn, "update blob set verified=true where name=?", nil, p2.name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
changes := p2.conn.Changes()
|
|
|
|
if changes != 1 {
|
|
|
|
panic(changes)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-05 10:02:15 +10:00
|
|
|
func (p2 piece) blobWouldExpire() {
|
|
|
|
blob, ok := p2.blobs[p2.name]
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
blob.Close()
|
|
|
|
delete(p2.blobs, p2.name)
|
|
|
|
}
|
|
|
|
|
2021-05-04 19:51:42 +10:00
|
|
|
func (p2 piece) MarkNotComplete() error {
|
2021-05-04 22:56:43 +10:00
|
|
|
return sqlitex.Exec(p2.conn, "update blob set verified=false where name=?", nil, p2.name)
|
2021-05-04 19:51:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p2 piece) Completion() (ret storage.Completion) {
|
|
|
|
p2.l.Lock()
|
|
|
|
defer p2.l.Unlock()
|
|
|
|
err := sqlitex.Exec(p2.conn, "select verified from blob where name=?", func(stmt *sqlite.Stmt) error {
|
|
|
|
ret.Complete = stmt.ColumnInt(0) != 0
|
|
|
|
return nil
|
|
|
|
}, p2.name)
|
|
|
|
ret.Ok = err == nil
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2021-05-05 10:02:15 +10:00
|
|
|
|
|
|
|
func (p2 piece) closeBlobIfExists() {
|
|
|
|
if b, ok := p2.blobs[p2.name]; ok {
|
|
|
|
b.Close()
|
|
|
|
delete(p2.blobs, p2.name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p2 piece) getBlob() *sqlite.Blob {
|
|
|
|
blob, ok := p2.blobs[p2.name]
|
|
|
|
if !ok {
|
|
|
|
rowid, err := rowidForBlob(p2.conn, p2.name, p2.length)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
blob, err = p2.conn.OpenBlob("main", "blob", "data", rowid, true)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
p2.blobs[p2.name] = blob
|
|
|
|
}
|
|
|
|
return blob
|
|
|
|
}
|