summaryrefslogtreecommitdiffstats
path: root/resources/resource_spec.go
diff options
context:
space:
mode:
Diffstat (limited to 'resources/resource_spec.go')
-rw-r--r--resources/resource_spec.go304
1 files changed, 304 insertions, 0 deletions
diff --git a/resources/resource_spec.go b/resources/resource_spec.go
new file mode 100644
index 000000000..528a2bd58
--- /dev/null
+++ b/resources/resource_spec.go
@@ -0,0 +1,304 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package resources
+
+import (
+ "errors"
+ "fmt"
+ "mime"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/gohugoio/hugo/helpers"
+
+ "github.com/gohugoio/hugo/cache/filecache"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/media"
+ "github.com/gohugoio/hugo/output"
+ "github.com/gohugoio/hugo/resources/images"
+ "github.com/gohugoio/hugo/resources/page"
+ "github.com/gohugoio/hugo/resources/resource"
+ "github.com/gohugoio/hugo/tpl"
+ "github.com/spf13/afero"
+)
+
+func NewSpec(
+ s *helpers.PathSpec,
+ fileCaches filecache.Caches,
+ logger *loggers.Logger,
+ outputFormats output.Formats,
+ mimeTypes media.Types) (*Spec, error) {
+
+ imgConfig, err := images.DecodeConfig(s.Cfg.GetStringMap("imaging"))
+ if err != nil {
+ return nil, err
+ }
+
+ imaging := &images.ImageProcessor{Cfg: imgConfig}
+
+ if logger == nil {
+ logger = loggers.NewErrorLogger()
+ }
+
+ permalinks, err := page.NewPermalinkExpander(s)
+ if err != nil {
+ return nil, err
+ }
+
+ rs := &Spec{PathSpec: s,
+ Logger: logger,
+ imaging: imaging,
+ MediaTypes: mimeTypes,
+ OutputFormats: outputFormats,
+ Permalinks: permalinks,
+ FileCaches: fileCaches,
+ imageCache: newImageCache(
+ fileCaches.ImageCache(),
+
+ s,
+ )}
+
+ rs.ResourceCache = newResourceCache(rs)
+
+ return rs, nil
+
+}
+
+type Spec struct {
+ *helpers.PathSpec
+
+ MediaTypes media.Types
+ OutputFormats output.Formats
+
+ Logger *loggers.Logger
+
+ TextTemplates tpl.TemplateParseFinder
+
+ Permalinks page.PermalinkExpander
+
+ // Holds default filter settings etc.
+ imaging *images.ImageProcessor
+
+ imageCache *imageCache
+ ResourceCache *ResourceCache
+ FileCaches filecache.Caches
+}
+
+func (r *Spec) New(fd ResourceSourceDescriptor) (resource.Resource, error) {
+ return r.newResourceFor(fd)
+}
+
+func (r *Spec) CacheStats() string {
+ r.imageCache.mu.RLock()
+ defer r.imageCache.mu.RUnlock()
+
+ s := fmt.Sprintf("Cache entries: %d", len(r.imageCache.store))
+
+ count := 0
+ for k := range r.imageCache.store {
+ if count > 5 {
+ break
+ }
+ s += "\n" + k
+ count++
+ }
+
+ return s
+}
+
+func (r *Spec) ClearCaches() {
+ r.imageCache.clear()
+ r.ResourceCache.clear()
+}
+
+func (r *Spec) DeleteCacheByPrefix(prefix string) {
+ r.imageCache.deleteByPrefix(prefix)
+}
+
+// TODO(bep) unify
+func (r *Spec) IsInImageCache(key string) bool {
+ // This is used for cache pruning. We currently only have images, but we could
+ // imagine expanding on this.
+ return r.imageCache.isInCache(key)
+}
+
+func (s *Spec) String() string {
+ return "spec"
+}
+
+// TODO(bep) clean up below
+func (r *Spec) newGenericResource(sourceFs afero.Fs,
+ targetPathBuilder func() page.TargetPaths,
+ osFileInfo os.FileInfo,
+ sourceFilename,
+ baseFilename string,
+ mediaType media.Type) *genericResource {
+ return r.newGenericResourceWithBase(
+ sourceFs,
+ nil,
+ nil,
+ targetPathBuilder,
+ osFileInfo,
+ sourceFilename,
+ baseFilename,
+ mediaType,
+ )
+
+}
+
+func (r *Spec) newGenericResourceWithBase(
+ sourceFs afero.Fs,
+ openReadSeekerCloser resource.OpenReadSeekCloser,
+ targetPathBaseDirs []string,
+ targetPathBuilder func() page.TargetPaths,
+ osFileInfo os.FileInfo,
+ sourceFilename,
+ baseFilename string,
+ mediaType media.Type) *genericResource {
+
+ if osFileInfo != nil && osFileInfo.IsDir() {
+ panic(fmt.Sprintf("dirs not supported resource types: %v", osFileInfo))
+ }
+
+ // This value is used both to construct URLs and file paths, but start
+ // with a Unix-styled path.
+ baseFilename = helpers.ToSlashTrimLeading(baseFilename)
+ fpath, fname := path.Split(baseFilename)
+
+ var resourceType string
+ if mediaType.MainType == "image" {
+ resourceType = mediaType.MainType
+ } else {
+ resourceType = mediaType.SubType
+ }
+
+ pathDescriptor := &resourcePathDescriptor{
+ baseTargetPathDirs: helpers.UniqueStringsReuse(targetPathBaseDirs),
+ targetPathBuilder: targetPathBuilder,
+ relTargetDirFile: dirFile{dir: fpath, file: fname},
+ }
+
+ gfi := &resourceFileInfo{
+ fi: osFileInfo,
+ openReadSeekerCloser: openReadSeekerCloser,
+ sourceFs: sourceFs,
+ sourceFilename: sourceFilename,
+ h: &resourceHash{},
+ }
+
+ g := &genericResource{
+ resourceFileInfo: gfi,
+ resourcePathDescriptor: pathDescriptor,
+ mediaType: mediaType,
+ resourceType: resourceType,
+ spec: r,
+ params: make(map[string]interface{}),
+ name: baseFilename,
+ title: baseFilename,
+ resourceContent: &resourceContent{},
+ }
+
+ return g
+
+}
+
+func (r *Spec) newResource(sourceFs afero.Fs, fd ResourceSourceDescriptor) (resource.Resource, error) {
+ fi := fd.FileInfo
+ var sourceFilename string
+
+ if fd.OpenReadSeekCloser != nil {
+ } else if fd.SourceFilename != "" {
+ var err error
+ fi, err = sourceFs.Stat(fd.SourceFilename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+ return nil, err
+ }
+ sourceFilename = fd.SourceFilename
+ } else {
+ sourceFilename = fd.SourceFile.Filename()
+ }
+
+ if fd.RelTargetFilename == "" {
+ fd.RelTargetFilename = sourceFilename
+ }
+
+ ext := strings.ToLower(filepath.Ext(fd.RelTargetFilename))
+ mimeType, found := r.MediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, "."))
+ // TODO(bep) we need to handle these ambigous types better, but in this context
+ // we most likely want the application/xml type.
+ if mimeType.Suffix() == "xml" && mimeType.SubType == "rss" {
+ mimeType, found = r.MediaTypes.GetByType("application/xml")
+ }
+
+ if !found {
+ // A fallback. Note that mime.TypeByExtension is slow by Hugo standards,
+ // so we should configure media types to avoid this lookup for most
+ // situations.
+ mimeStr := mime.TypeByExtension(ext)
+ if mimeStr != "" {
+ mimeType, _ = media.FromStringAndExt(mimeStr, ext)
+ }
+ }
+
+ gr := r.newGenericResourceWithBase(
+ sourceFs,
+ fd.OpenReadSeekCloser,
+ fd.TargetBasePaths,
+ fd.TargetPaths,
+ fi,
+ sourceFilename,
+ fd.RelTargetFilename,
+ mimeType)
+
+ if mimeType.MainType == "image" {
+ imgFormat, ok := images.ImageFormatFromExt(ext)
+ if ok {
+ ir := &imageResource{
+ Image: images.NewImage(imgFormat, r.imaging, nil, gr),
+ baseResource: gr,
+ }
+ return newResourceAdapter(gr.spec, fd.LazyPublish, ir), nil
+ }
+
+ }
+
+ return newResourceAdapter(gr.spec, fd.LazyPublish, gr), nil
+
+}
+
+func (r *Spec) newResourceFor(fd ResourceSourceDescriptor) (resource.Resource, error) {
+ if fd.OpenReadSeekCloser == nil {
+ if fd.SourceFile != nil && fd.SourceFilename != "" {
+ return nil, errors.New("both SourceFile and AbsSourceFilename provided")
+ } else if fd.SourceFile == nil && fd.SourceFilename == "" {
+ return nil, errors.New("either SourceFile or AbsSourceFilename must be provided")
+ }
+ }
+
+ if fd.RelTargetFilename == "" {
+ fd.RelTargetFilename = fd.Filename()
+ }
+
+ if len(fd.TargetBasePaths) == 0 {
+ // If not set, we publish the same resource to all hosts.
+ fd.TargetBasePaths = r.MultihostTargetBasePaths
+ }
+
+ return r.newResource(fd.Fs, fd)
+}