fix: case-insensitive ids via query language and caching
This commit is contained in:
1 parent
3b91a26b2c
commit
c8a16d3f3e
2 files changed
+69
-25
No files matched your search
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
@@ -53,7 +54,7 @@ type mountedItemMetadata struct {
|
||||
|
||||
const mountMetadataFileName = ".docspell-cli-metadata.json"
|
||||
|
||||
const docspellIDCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
const docspellIDCharacters = "abcdefghijklmnopqrstuvwxyz0123456789-_.@"
|
||||
|
||||
func runMountCommand(args []string, cfg config, password string) error {
|
||||
options, err := parseMountOptions(args, cfg)
|
||||
@@ -360,15 +361,16 @@ type lazyYearNode struct {
|
||||
}
|
||||
|
||||
type lazyMonthNode struct {
|
||||
query string
|
||||
client *DocspellClient
|
||||
limit int
|
||||
items *lazyItemDirNode
|
||||
}
|
||||
|
||||
type lazyItemDirNode struct {
|
||||
query string
|
||||
client *DocspellClient
|
||||
limit int
|
||||
query string
|
||||
client *DocspellClient
|
||||
limit int
|
||||
mu sync.Mutex
|
||||
cache map[string]fs.Node
|
||||
populated bool
|
||||
}
|
||||
|
||||
type lazyByIDNode struct {
|
||||
@@ -378,9 +380,12 @@ type lazyByIDNode struct {
|
||||
}
|
||||
|
||||
type lazyByIDShardNode struct {
|
||||
query string
|
||||
client *DocspellClient
|
||||
limit int
|
||||
query string
|
||||
client *DocspellClient
|
||||
limit int
|
||||
mu sync.Mutex
|
||||
cache map[string]fs.Node
|
||||
populated bool
|
||||
}
|
||||
|
||||
func combineItemQueries(base, constraint string) string {
|
||||
@@ -400,11 +405,11 @@ func (d *lazyYearNode) Lookup(ctx context.Context, name string) (fs.Node, error)
|
||||
if len(name) != 2 || name < "01" || name > "12" {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
return &lazyMonthNode{
|
||||
return &lazyMonthNode{items: &lazyItemDirNode{
|
||||
query: combineItemQueries(d.query, fmt.Sprintf("date:%04d-%s", d.year, name)),
|
||||
client: d.client,
|
||||
limit: d.limit,
|
||||
}, nil
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (d *lazyYearNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
@@ -421,11 +426,11 @@ func (d *lazyMonthNode) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
}
|
||||
|
||||
func (d *lazyMonthNode) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
return (&lazyItemDirNode{query: d.query, client: d.client, limit: d.limit}).Lookup(ctx, name)
|
||||
return d.items.Lookup(ctx, name)
|
||||
}
|
||||
|
||||
func (d *lazyMonthNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
return (&lazyItemDirNode{query: d.query, client: d.client, limit: d.limit}).ReadDirAll(ctx)
|
||||
return d.items.ReadDirAll(ctx)
|
||||
}
|
||||
|
||||
func (d *lazyItemDirNode) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
@@ -433,7 +438,12 @@ func (d *lazyItemDirNode) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *lazyItemDirNode) children(ctx context.Context) (map[string]fs.Node, error) {
|
||||
func (d *lazyItemDirNode) children(ctx context.Context, refresh bool) (map[string]fs.Node, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.populated && !refresh {
|
||||
return d.cache, nil
|
||||
}
|
||||
items, err := d.client.SearchItems(ctx, d.query, d.limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search items: %w", err)
|
||||
@@ -445,7 +455,9 @@ func (d *lazyItemDirNode) children(ctx context.Context) (map[string]fs.Node, err
|
||||
collectMountFiles(children, child)
|
||||
}
|
||||
}
|
||||
return children, nil
|
||||
d.cache = children
|
||||
d.populated = true
|
||||
return d.cache, nil
|
||||
}
|
||||
|
||||
func collectMountFiles(children map[string]fs.Node, node fs.Node) {
|
||||
@@ -471,7 +483,7 @@ func collectMountFiles(children map[string]fs.Node, node fs.Node) {
|
||||
}
|
||||
|
||||
func (d *lazyItemDirNode) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
children, err := d.children(ctx)
|
||||
children, err := d.children(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -483,7 +495,7 @@ func (d *lazyItemDirNode) Lookup(ctx context.Context, name string) (fs.Node, err
|
||||
}
|
||||
|
||||
func (d *lazyItemDirNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
children, err := d.children(ctx)
|
||||
children, err := d.children(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -496,7 +508,7 @@ func (d *lazyByIDNode) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
}
|
||||
|
||||
func (d *lazyByIDNode) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
if len(name) != 2 {
|
||||
if !isDocspellIDPrefix(name) {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
return &lazyByIDShardNode{
|
||||
@@ -516,12 +528,29 @@ func (d *lazyByIDNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func isDocspellIDPrefix(prefix string) bool {
|
||||
if len(prefix) != 2 {
|
||||
return false
|
||||
}
|
||||
for _, char := range prefix {
|
||||
if !strings.ContainsRune(docspellIDCharacters, char) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *lazyByIDShardNode) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||
a.Mode = os.ModeDir | 0555
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *lazyByIDShardNode) children(ctx context.Context) (map[string]fs.Node, error) {
|
||||
func (d *lazyByIDShardNode) children(ctx context.Context, refresh bool) (map[string]fs.Node, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.populated && !refresh {
|
||||
return d.cache, nil
|
||||
}
|
||||
items, err := d.client.SearchItems(ctx, d.query, d.limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search items: %w", err)
|
||||
@@ -531,16 +560,20 @@ func (d *lazyByIDShardNode) children(ctx context.Context) (map[string]fs.Node, e
|
||||
if !ok {
|
||||
return map[string]fs.Node{}, nil
|
||||
}
|
||||
children := map[string]fs.Node{}
|
||||
for _, child := range byID.children {
|
||||
if shard, ok := child.(*dirNode); ok {
|
||||
return shard.children, nil
|
||||
children = shard.children
|
||||
break
|
||||
}
|
||||
}
|
||||
return map[string]fs.Node{}, nil
|
||||
d.cache = children
|
||||
d.populated = true
|
||||
return d.cache, nil
|
||||
}
|
||||
|
||||
func (d *lazyByIDShardNode) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
children, err := d.children(ctx)
|
||||
children, err := d.children(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -552,7 +585,7 @@ func (d *lazyByIDShardNode) Lookup(ctx context.Context, name string) (fs.Node, e
|
||||
}
|
||||
|
||||
func (d *lazyByIDShardNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||
children, err := d.children(ctx)
|
||||
children, err := d.children(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -656,7 +689,7 @@ func sanitizePathParts(parts []string) []string {
|
||||
}
|
||||
|
||||
func idShard(id string) string {
|
||||
id = sanitizeName(id)
|
||||
id = strings.ToLower(sanitizeName(id))
|
||||
if len(id) >= 2 {
|
||||
return id[:2]
|
||||
}
|
||||
|
||||
@@ -101,6 +101,9 @@ func TestLazyByIDShardFetchesMatchingPrefix(t *testing.T) {
|
||||
if len(entries) != 1 || entries[0].Name != "it-item-1.pdf" {
|
||||
t.Fatalf("unexpected shard entries: %#v", entries)
|
||||
}
|
||||
if _, err := shard.(*lazyByIDShardNode).Lookup(context.Background(), "it-item-1.pdf"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(queries) != 2 || queries[0] != "id:it*" || queries[1] != "id:it*" {
|
||||
t.Fatalf("unexpected queries: %v", queries)
|
||||
}
|
||||
@@ -130,6 +133,14 @@ func TestLazyByIDListsEveryPrefixWithoutSearching(t *testing.T) {
|
||||
if !found {
|
||||
t.Fatalf("expected 7j directory in %#v", entries)
|
||||
}
|
||||
if _, err := byID.(*lazyByIDNode).Lookup(context.Background(), "7J"); err == nil {
|
||||
t.Fatal("expected uppercase ID prefix lookup to fail")
|
||||
}
|
||||
for _, prefix := range []string{"-_", "_o", ".@"} {
|
||||
if _, err := byID.(*lazyByIDNode).Lookup(context.Background(), prefix); err != nil {
|
||||
t.Fatalf("expected %q ID prefix lookup to succeed: %v", prefix, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMountTreeGroupsByFolderAndDate(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user