github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/ext/fileio/fsdir.go (about)

     1  package fileio
     2  
     3  import (
     4  	"io/fs"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/ncruces/go-sqlite3"
    11  )
    12  
    13  type fsdir struct{ fsys fs.FS }
    14  
    15  func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
    16  	var root, base bool
    17  	for i, cst := range idx.Constraint {
    18  		switch cst.Column {
    19  		case 4: // root
    20  			if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
    21  				return sqlite3.CONSTRAINT
    22  			}
    23  			idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
    24  				Omit:      true,
    25  				ArgvIndex: 1,
    26  			}
    27  			root = true
    28  		case 5: // base
    29  			if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
    30  				return sqlite3.CONSTRAINT
    31  			}
    32  			idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{
    33  				Omit:      true,
    34  				ArgvIndex: 2,
    35  			}
    36  			base = true
    37  		}
    38  	}
    39  	if !root {
    40  		return sqlite3.CONSTRAINT
    41  	}
    42  	if base {
    43  		idx.EstimatedCost = 10
    44  	} else {
    45  		idx.EstimatedCost = 100
    46  	}
    47  	return nil
    48  }
    49  
    50  func (d fsdir) Open() (sqlite3.VTabCursor, error) {
    51  	return &cursor{fsdir: d}, nil
    52  }
    53  
    54  type cursor struct {
    55  	fsdir
    56  	base   string
    57  	resume func(struct{}) (entry, bool)
    58  	cancel func()
    59  	curr   entry
    60  	eof    bool
    61  	rowID  int64
    62  }
    63  
    64  type entry struct {
    65  	fs.DirEntry
    66  	err  error
    67  	path string
    68  }
    69  
    70  func (c *cursor) Close() error {
    71  	if c.cancel != nil {
    72  		c.cancel()
    73  	}
    74  	return nil
    75  }
    76  
    77  func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
    78  	if err := c.Close(); err != nil {
    79  		return err
    80  	}
    81  
    82  	root := arg[0].Text()
    83  	if len(arg) > 1 {
    84  		base := arg[1].Text()
    85  		if c.fsys != nil {
    86  			root = path.Join(base, root)
    87  			base = path.Clean(base) + "/"
    88  		} else {
    89  			root = filepath.Join(base, root)
    90  			base = filepath.Clean(base) + string(filepath.Separator)
    91  		}
    92  		c.base = base
    93  	}
    94  
    95  	c.resume, c.cancel = coroNew(func(_ struct{}, yield func(entry) struct{}) entry {
    96  		walkDir := func(path string, d fs.DirEntry, err error) error {
    97  			yield(entry{d, err, path})
    98  			return nil
    99  		}
   100  		if c.fsys != nil {
   101  			fs.WalkDir(c.fsys, root, walkDir)
   102  		} else {
   103  			filepath.WalkDir(root, walkDir)
   104  		}
   105  		return entry{}
   106  	})
   107  	c.eof = false
   108  	c.rowID = 0
   109  	return c.Next()
   110  }
   111  
   112  func (c *cursor) Next() error {
   113  	curr, ok := c.resume(struct{}{})
   114  	c.curr = curr
   115  	c.eof = !ok
   116  	c.rowID++
   117  	return c.curr.err
   118  }
   119  
   120  func (c *cursor) EOF() bool {
   121  	return c.eof
   122  }
   123  
   124  func (c *cursor) RowID() (int64, error) {
   125  	return c.rowID, nil
   126  }
   127  
   128  func (c *cursor) Column(ctx *sqlite3.Context, n int) error {
   129  	switch n {
   130  	case 0: // name
   131  		name := strings.TrimPrefix(c.curr.path, c.base)
   132  		ctx.ResultText(name)
   133  
   134  	case 1: // mode
   135  		i, err := c.curr.Info()
   136  		if err != nil {
   137  			return err
   138  		}
   139  		ctx.ResultInt64(int64(i.Mode()))
   140  
   141  	case 2: // mtime
   142  		i, err := c.curr.Info()
   143  		if err != nil {
   144  			return err
   145  		}
   146  		ctx.ResultTime(i.ModTime(), sqlite3.TimeFormatUnixFrac)
   147  
   148  	case 3: // data
   149  		switch typ := c.curr.Type(); {
   150  		case typ.IsRegular():
   151  			var data []byte
   152  			var err error
   153  			if c.fsys != nil {
   154  				data, err = fs.ReadFile(c.fsys, c.curr.path)
   155  			} else {
   156  				data, err = os.ReadFile(c.curr.path)
   157  			}
   158  			if err != nil {
   159  				return err
   160  			}
   161  			ctx.ResultBlob(data)
   162  
   163  		case typ&fs.ModeSymlink != 0 && c.fsys == nil:
   164  			t, err := os.Readlink(c.curr.path)
   165  			if err != nil {
   166  				return err
   167  			}
   168  			ctx.ResultText(t)
   169  		}
   170  	}
   171  	return nil
   172  }