gitee.com/mirrors/Hugo-Go@v0.47.1/commands/static_syncer.go (about) 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 := c.hugo.PathSpec.PublishDir 44 // If root, remove the second '/' 45 if publishDir == "//" { 46 publishDir = helpers.FilePathSeparator 47 } 48 49 if sourceFs.PublishFolder != "" { 50 publishDir = filepath.Join(publishDir, sourceFs.PublishFolder) 51 } 52 53 syncer := fsync.NewSyncer() 54 syncer.NoTimes = c.Cfg.GetBool("noTimes") 55 syncer.NoChmod = c.Cfg.GetBool("noChmod") 56 syncer.SrcFs = sourceFs.Fs 57 syncer.DestFs = c.Fs.Destination 58 59 // prevent spamming the log on changes 60 logger := helpers.NewDistinctFeedbackLogger() 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 := sourceFs.MakePathRelative(fromPath) 82 if relPath == "" { 83 // Not member of this virtual host. 84 continue 85 } 86 87 // Remove || rename is harder and will require an assumption. 88 // Hugo takes the following approach: 89 // If the static file exists in any of the static source directories after this event 90 // Hugo will re-sync it. 91 // If it does not exist in all of the static directories Hugo will remove it. 92 // 93 // This assumes that Hugo has not generated content on top of a static file and then removed 94 // the source of that static file. In this case Hugo will incorrectly remove that file 95 // from the published directory. 96 if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove { 97 if _, err := sourceFs.Fs.Stat(relPath); os.IsNotExist(err) { 98 // If file doesn't exist in any static dir, remove it 99 toRemove := filepath.Join(publishDir, relPath) 100 101 logger.Println("File no longer exists in static dir, removing", toRemove) 102 _ = c.Fs.Destination.RemoveAll(toRemove) 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(filepath.Join(publishDir, relPath), relPath); err != nil { 108 c.Logger.ERROR.Println(err) 109 } 110 } else { 111 c.Logger.ERROR.Println(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.ERROR.Println(err) 121 } 122 } 123 124 return 0, nil 125 } 126 127 _, err := c.doWithPublishDirs(syncFn) 128 return err 129 130 }