github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugofs/hashing_fs.go (about)

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