mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 05:59:14 +00:00
199 lines
3.5 KiB
Go
199 lines
3.5 KiB
Go
package fileio
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/ncruces/go-sqlite3"
|
|
)
|
|
|
|
type fsdir struct{ fs.FS }
|
|
|
|
func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
|
|
var path, dir bool
|
|
for i, cst := range idx.Constraint {
|
|
switch cst.Column {
|
|
case 4: // path
|
|
if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
|
|
return sqlite3.CONSTRAINT
|
|
}
|
|
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
|
Omit: true,
|
|
ArgvIndex: 1,
|
|
}
|
|
path = true
|
|
case 5: // dir
|
|
if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
|
|
return sqlite3.CONSTRAINT
|
|
}
|
|
idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
|
|
Omit: true,
|
|
ArgvIndex: 2,
|
|
}
|
|
dir = true
|
|
}
|
|
}
|
|
if path {
|
|
idx.EstimatedCost = 100
|
|
}
|
|
if dir {
|
|
idx.EstimatedCost = 10
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d fsdir) Open() (sqlite3.VTabCursor, error) {
|
|
return &cursor{fs: d.FS}, nil
|
|
}
|
|
|
|
type cursor struct {
|
|
fs fs.FS
|
|
dir string
|
|
rowID int64
|
|
eof bool
|
|
curr entry
|
|
next chan entry
|
|
done chan struct{}
|
|
}
|
|
|
|
type entry struct {
|
|
path string
|
|
entry fs.DirEntry
|
|
err error
|
|
}
|
|
|
|
func (c *cursor) Close() error {
|
|
if c.done != nil {
|
|
close(c.done)
|
|
s := <-c.next
|
|
c.done = nil
|
|
c.next = nil
|
|
return s.err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
|
if err := c.Close(); err != nil {
|
|
return err
|
|
}
|
|
if len(arg) == 0 {
|
|
return fmt.Errorf("fsdir: wrong number of arguments")
|
|
}
|
|
|
|
path := arg[0].Text()
|
|
if len(arg) > 1 {
|
|
if dir := arg[1].RawText(); c.fs != nil {
|
|
c.dir = string(dir) + "/"
|
|
} else {
|
|
c.dir = string(dir) + string(filepath.Separator)
|
|
}
|
|
path = c.dir + path
|
|
}
|
|
|
|
c.rowID = 0
|
|
c.eof = false
|
|
c.next = make(chan entry)
|
|
c.done = make(chan struct{})
|
|
go c.WalkDir(path)
|
|
return c.Next()
|
|
}
|
|
|
|
func (c *cursor) Next() error {
|
|
curr, ok := <-c.next
|
|
c.curr = curr
|
|
c.eof = !ok
|
|
c.rowID++
|
|
return c.curr.err
|
|
}
|
|
|
|
func (c *cursor) EOF() bool {
|
|
return c.eof
|
|
}
|
|
|
|
func (c *cursor) RowID() (int64, error) {
|
|
return c.rowID, nil
|
|
}
|
|
|
|
func (c *cursor) Column(ctx *sqlite3.Context, n int) error {
|
|
switch n {
|
|
case 0: // name
|
|
name := strings.TrimPrefix(c.curr.path, c.dir)
|
|
ctx.ResultText(name)
|
|
|
|
case 1: // mode
|
|
i, err := c.curr.entry.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.ResultInt64(int64(i.Mode()))
|
|
|
|
case 2: // mtime
|
|
i, err := c.curr.entry.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.ResultTime(i.ModTime(), sqlite3.TimeFormatUnixFrac)
|
|
|
|
case 3: // data
|
|
switch typ := c.curr.entry.Type(); {
|
|
case typ.IsRegular():
|
|
var data []byte
|
|
var err error
|
|
if name := c.curr.entry.Name(); c.fs != nil {
|
|
data, err = fs.ReadFile(c.fs, name)
|
|
} else {
|
|
data, err = os.ReadFile(name)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.ResultBlob(data)
|
|
|
|
case typ&fs.ModeSymlink != 0 && c.fs == nil:
|
|
t, err := os.Readlink(c.curr.entry.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.ResultText(t)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *cursor) WalkDir(path string) {
|
|
var err error
|
|
|
|
defer func() {
|
|
if p := recover(); p != nil {
|
|
if perr, ok := p.(error); ok {
|
|
err = fmt.Errorf("panic: %w", perr)
|
|
} else {
|
|
err = fmt.Errorf("panic: %v", p)
|
|
}
|
|
}
|
|
if err != nil {
|
|
c.next <- entry{err: err}
|
|
}
|
|
close(c.next)
|
|
}()
|
|
|
|
if c.fs != nil {
|
|
err = fs.WalkDir(c.fs, path, c.WalkDirFunc)
|
|
} else {
|
|
err = filepath.WalkDir(path, c.WalkDirFunc)
|
|
}
|
|
}
|
|
|
|
func (c *cursor) WalkDirFunc(path string, de fs.DirEntry, err error) error {
|
|
select {
|
|
case <-c.done:
|
|
return fs.SkipAll
|
|
case c.next <- entry{path, de, err}:
|
|
return nil
|
|
}
|
|
}
|