Fix same resource file published more than once

This may still happen, though, in low memory situations or very big sites, but I'm not sure it's worth spending time on fixing that. Writing the same file more than once isn't harmful, the negative effect is the false path warning.
 We may find a way to detect that situation if this becomes a real problem.

Fixes #13164
This commit is contained in:
Bjørn Erik Pedersen 2024-12-26 13:47:10 +01:00
parent ec0caaec7c
commit 77824d704c
4 changed files with 48 additions and 27 deletions

View file

@ -311,12 +311,13 @@ func (fs *RootMappingFs) Open(name string) (afero.File, error) {
// Stat returns the os.FileInfo structure describing a given file. If there is
// an error, it will be of type *os.PathError.
// If multiple roots are found, the last one will be used.
func (fs *RootMappingFs) Stat(name string) (os.FileInfo, error) {
fis, err := fs.doStat(name)
if err != nil {
return nil, err
}
return fis[0], nil
return fis[len(fis)-1], nil
}
type ComponentPath struct {

View file

@ -36,6 +36,11 @@ func newResourceCache(rs *Spec, memCache *dynacache.Cache) *ResourceCache {
"/res1",
dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnChange, Weight: 40},
),
cacheResourceFile: dynacache.GetOrCreatePartition[string, resource.Resource](
memCache,
"/res2",
dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnChange, Weight: 40},
),
CacheResourceRemote: dynacache.GetOrCreatePartition[string, resource.Resource](
memCache,
"/resr",
@ -58,6 +63,7 @@ type ResourceCache struct {
sync.RWMutex
cacheResource *dynacache.Partition[string, resource.Resource]
cacheResourceFile *dynacache.Partition[string, resource.Resource]
CacheResourceRemote *dynacache.Partition[string, resource.Resource]
cacheResources *dynacache.Partition[string, resource.Resources]
cacheResourceTransformation *dynacache.Partition[string, *resourceAdapterInner]
@ -79,6 +85,12 @@ func (c *ResourceCache) GetOrCreate(key string, f func() (resource.Resource, err
})
}
func (c *ResourceCache) GetOrCreateFile(key string, f func() (resource.Resource, error)) (resource.Resource, error) {
return c.cacheResourceFile.GetOrCreate(key, func(key string) (resource.Resource, error) {
return f()
})
}
func (c *ResourceCache) GetOrCreateResources(key string, f func() (resource.Resources, error)) (resource.Resources, error) {
return c.cacheResources.GetOrCreate(key, func(key string) (resource.Resources, error) {
return f()

View file

@ -143,19 +143,7 @@ func (c *Client) Get(pathname string) (resource.Resource, error) {
return nil, err
}
meta := fi.(hugofs.FileMetaInfo).Meta()
pi := meta.PathInfo
return c.rs.NewResource(resources.ResourceSourceDescriptor{
LazyPublish: true,
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
return c.rs.BaseFs.Assets.Fs.Open(filename)
},
Path: pi,
GroupIdentity: pi,
TargetPath: pathname,
SourceFilenameOrPath: meta.Filename,
})
return c.getOrCreateFileResource(fi.(hugofs.FileMetaInfo))
})
}
@ -181,19 +169,10 @@ func (c *Client) GetMatch(pattern string) (resource.Resource, error) {
return res[0], err
}
func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource) bool, firstOnly bool) (resource.Resources, error) {
pattern = glob.NormalizePath(pattern)
partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
key := path.Join(name, path.Join(partitions...))
key = path.Join(key, pattern)
return c.rs.ResourceCache.GetOrCreateResources(key, func() (resource.Resources, error) {
var res resource.Resources
handle := func(info hugofs.FileMetaInfo) (bool, error) {
func (c *Client) getOrCreateFileResource(info hugofs.FileMetaInfo) (resource.Resource, error) {
meta := info.Meta()
r, err := c.rs.NewResource(resources.ResourceSourceDescriptor{
return c.rs.ResourceCache.GetOrCreateFile(filepath.ToSlash(meta.Filename), func() (resource.Resource, error) {
return c.rs.NewResource(resources.ResourceSourceDescriptor{
LazyPublish: true,
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
return meta.Open()
@ -204,6 +183,20 @@ func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource)
TargetPath: meta.PathInfo.Unnormalized().Path(),
SourceFilenameOrPath: meta.Filename,
})
})
}
func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource) bool, firstOnly bool) (resource.Resources, error) {
pattern = glob.NormalizePath(pattern)
partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
key := path.Join(name, path.Join(partitions...))
key = path.Join(key, pattern)
return c.rs.ResourceCache.GetOrCreateResources(key, func() (resource.Resources, error) {
var res resource.Resources
handle := func(info hugofs.FileMetaInfo) (bool, error) {
r, err := c.getOrCreateFileResource(info)
if err != nil {
return true, err
}

View file

@ -0,0 +1,15 @@
hugo --printPathWarnings
! stderr 'Duplicate target paths'
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- assets/foo.txt --
foo
-- layouts/index.html --
A: {{ (resources.Get "foo.txt").RelPermalink }}
B: {{ (resources.GetMatch "foo.txt").RelPermalink }}
C: {{ (index (resources.Match "foo.txt") 0).RelPermalink }}
D: {{ (index (resources.ByType "text") 0).RelPermalink }}
-- layouts/unused/single.html --
{{ .Title }}