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 }