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
}
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) {
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 != "" {
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 {
return nil, err
}
+23 -8
View File
@@ -54,7 +54,7 @@ func runMountCommand(args []string, cfg config, password string) error {
if err != nil {
return fmt.Errorf("search items: %w", err)
}
root := buildMountTree(client, items)
root := buildMountTree(context.Background(), client, items)
conn, err := fuse.Mount(
fsFlags.Arg(0),
@@ -71,9 +71,12 @@ func runMountCommand(args []string, cfg config, password string) error {
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{}}
for _, item := range items {
if full, err := client.GetItem(ctx, item.ID); err == nil {
item = *full
}
folderName := "No Folder"
if item.Folder != nil && item.Folder.Name != "" {
folderName = item.Folder.Name
@@ -94,15 +97,22 @@ func buildMountTree(client *DocspellClient, items []SearchItem) *dirNode {
base = item.ID
}
for i, att := range item.Attachments {
name := sanitizeName(att.Name)
if name == "" {
name = fmt.Sprintf("%s-%d.pdf", base, i+1)
ext := filepath.Ext(att.Name)
if ext == "" {
ext = ".pdf"
}
name := base + ext
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)
dir.children[name] = &fileNode{name: name, attachmentID: att.ID, size: uint64(att.Size), knownSize: att.Size > 0, modTime: modTime, client: client}
size := att.Size
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
@@ -189,6 +199,11 @@ func (f *fileNode) Attr(ctx context.Context, a *fuse.Attr) error {
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 {
data, err := f.load(ctx)
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}},
}}
root := buildMountTree(&DocspellClient{}, items)
root := buildMountTree(context.Background(), &DocspellClient{}, items)
folder, ok := root.children["Bills_Private"].(*dirNode)
if !ok {
t.Fatalf("expected sanitized folder directory, got %#v", root.children)
@@ -31,8 +31,8 @@ func TestBuildMountTreeGroupsByFolderAndDate(t *testing.T) {
if !ok {
t.Fatalf("expected month directory, got %#v", year.children)
}
if _, ok := month.children["invoice.pdf"].(*fileNode); !ok {
t.Fatalf("expected attachment file, got %#v", month.children)
if _, ok := month.children["Invoice_July.pdf"].(*fileNode); !ok {
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)
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)
}
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)
}
}