github.com/stackb/hugo@v0.47.1/hugofs/hashing_fs.go (about)

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