]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
resources: Add FromOpts for more effective resource creation
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 3 Jan 2025 17:36:44 +0000 (18:36 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 5 Jan 2025 11:43:37 +0000 (12:43 +0100)
E.g. when the targetPath already contains a hash or if the resource content is expensive to create.

common/hashing/hashing.go
resources/resource_factories/create/create.go

index 70aa74ecd7a76176f76af0cd08ace185d9b05018..0b470cafeae6b899a7263787b3e077caf6acbce7 100644 (file)
@@ -38,6 +38,19 @@ func XXHashFromReader(r io.Reader) (uint64, int64, error) {
        return h.Sum64(), size, nil
 }
 
+// XxHashFromReaderHexEncoded calculates the xxHash for the given reader
+// and returns the hash as a hex encoded string.
+func XxHashFromReaderHexEncoded(r io.Reader) (string, error) {
+       h := getXxHashReadFrom()
+       defer putXxHashReadFrom(h)
+       _, err := io.Copy(h, r)
+       if err != nil {
+               return "", err
+       }
+       hash := h.Sum(nil)
+       return hex.EncodeToString(hash), nil
+}
+
 // XXHashFromString calculates the xxHash for the given string.
 func XXHashFromString(s string) (uint64, error) {
        h := xxhash.New()
index 581c0a8541d662d7097602d66a9d64884595bf53..1d78a2923a29a1a3d9996f570b3bc7d69c1ac5a5 100644 (file)
@@ -218,22 +218,83 @@ func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource)
        })
 }
 
-// FromString creates a new Resource from a string with the given relative target path.
-// TODO(bep) see #10912; we currently emit a warning for this config scenario.
-func (c *Client) FromString(targetPath, content string) (resource.Resource, error) {
-       targetPath = path.Clean(targetPath)
-       key := dynacache.CleanKey(targetPath) + hashing.MD5FromStringHexEncoded(content)
+type Options struct {
+       // The target path relative to the publish directory.
+       // Unix style path, i.e. "images/logo.png".
+       TargetPath string
+
+       // Whether the TargetPath has a hash in it which will change if the resource changes.
+       // If not, we will calculate a hash from the content.
+       TargetPathHasHash bool
+
+       // The content to create the Resource from.
+       CreateContent func() (func() (hugio.ReadSeekCloser, error), error)
+}
+
+// FromOpts creates a new Resource from the given Options.
+// Make sure to set optis.TargetPathHasHash if the TargetPath already contains a hash,
+// as this avoids the need to calculate it.
+// To create a new ReadSeekCloser from a string, use hugio.NewReadSeekerNoOpCloserFromString,
+// or hugio.NewReadSeekerNoOpCloserFromBytes for a byte slice.
+// See FromString.
+func (c *Client) FromOpts(opts Options) (resource.Resource, error) {
+       opts.TargetPath = path.Clean(opts.TargetPath)
+       var hash string
+       var newReadSeeker func() (hugio.ReadSeekCloser, error) = nil
+       if !opts.TargetPathHasHash {
+               var err error
+               newReadSeeker, err = opts.CreateContent()
+               if err != nil {
+                       return nil, err
+               }
+               if err := func() error {
+                       r, err := newReadSeeker()
+                       if err != nil {
+                               return err
+                       }
+                       defer r.Close()
+
+                       hash, err = hashing.XxHashFromReaderHexEncoded(r)
+                       if err != nil {
+                               return err
+                       }
+                       return nil
+               }(); err != nil {
+                       return nil, err
+               }
+       }
+
+       key := dynacache.CleanKey(opts.TargetPath) + hash
        r, err := c.rs.ResourceCache.GetOrCreate(key, func() (resource.Resource, error) {
+               if newReadSeeker == nil {
+                       var err error
+                       newReadSeeker, err = opts.CreateContent()
+                       if err != nil {
+                               return nil, err
+                       }
+               }
                return c.rs.NewResource(
                        resources.ResourceSourceDescriptor{
                                LazyPublish:   true,
                                GroupIdentity: identity.Anonymous, // All usage of this resource are tracked via its string content.
                                OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
-                                       return hugio.NewReadSeekerNoOpCloserFromString(content), nil
+                                       return newReadSeeker()
                                },
-                               TargetPath: targetPath,
+                               TargetPath: opts.TargetPath,
                        })
        })
 
        return r, err
 }
+
+// FromString creates a new Resource from a string with the given relative target path.
+func (c *Client) FromString(targetPath, content string) (resource.Resource, error) {
+       return c.FromOpts(Options{
+               TargetPath: targetPath,
+               CreateContent: func() (func() (hugio.ReadSeekCloser, error), error) {
+                       return func() (hugio.ReadSeekCloser, error) {
+                               return hugio.NewReadSeekerNoOpCloserFromString(content), nil
+                       }, nil
+               },
+       })
+}