hashing_fs.go (2723B)
1 // Copyright 2019 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 hugofs
15
16 import (
17 "crypto/md5"
18 "encoding/hex"
19 "hash"
20 "os"
21
22 "github.com/spf13/afero"
23 )
24
25 var (
26 _ afero.Fs = (*md5HashingFs)(nil)
27 _ FilesystemUnwrapper = (*md5HashingFs)(nil)
28 )
29
30 // FileHashReceiver will receive the filename an the content's MD5 sum on file close.
31 type FileHashReceiver interface {
32 OnFileClose(name, md5sum string)
33 }
34
35 type md5HashingFs struct {
36 afero.Fs
37 hashReceiver FileHashReceiver
38 }
39
40 // NewHashingFs creates a new filesystem that will receive MD5 checksums of
41 // any written file content on Close. Note that this is probably not a good
42 // idea for "full build" situations, but when doing fast render mode, the amount
43 // of files published is low, and it would be really nice to know exactly which
44 // of these files where actually changed.
45 // Note that this will only work for file operations that use the io.Writer
46 // to write content to file, but that is fine for the "publish content" use case.
47 func NewHashingFs(delegate afero.Fs, hashReceiver FileHashReceiver) afero.Fs {
48 return &md5HashingFs{Fs: delegate, hashReceiver: hashReceiver}
49 }
50
51 func (fs *md5HashingFs) UnwrapFilesystem() afero.Fs {
52 return fs.Fs
53 }
54
55 func (fs *md5HashingFs) Create(name string) (afero.File, error) {
56 f, err := fs.Fs.Create(name)
57 if err == nil {
58 f = fs.wrapFile(f)
59 }
60 return f, err
61 }
62
63 func (fs *md5HashingFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
64 f, err := fs.Fs.OpenFile(name, flag, perm)
65 if err == nil && isWrite(flag) {
66 f = fs.wrapFile(f)
67 }
68 return f, err
69 }
70
71 func (fs *md5HashingFs) wrapFile(f afero.File) afero.File {
72 return &hashingFile{File: f, h: md5.New(), hashReceiver: fs.hashReceiver}
73 }
74
75 func (fs *md5HashingFs) Name() string {
76 return "md5HashingFs"
77 }
78
79 type hashingFile struct {
80 hashReceiver FileHashReceiver
81 h hash.Hash
82 afero.File
83 }
84
85 func (h *hashingFile) Write(p []byte) (n int, err error) {
86 n, err = h.File.Write(p)
87 if err != nil {
88 return
89 }
90 return h.h.Write(p)
91 }
92
93 func (h *hashingFile) Close() error {
94 sum := hex.EncodeToString(h.h.Sum(nil))
95 h.hashReceiver.OnFileClose(h.Name(), sum)
96 return h.File.Close()
97 }