static_syncer.go (4391B)
1 // Copyright 2017 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package commands
15
16 import (
17 "os"
18 "path/filepath"
19
20 "github.com/gohugoio/hugo/hugolib/filesystems"
21
22 "github.com/fsnotify/fsnotify"
23 "github.com/gohugoio/hugo/helpers"
24 "github.com/spf13/fsync"
25 )
26
27 type staticSyncer struct {
28 c *commandeer
29 }
30
31 func newStaticSyncer(c *commandeer) (*staticSyncer, error) {
32 return &staticSyncer{c: c}, nil
33 }
34
35 func (s *staticSyncer) isStatic(filename string) bool {
36 return s.c.hugo().BaseFs.SourceFilesystems.IsStatic(filename)
37 }
38
39 func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
40 c := s.c
41
42 syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
43 publishDir := helpers.FilePathSeparator
44
45 if sourceFs.PublishFolder != "" {
46 publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
47 }
48
49 syncer := fsync.NewSyncer()
50 syncer.NoTimes = c.Cfg.GetBool("noTimes")
51 syncer.NoChmod = c.Cfg.GetBool("noChmod")
52 syncer.ChmodFilter = chmodFilter
53 syncer.SrcFs = sourceFs.Fs
54 syncer.DestFs = c.Fs.PublishDir
55 if c.renderStaticToDisk {
56 syncer.DestFs = c.Fs.PublishDirStatic
57 }
58
59 // prevent spamming the log on changes
60 logger := helpers.NewDistinctErrorLogger()
61
62 for _, ev := range staticEvents {
63 // Due to our approach of layering both directories and the content's rendered output
64 // into one we can't accurately remove a file not in one of the source directories.
65 // If a file is in the local static dir and also in the theme static dir and we remove
66 // it from one of those locations we expect it to still exist in the destination
67 //
68 // If Hugo generates a file (from the content dir) over a static file
69 // the content generated file should take precedence.
70 //
71 // Because we are now watching and handling individual events it is possible that a static
72 // event that occupies the same path as a content generated file will take precedence
73 // until a regeneration of the content takes places.
74 //
75 // Hugo assumes that these cases are very rare and will permit this bad behavior
76 // The alternative is to track every single file and which pipeline rendered it
77 // and then to handle conflict resolution on every event.
78
79 fromPath := ev.Name
80
81 relPath, found := sourceFs.MakePathRelative(fromPath)
82
83 if !found {
84 // Not member of this virtual host.
85 continue
86 }
87
88 // Remove || rename is harder and will require an assumption.
89 // Hugo takes the following approach:
90 // If the static file exists in any of the static source directories after this event
91 // Hugo will re-sync it.
92 // If it does not exist in all of the static directories Hugo will remove it.
93 //
94 // This assumes that Hugo has not generated content on top of a static file and then removed
95 // the source of that static file. In this case Hugo will incorrectly remove that file
96 // from the published directory.
97 if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove {
98 if _, err := sourceFs.Fs.Stat(relPath); os.IsNotExist(err) {
99 // If file doesn't exist in any static dir, remove it
100 logger.Println("File no longer exists in static dir, removing", relPath)
101 _ = c.Fs.PublishDirStatic.RemoveAll(relPath)
102
103 } else if err == nil {
104 // If file still exists, sync it
105 logger.Println("Syncing", relPath, "to", publishDir)
106
107 if err := syncer.Sync(relPath, relPath); err != nil {
108 c.logger.Errorln(err)
109 }
110 } else {
111 c.logger.Errorln(err)
112 }
113
114 continue
115 }
116
117 // For all other event operations Hugo will sync static.
118 logger.Println("Syncing", relPath, "to", publishDir)
119 if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
120 c.logger.Errorln(err)
121 }
122 }
123
124 return 0, nil
125 }
126
127 _, err := c.doWithPublishDirs(syncFn)
128 return err
129 }