aboutsummaryrefslogtreecommitdiffhomepage
path: root/commands/static_syncer.go
blob: b97c4df7ade2ae7f97f113fdc7436ebce5e7301d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 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/gohugoio/hugo/hugolib/filesystems"

	"github.com/fsnotify/fsnotify"
	"github.com/gohugoio/hugo/helpers"
	"github.com/spf13/fsync"
)

type staticSyncer struct {
	c *commandeer
}

func newStaticSyncer(c *commandeer) (*staticSyncer, error) {
	return &staticSyncer{c: c}, nil
}

func (s *staticSyncer) isStatic(filename string) bool {
	return s.c.hugo().BaseFs.SourceFilesystems.IsStatic(filename)
}

func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
	c := s.c

	syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
		publishDir := helpers.FilePathSeparator

		if sourceFs.PublishFolder != "" {
			publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
		}

		syncer := fsync.NewSyncer()
		syncer.NoTimes = c.Cfg.GetBool("noTimes")
		syncer.NoChmod = c.Cfg.GetBool("noChmod")
		syncer.ChmodFilter = chmodFilter
		syncer.SrcFs = sourceFs.Fs
		syncer.DestFs = c.Fs.PublishDir
		if c.renderStaticToDisk {
			syncer.DestFs = c.Fs.PublishDirStatic
		}

		// prevent spamming the log on changes
		logger := helpers.NewDistinctErrorLogger()

		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

			relPath, found := sourceFs.MakePathRelative(fromPath)

			if !found {
				// 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 := sourceFs.Fs.Stat(relPath); os.IsNotExist(err) {
					// If file doesn't exist in any static dir, remove it
					logger.Println("File no longer exists in static dir, removing", relPath)
					_ = c.Fs.PublishDirStatic.RemoveAll(relPath)

				} else if err == nil {
					// If file still exists, sync it
					logger.Println("Syncing", relPath, "to", publishDir)

					if err := syncer.Sync(relPath, relPath); err != nil {
						c.logger.Errorln(err)
					}
				} else {
					c.logger.Errorln(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.Errorln(err)
			}
		}

		return 0, nil
	}

	_, err := c.doWithPublishDirs(syncFn)
	return err
}