Fix FUSE attachment sizes and item filenames

This commit is contained in:
Jan Bader
2026-07-13 23:07:30 +02:00
parent d57724151a
commit 621fbe4913
3 changed files with 58 additions and 15 deletions
+29 -1
View File
@@ -137,6 +137,30 @@ func (c *DocspellClient) GetItem(ctx context.Context, id string) (*SearchItem, e
return &out, nil return &out, nil
} }
func (c *DocspellClient) AttachmentSize(ctx context.Context, id string) (int64, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodHead, c.url("/api/v1/sec/attachment/"+url.PathEscape(id)), nil)
if err != nil {
return 0, err
}
if c.Token != "" {
req.Header.Set(c.AuthHeader, c.Token)
}
httpClient := c.HTTPClient
if httpClient == nil {
httpClient = http.DefaultClient
}
resp, err := httpClient.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
return 0, fmt.Errorf("%s %s: %s: %s", req.Method, req.URL, resp.Status, strings.TrimSpace(string(body)))
}
return resp.ContentLength, nil
}
func (c *DocspellClient) DownloadAttachment(ctx context.Context, id string) ([]byte, error) { func (c *DocspellClient) DownloadAttachment(ctx context.Context, id string) ([]byte, error) {
return c.doBytes(ctx, http.MethodGet, "/api/v1/sec/attachment/"+url.PathEscape(id), nil) return c.doBytes(ctx, http.MethodGet, "/api/v1/sec/attachment/"+url.PathEscape(id), nil)
} }
@@ -179,7 +203,11 @@ func (c *DocspellClient) do(req *http.Request) ([]byte, error) {
if c.Token != "" { if c.Token != "" {
req.Header.Set(c.AuthHeader, c.Token) req.Header.Set(c.AuthHeader, c.Token)
} }
resp, err := c.HTTPClient.Do(req) httpClient := c.HTTPClient
if httpClient == nil {
httpClient = http.DefaultClient
}
resp, err := httpClient.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+23 -8
View File
@@ -54,7 +54,7 @@ func runMountCommand(args []string, cfg config, password string) error {
if err != nil { if err != nil {
return fmt.Errorf("search items: %w", err) return fmt.Errorf("search items: %w", err)
} }
root := buildMountTree(client, items) root := buildMountTree(context.Background(), client, items)
conn, err := fuse.Mount( conn, err := fuse.Mount(
fsFlags.Arg(0), fsFlags.Arg(0),
@@ -71,9 +71,12 @@ func runMountCommand(args []string, cfg config, password string) error {
return fs.Serve(conn, &docspellFS{root: root}) return fs.Serve(conn, &docspellFS{root: root})
} }
func buildMountTree(client *DocspellClient, items []SearchItem) *dirNode { func buildMountTree(ctx context.Context, client *DocspellClient, items []SearchItem) *dirNode {
root := &dirNode{name: "", children: map[string]fs.Node{}} root := &dirNode{name: "", children: map[string]fs.Node{}}
for _, item := range items { for _, item := range items {
if full, err := client.GetItem(ctx, item.ID); err == nil {
item = *full
}
folderName := "No Folder" folderName := "No Folder"
if item.Folder != nil && item.Folder.Name != "" { if item.Folder != nil && item.Folder.Name != "" {
folderName = item.Folder.Name folderName = item.Folder.Name
@@ -94,15 +97,22 @@ func buildMountTree(client *DocspellClient, items []SearchItem) *dirNode {
base = item.ID base = item.ID
} }
for i, att := range item.Attachments { for i, att := range item.Attachments {
name := sanitizeName(att.Name) ext := filepath.Ext(att.Name)
if name == "" { if ext == "" {
name = fmt.Sprintf("%s-%d.pdf", base, i+1) ext = ".pdf"
} }
name := base + ext
if len(item.Attachments) > 1 { if len(item.Attachments) > 1 {
name = fmt.Sprintf("%s - %02d - %s", base, i+1, name) name = fmt.Sprintf("%s - %02d%s", base, i+1, ext)
} }
name = uniqueName(dir.children, name) size := att.Size
dir.children[name] = &fileNode{name: name, attachmentID: att.ID, size: uint64(att.Size), knownSize: att.Size > 0, modTime: modTime, client: client} if size <= 0 {
if headSize, err := client.AttachmentSize(ctx, att.ID); err == nil && headSize > 0 {
size = headSize
}
}
name = uniqueName(dir.children, sanitizeName(name))
dir.children[name] = &fileNode{name: name, attachmentID: att.ID, size: uint64(size), knownSize: size > 0, modTime: modTime, client: client}
} }
} }
return root return root
@@ -189,6 +199,11 @@ func (f *fileNode) Attr(ctx context.Context, a *fuse.Attr) error {
return nil return nil
} }
func (f *fileNode) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) error {
resp.Flags |= fuse.OpenDirectIO
return nil
}
func (f *fileNode) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { func (f *fileNode) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
data, err := f.load(ctx) data, err := f.load(ctx)
if err != nil { if err != nil {
+6 -6
View File
@@ -18,7 +18,7 @@ func TestBuildMountTreeGroupsByFolderAndDate(t *testing.T) {
Attachments: []ItemAttachment{{ID: "att-1", Name: "invoice.pdf", Size: 123}}, Attachments: []ItemAttachment{{ID: "att-1", Name: "invoice.pdf", Size: 123}},
}} }}
root := buildMountTree(&DocspellClient{}, items) root := buildMountTree(context.Background(), &DocspellClient{}, items)
folder, ok := root.children["Bills_Private"].(*dirNode) folder, ok := root.children["Bills_Private"].(*dirNode)
if !ok { if !ok {
t.Fatalf("expected sanitized folder directory, got %#v", root.children) t.Fatalf("expected sanitized folder directory, got %#v", root.children)
@@ -31,8 +31,8 @@ func TestBuildMountTreeGroupsByFolderAndDate(t *testing.T) {
if !ok { if !ok {
t.Fatalf("expected month directory, got %#v", year.children) t.Fatalf("expected month directory, got %#v", year.children)
} }
if _, ok := month.children["invoice.pdf"].(*fileNode); !ok { if _, ok := month.children["Invoice_July.pdf"].(*fileNode); !ok {
t.Fatalf("expected attachment file, got %#v", month.children) t.Fatalf("expected item-named attachment file, got %#v", month.children)
} }
} }
@@ -46,13 +46,13 @@ func TestBuildMountTreeDeduplicatesAttachmentNames(t *testing.T) {
}, },
}} }}
root := buildMountTree(&DocspellClient{}, items) root := buildMountTree(context.Background(), &DocspellClient{}, items)
folder := root.children["No Folder"].(*dirNode) folder := root.children["No Folder"].(*dirNode)
date := folder.children["No Date"].(*dirNode) date := folder.children["No Date"].(*dirNode)
if _, ok := date.children["Invoice - 01 - same.pdf"]; !ok { if _, ok := date.children["Invoice - 01.pdf"]; !ok {
t.Fatalf("expected first unique name, got %#v", date.children) t.Fatalf("expected first unique name, got %#v", date.children)
} }
if _, ok := date.children["Invoice - 02 - same.pdf"]; !ok { if _, ok := date.children["Invoice - 02.pdf"]; !ok {
t.Fatalf("expected second unique name, got %#v", date.children) t.Fatalf("expected second unique name, got %#v", date.children)
} }
} }