fix(webdav): name the thumbnail config fields after what they configure

The yaml keys stay as they are, so a config file written against them keeps
working, and OC_DISABLE_PREVIEWS is back without disabling search.
This commit is contained in:
Dominik Schmidt committed 2026-10-19 14:57:35 +02:00
1 parent 95fcc4c623
commit 4c0768cb83
5 files changed
+99 -78

No files matched your search

+1 -1
View File
@@ -36,5 +36,5 @@ type Thumbnail struct {
// decoded and resized in parallel. 0 (the default) means unlimited, matching
// main's THUMBNAILS_MAX_CONCURRENT_REQUESTS. Requests arriving while the limit
// is reached get HTTP 429 Too Many Requests.
MaxConcurrentRequests int `yaml:"max_concurrent_requests" env:"THUMBNAILS_MAX_CONCURRENT_REQUESTS" desc:"Maximum number of concurrent thumbnail generation requests. Default is 0 which is unlimited." introductionVersion:"1.0.0"`
MaxConcurrentRequests int `yaml:"max_concurrent_requests" env:"THUMBNAILS_MAX_CONCURRENT_REQUESTS" desc:"Maximum number of concurrent thumbnail generation requests. Default is 0 which is unlimited." introductionVersion:"%%NEXT%%"`
}
+15 -12
View File
@@ -24,20 +24,23 @@ type Config struct {
OpenCloudPublicURL string `yaml:"opencloud_public_url" env:"OC_URL;OC_PUBLIC_URL" desc:"URL, where OpenCloud is reachable for users." introductionVersion:"1.0.0"`
WebdavNamespace string `yaml:"webdav_namespace" env:"WEBDAV_WEBDAV_NAMESPACE" desc:"CS3 path layout to use when forwarding /webdav requests" introductionVersion:"1.0.0"`
RevaGateway string `yaml:"reva_gateway" env:"OC_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" introductionVersion:"1.0.0"`
DisablePreviews bool `yaml:"disablePreviews" env:"OC_DISABLE_PREVIEWS;WEBDAV_DISABLE_PREVIEWS" desc:"Set this option to 'true' to disable rendering of thumbnails triggered via webdav access. Note that when disabled, all access to preview related webdav paths will return a 404." introductionVersion:"1.0.0"`
ThumbnailGeneratorURL string `yaml:"thumbnail_generator_url" env:"WEBDAV_THUMBNAIL_GENERATOR_URL" desc:"Base URL of the thumbnail generator service, e.g. http://thumbnails:9130" introductionVersion:"1.0.0"`
ThumbnailGeneratorTimeout string `yaml:"thumbnail_generator_timeout" env:"WEBDAV_THUMBNAIL_GENERATOR_TIMEOUT" desc:"HTTP timeout for requests to the thumbnail generator, e.g. 30s" introductionVersion:"1.0.0"`
MaxInputFileSize string `yaml:"max_input_file_size" env:"WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE" desc:"Maximum file size of an input image for thumbnail generation. Usable common abbreviations: [KB, KiB, MB, MiB, GB, GiB], example: 50MB" introductionVersion:"1.0.0"`
ThumbnailGeneratorURL string `yaml:"thumbnail_generator_url" env:"WEBDAV_THUMBNAIL_GENERATOR_URL" desc:"Base URL of the thumbnail generator service, e.g. http://thumbnails:9130" introductionVersion:"%%NEXT%%"`
ThumbnailGeneratorTimeout string `yaml:"thumbnail_generator_timeout" env:"WEBDAV_THUMBNAIL_GENERATOR_TIMEOUT" desc:"HTTP timeout for requests to the thumbnail generator, e.g. 30s" introductionVersion:"%%NEXT%%"`
// the yaml key stays unprefixed, a config file written against it has to keep working
ThumbnailMaxInputFileSize string `yaml:"max_input_file_size" env:"WEBDAV_THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE;THUMBNAILS_MAX_INPUT_IMAGE_FILE_SIZE" desc:"Maximum file size of an input image for thumbnail generation. Usable common abbreviations: [KB, KiB, MB, MiB, GB, GiB], example: 50MB" introductionVersion:"%%NEXT%%"`
ThumbnailCacheBackend string `yaml:"thumbnail_cache_backend" env:"WEBDAV_THUMBNAIL_CACHE_BACKEND" desc:"Cache backend for imagor thumbnails: 'none', 'memory', 'file', or 's3'. Default: file." introductionVersion:"1.0.0"`
ThumbnailCacheDir string `yaml:"thumbnail_cache_dir" env:"WEBDAV_THUMBNAIL_CACHE_DIR" desc:"Directory for file-based thumbnail cache (Default: $OC_BASE_DATA_PATH/thumbnails/files). Only used when ThumbnailCacheBackend is 'file'." introductionVersion:"1.0.0"`
ThumbnailResolutions []string `yaml:"thumbnail_resolutions" env:"THUMBNAILS_RESOLUTIONS;WEBDAV_THUMBNAIL_RESOLUTIONS" desc:"Allowed thumbnail edge lengths, like 512. Each axis of a requested size is rounded up to the next one of these before the box is sent to the generator. Legacy WidthxHeight entries are still accepted and contribute both of their sides." introductionVersion:"1.0.0"`
ThumbnailCacheS3Bucket string `yaml:"thumbnail_cache_s3_bucket" env:"WEBDAV_THUMBNAIL_CACHE_S3_BUCKET" desc:"S3 bucket name for thumbnail cache when ThumbnailCacheBackend is 's3'." introductionVersion:"1.0.0"`
ThumbnailCacheS3Region string `yaml:"thumbnail_cache_s3_region" env:"WEBDAV_THUMBNAIL_CACHE_S3_REGION" desc:"S3 region for thumbnail cache when ThumbnailCacheBackend is 's3'." introductionVersion:"1.0.0"`
ThumbnailCacheS3Endpoint string `yaml:"thumbnail_cache_s3_endpoint" env:"WEBDAV_THUMBNAIL_CACHE_S3_ENDPOINT" desc:"S3 endpoint URL (for MinIO or compatible services) when ThumbnailCacheBackend is 's3'." introductionVersion:"1.0.0"`
ThumbnailCacheS3AccessKey string `yaml:"thumbnail_cache_s3_access_key" env:"WEBDAV_THUMBNAIL_CACHE_S3_ACCESS_KEY" desc:"S3 access key for thumbnail cache authentication." introductionVersion:"1.0.0"`
ThumbnailCacheS3SecretKey string `yaml:"thumbnail_cache_s3_secret_key" env:"WEBDAV_THUMBNAIL_CACHE_S3_SECRET_KEY" desc:"S3 secret key for thumbnail cache authentication." introductionVersion:"1.0.0"`
FontMapFile string `yaml:"font_map_file" env:"WEBDAV_THUMBNAILS_TXT_FONTMAP_FILE;THUMBNAILS_TXT_FONTMAP_FILE" desc:"The path to a font map file for txt thumbnails." introductionVersion:"1.0.0"`
ThumbnailCacheBackend string `yaml:"thumbnail_cache_backend" env:"WEBDAV_THUMBNAIL_CACHE_BACKEND" desc:"Cache backend for imagor thumbnails: 'none', 'memory', 'file', or 's3'. Default: file." introductionVersion:"%%NEXT%%"`
ThumbnailCacheDir string `yaml:"thumbnail_cache_dir" env:"WEBDAV_THUMBNAIL_CACHE_DIR" desc:"Directory for file-based thumbnail cache (Default: $OC_BASE_DATA_PATH/thumbnails/files). Only used when ThumbnailCacheBackend is 'file'." introductionVersion:"%%NEXT%%"`
ThumbnailResolutions []string `yaml:"thumbnail_resolutions" env:"THUMBNAILS_RESOLUTIONS;WEBDAV_THUMBNAIL_RESOLUTIONS" desc:"Allowed thumbnail edge lengths, like 512. Each axis of a requested size is rounded up to the next one of these before the box is sent to the generator. Legacy WidthxHeight entries are still accepted and contribute both of their sides." introductionVersion:"%%NEXT%%"`
ThumbnailCacheS3Bucket string `yaml:"thumbnail_cache_s3_bucket" env:"WEBDAV_THUMBNAIL_CACHE_S3_BUCKET" desc:"S3 bucket name for thumbnail cache when ThumbnailCacheBackend is 's3'." introductionVersion:"%%NEXT%%"`
ThumbnailCacheS3Region string `yaml:"thumbnail_cache_s3_region" env:"WEBDAV_THUMBNAIL_CACHE_S3_REGION" desc:"S3 region for thumbnail cache when ThumbnailCacheBackend is 's3'." introductionVersion:"%%NEXT%%"`
ThumbnailCacheS3Endpoint string `yaml:"thumbnail_cache_s3_endpoint" env:"WEBDAV_THUMBNAIL_CACHE_S3_ENDPOINT" desc:"S3 endpoint URL (for MinIO or compatible services) when ThumbnailCacheBackend is 's3'." introductionVersion:"%%NEXT%%"`
ThumbnailCacheS3AccessKey string `yaml:"thumbnail_cache_s3_access_key" env:"WEBDAV_THUMBNAIL_CACHE_S3_ACCESS_KEY" desc:"S3 access key for thumbnail cache authentication." introductionVersion:"%%NEXT%%"`
ThumbnailCacheS3SecretKey string `yaml:"thumbnail_cache_s3_secret_key" env:"WEBDAV_THUMBNAIL_CACHE_S3_SECRET_KEY" desc:"S3 secret key for thumbnail cache authentication." introductionVersion:"%%NEXT%%"`
// the yaml key stays unprefixed, a config file written against it has to keep working
ThumbnailFontMapFile string `yaml:"font_map_file" env:"WEBDAV_THUMBNAILS_TXT_FONTMAP_FILE;THUMBNAILS_TXT_FONTMAP_FILE" desc:"The path to a font map file for txt thumbnails." introductionVersion:"%%NEXT%%"`
Context context.Context `yaml:"-"`
}
@@ -47,7 +47,7 @@ func DefaultConfig() *config.Config {
ThumbnailGeneratorURL: "http://127.0.0.1:9186",
ThumbnailGeneratorTimeout: "30s",
MaxInputFileSize: "50MB",
ThumbnailMaxInputFileSize: "50MB",
ThumbnailCacheBackend: "file",
ThumbnailCacheDir: path.Join(cored.BaseDataPath(), "thumbnails", "files"),
ThumbnailResolutions: []string{
+1 -1
View File
@@ -26,10 +26,10 @@ import (
searchmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/search/v0"
searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/constants"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/net"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/prop"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/propfind"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail"
)
const (
+81 -63
View File
@@ -84,6 +84,83 @@ func NewService(opts ...Option) (Service, error) {
return nil, err
}
// with previews disabled nothing talks to the generator, so the workflow
// and its cache are never built
var wf *workflow.ThumbnailWorkflow
if !conf.DisablePreviews {
wf, err = newThumbnailWorkflow(conf, options.Logger, gatewaySelector)
if err != nil {
return nil, fmt.Errorf("create thumbnail workflow: %w", err)
}
}
svc := Webdav{
config: conf,
log: options.Logger,
mux: m,
searchClient: searchsvc.NewSearchProviderService("eu.opencloud.api.search", conf.GrpcClient),
workflow: wf,
gatewaySelector: gatewaySelector,
}
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(svc.DavUserContext())
if !conf.DisablePreviews {
r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail)
r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail)
r.Get("/dav/spaces/{id}", svc.SpacesThumbnail)
r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail)
r.Get("/remote.php/dav/files/{id}", svc.Thumbnail)
r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail)
r.Get("/dav/files/{id}", svc.Thumbnail)
r.Get("/dav/files/{id}/*", svc.Thumbnail)
}
r.MethodFunc("REPORT", "/remote.php/dav/spaces*", svc.Search)
r.MethodFunc("REPORT", "/dav/spaces*", svc.Search)
r.MethodFunc("REPORT", "/remote.php/dav/files*", svc.Search)
r.MethodFunc("REPORT", "/dav/files*", svc.Search)
})
if !conf.DisablePreviews {
r.Group(func(r chi.Router) {
r.Use(svc.DavPublicContext())
r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead)
r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead)
r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail)
r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail)
})
}
r.Group(func(r chi.Router) {
r.Use(svc.WebDAVContext())
if !conf.DisablePreviews {
r.Get("/remote.php/webdav/*", svc.Thumbnail)
r.Get("/webdav/*", svc.Thumbnail)
}
r.MethodFunc("REPORT", "/remote.php/webdav*", svc.Search)
r.MethodFunc("REPORT", "/webdav*", svc.Search)
})
})
_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
return nil
})
return svc, nil
}
// newThumbnailWorkflow wires the preview pipeline: the generator client, the
// cache and the gateway-backed lookups.
func newThumbnailWorkflow(conf *config.Config, logger log.Logger, gatewaySelector pool.Selectable[gatewayv1beta1.GatewayAPIClient]) (*workflow.ThumbnailWorkflow, error) {
genTimeout, _ := time.ParseDuration(conf.ThumbnailGeneratorTimeout)
if genTimeout == 0 {
genTimeout = 30 * time.Second
@@ -94,7 +171,7 @@ func NewService(opts ...Option) (Service, error) {
return nil, fmt.Errorf("parse thumbnail sizes: %w", err)
}
maxInputSize, err := generator.ParseMaxInputFileSize(conf.MaxInputFileSize)
maxInputSize, err := generator.ParseMaxInputFileSize(conf.ThumbnailMaxInputFileSize)
if err != nil {
return nil, fmt.Errorf("parse max input file size: %w", err)
}
@@ -111,79 +188,20 @@ func NewService(opts ...Option) (Service, error) {
httpClient := &http.Client{Timeout: genTimeout}
wf, err := workflow.NewWorkflow(
return workflow.NewWorkflow(
workflow.WithGeneratorURL(conf.ThumbnailGeneratorURL),
workflow.WithCache(c),
workflow.WithHTTPClient(httpClient),
workflow.WithMaxInputSize(maxInputSize),
workflow.WithSizes(sizes),
workflow.WithWebdavNamespace(conf.WebdavNamespace),
workflow.WithFontMapFile(conf.FontMapFile),
workflow.WithLogger(options.Logger),
workflow.WithFontMapFile(conf.ThumbnailFontMapFile),
workflow.WithLogger(logger),
workflow.WithStater(workflow.NewGatewayStater(gatewaySelector)),
workflow.WithFileDownloader(workflow.NewGatewayFileDownloader(gatewaySelector, httpClient)),
workflow.WithSpaceLookup(workflow.NewGatewaySpaceLookup(gatewaySelector)),
workflow.WithUserResolver(workflow.NewGatewayUserResolver(gatewaySelector)),
)
if err != nil {
return nil, fmt.Errorf("create thumbnail workflow: %w", err)
}
svc := Webdav{
config: conf,
log: options.Logger,
mux: m,
searchClient: searchsvc.NewSearchProviderService("eu.opencloud.api.search", conf.GrpcClient),
workflow: wf,
gatewaySelector: gatewaySelector,
}
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
r.Group(func(r chi.Router) {
r.Use(svc.DavUserContext())
r.Get("/remote.php/dav/spaces/{id}", svc.SpacesThumbnail)
r.Get("/remote.php/dav/spaces/{id}/*", svc.SpacesThumbnail)
r.Get("/dav/spaces/{id}", svc.SpacesThumbnail)
r.Get("/dav/spaces/{id}/*", svc.SpacesThumbnail)
r.MethodFunc("REPORT", "/remote.php/dav/spaces*", svc.Search)
r.MethodFunc("REPORT", "/dav/spaces*", svc.Search)
r.Get("/remote.php/dav/files/{id}", svc.Thumbnail)
r.Get("/remote.php/dav/files/{id}/*", svc.Thumbnail)
r.Get("/dav/files/{id}", svc.Thumbnail)
r.Get("/dav/files/{id}/*", svc.Thumbnail)
r.MethodFunc("REPORT", "/remote.php/dav/files*", svc.Search)
r.MethodFunc("REPORT", "/dav/files*", svc.Search)
})
r.Group(func(r chi.Router) {
r.Use(svc.DavPublicContext())
r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead)
r.Head("/dav/public-files/{token}/*", svc.PublicThumbnailHead)
r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail)
r.Get("/dav/public-files/{token}/*", svc.PublicThumbnail)
})
r.Group(func(r chi.Router) {
r.Use(svc.WebDAVContext())
r.Get("/remote.php/webdav/*", svc.Thumbnail)
r.Get("/webdav/*", svc.Thumbnail)
r.MethodFunc("REPORT", "/remote.php/webdav*", svc.Search)
r.MethodFunc("REPORT", "/webdav*", svc.Search)
})
})
_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
options.Logger.Debug().Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
return nil
})
return svc, nil
}
// Webdav implements the business logic for Service.