From 3fa311c6cba28fb513799bd4f82aa296c140904c Mon Sep 17 00:00:00 2001 From: Acbox Date: Fri, 3 Apr 2026 01:14:20 +0800 Subject: [PATCH] fix(media): proxy ContainerFileOpener through fallback storage provider The fallback provider introduced in 5aeb2fd3 wrapped containerfs but did not implement storage.ContainerFileOpener, causing IngestContainerFile to fail with "provider does not support container file reading". This broke outbound file attachments on all IM channels (Telegram, Discord, etc.) because container paths like /data/xxx.xlsx were passed as-is to the platform API instead of being ingested into the media store first. --- internal/media/service.go | 2 +- internal/storage/providers/fallback/provider.go | 17 +++++++++++++++++ internal/storage/storage.go | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/internal/media/service.go b/internal/media/service.go index ed890cc7..53a16f70 100644 --- a/internal/media/service.go +++ b/internal/media/service.go @@ -148,7 +148,7 @@ func (s *Service) IngestContainerFile(ctx context.Context, botID, containerPath } opener, ok := s.provider.(storage.ContainerFileOpener) if !ok { - return Asset{}, errors.New("provider does not support container file reading") + return Asset{}, storage.ErrContainerFileNotSupported } f, err := opener.OpenContainerFile(ctx, botID, containerPath) if err != nil { diff --git a/internal/storage/providers/fallback/provider.go b/internal/storage/providers/fallback/provider.go index 51818151..3c450a6a 100644 --- a/internal/storage/providers/fallback/provider.go +++ b/internal/storage/providers/fallback/provider.go @@ -10,6 +10,8 @@ import ( "github.com/memohai/memoh/internal/storage" ) +var _ storage.ContainerFileOpener = (*Provider)(nil) + // Provider delegates to primary and falls back to secondary on write errors. type Provider struct { primary storage.Provider @@ -79,3 +81,18 @@ func tryListPrefix(ctx context.Context, p storage.Provider, prefix string) ([]st } return nil, nil } + +// OpenContainerFile delegates to whichever inner provider implements +// storage.ContainerFileOpener, trying the primary first. +func (p *Provider) OpenContainerFile(ctx context.Context, botID, containerPath string) (io.ReadCloser, error) { + if opener, ok := p.primary.(storage.ContainerFileOpener); ok { + rc, err := opener.OpenContainerFile(ctx, botID, containerPath) + if err == nil { + return rc, nil + } + } + if opener, ok := p.secondary.(storage.ContainerFileOpener); ok { + return opener.OpenContainerFile(ctx, botID, containerPath) + } + return nil, storage.ErrContainerFileNotSupported +} diff --git a/internal/storage/storage.go b/internal/storage/storage.go index d0cc3aa5..1caa1bd3 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -3,9 +3,14 @@ package storage import ( "context" + "errors" "io" ) +// ErrContainerFileNotSupported is returned when no underlying provider +// implements ContainerFileOpener. +var ErrContainerFileNotSupported = errors.New("provider does not support container file reading") + // Provider abstracts object storage operations. type Provider interface { // Put writes data to storage under the given key.