summaryrefslogtreecommitdiffstats
path: root/commands/static_syncer.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-11-12 10:03:56 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-11-17 11:01:46 +0100
commit60dfb9a6e076200ab3ca3fd30e34bb3c14e0a893 (patch)
tree810d3d7ca40a55045fec4a0718eb7728621495e4 /commands/static_syncer.go
parent2e0465764b5dacc511b977b1c9aa07324ad0ee9c (diff)
Add support for multiple staticDirs
This commit adds support for multiple statDirs both on the global and language level. A simple `config.toml` example: ```bash staticDir = ["static1", "static2"] [languages] [languages.no] staticDir = ["staticDir_override", "static_no"] baseURL = "https://example.no" languageName = "Norsk" weight = 1 title = "På norsk" [languages.en] staticDir2 = "static_en" baseURL = "https://example.com" languageName = "English" weight = 2 title = "In English" ``` In the above, with no theme used: the English site will get its static files as a union of "static1", "static2" and "static_en". On file duplicates, the right-most version will win. the Norwegian site will get its static files as a union of "staticDir_override" and "static_no". This commit also concludes the Multihost support in #4027. Fixes #36 Closes #4027
Diffstat (limited to 'commands/static_syncer.go')
-rw-r--r--commands/static_syncer.go135
1 files changed, 135 insertions, 0 deletions
diff --git a/commands/static_syncer.go b/commands/static_syncer.go
new file mode 100644
index 000000000..98b745e4c
--- /dev/null
+++ b/commands/static_syncer.go
@@ -0,0 +1,135 @@
+// Copyright 2017 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 commands
+
+import (
+ "os"
+ "path/filepath"
+
+ "github.com/fsnotify/fsnotify"
+ "github.com/gohugoio/hugo/helpers"
+ src "github.com/gohugoio/hugo/source"
+ "github.com/spf13/fsync"
+)
+
+type staticSyncer struct {
+ c *commandeer
+ d *src.Dirs
+}
+
+func newStaticSyncer(c *commandeer) (*staticSyncer, error) {
+ dirs, err := src.NewDirs(c.Fs, c.Cfg, c.DepsCfg.Logger)
+ if err != nil {
+ return nil, err
+ }
+
+ return &staticSyncer{c: c, d: dirs}, nil
+}
+
+func (s *staticSyncer) isStatic(path string) bool {
+ return s.d.IsStatic(path)
+}
+
+func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
+ c := s.c
+
+ syncFn := func(dirs *src.Dirs, publishDir string) error {
+ staticSourceFs, err := dirs.CreateStaticFs()
+ if err != nil {
+ return err
+ }
+
+ if staticSourceFs == nil {
+ c.Logger.WARN.Println("No static directories found to sync")
+ return nil
+ }
+
+ syncer := fsync.NewSyncer()
+ syncer.NoTimes = c.Cfg.GetBool("noTimes")
+ syncer.NoChmod = c.Cfg.GetBool("noChmod")
+ syncer.SrcFs = staticSourceFs
+ syncer.DestFs = c.Fs.Destination
+
+ // prevent spamming the log on changes
+ logger := helpers.NewDistinctFeedbackLogger()
+
+ for _, ev := range staticEvents {
+ // Due to our approach of layering both directories and the content's rendered output
+ // into one we can't accurately remove a file not in one of the source directories.
+ // If a file is in the local static dir and also in the theme static dir and we remove
+ // it from one of those locations we expect it to still exist in the destination
+ //
+ // If Hugo generates a file (from the content dir) over a static file
+ // the content generated file should take precedence.
+ //
+ // Because we are now watching and handling individual events it is possible that a static
+ // event that occupies the same path as a content generated file will take precedence
+ // until a regeneration of the content takes places.
+ //
+ // Hugo assumes that these cases are very rare and will permit this bad behavior
+ // The alternative is to track every single file and which pipeline rendered it
+ // and then to handle conflict resolution on every event.
+
+ fromPath := ev.Name
+
+ // If we are here we already know the event took place in a static dir
+ relPath := dirs.MakeStaticPathRelative(fromPath)
+ if relPath == "" {
+ // Not member of this virtual host.
+ continue
+ }
+
+ // Remove || rename is harder and will require an assumption.
+ // Hugo takes the following approach:
+ // If the static file exists in any of the static source directories after this event
+ // Hugo will re-sync it.
+ // If it does not exist in all of the static directories Hugo will remove it.
+ //
+ // This assumes that Hugo has not generated content on top of a static file and then removed
+ // the source of that static file. In this case Hugo will incorrectly remove that file
+ // from the published directory.
+ if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove {
+ if _, err := staticSourceFs.Stat(relPath); os.IsNotExist(err) {
+ // If file doesn't exist in any static dir, remove it
+ toRemove := filepath.Join(publishDir, relPath)
+
+ logger.Println("File no longer exists in static dir, removing", toRemove)
+ _ = c.Fs.Destination.RemoveAll(toRemove)
+ } else if err == nil {
+ // If file still exists, sync it
+ logger.Println("Syncing", relPath, "to", publishDir)
+
+ if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
+ c.Logger.ERROR.Println(err)
+ }
+ } else {
+ c.Logger.ERROR.Println(err)
+ }
+
+ continue
+ }
+
+ // For all other event operations Hugo will sync static.
+ logger.Println("Syncing", relPath, "to", publishDir)
+ if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
+ c.Logger.ERROR.Println(err)
+ }
+ }
+
+ return nil
+ }
+
+ return c.doWithPublishDirs(syncFn)
+
+}