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 }