github.com/jimmyx0x/go-ethereum@v1.10.28/internal/build/download.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package build
    18  
    19  import (
    20  	"bufio"
    21  	"crypto/sha256"
    22  	"encoding/hex"
    23  	"fmt"
    24  	"io"
    25  	"log"
    26  	"net/http"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  )
    31  
    32  // ChecksumDB keeps file checksums.
    33  type ChecksumDB struct {
    34  	allChecksums []string
    35  }
    36  
    37  // MustLoadChecksums loads a file containing checksums.
    38  func MustLoadChecksums(file string) *ChecksumDB {
    39  	content, err := os.ReadFile(file)
    40  	if err != nil {
    41  		log.Fatal("can't load checksum file: " + err.Error())
    42  	}
    43  	return &ChecksumDB{strings.Split(string(content), "\n")}
    44  }
    45  
    46  // Verify checks whether the given file is valid according to the checksum database.
    47  func (db *ChecksumDB) Verify(path string) error {
    48  	fd, err := os.Open(path)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	defer fd.Close()
    53  
    54  	h := sha256.New()
    55  	if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil {
    56  		return err
    57  	}
    58  	fileHash := hex.EncodeToString(h.Sum(nil))
    59  	if !db.findHash(filepath.Base(path), fileHash) {
    60  		return fmt.Errorf("invalid file hash %s for %s", fileHash, filepath.Base(path))
    61  	}
    62  	return nil
    63  }
    64  
    65  func (db *ChecksumDB) findHash(basename, hash string) bool {
    66  	want := hash + "  " + basename
    67  	for _, line := range db.allChecksums {
    68  		if strings.TrimSpace(line) == want {
    69  			return true
    70  		}
    71  	}
    72  	return false
    73  }
    74  
    75  // DownloadFile downloads a file and verifies its checksum.
    76  func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
    77  	if err := db.Verify(dstPath); err == nil {
    78  		fmt.Printf("%s is up-to-date\n", dstPath)
    79  		return nil
    80  	}
    81  	fmt.Printf("%s is stale\n", dstPath)
    82  	fmt.Printf("downloading from %s\n", url)
    83  
    84  	resp, err := http.Get(url)
    85  	if err != nil {
    86  		return fmt.Errorf("download error: %v", err)
    87  	} else if resp.StatusCode != http.StatusOK {
    88  		return fmt.Errorf("download error: status %d", resp.StatusCode)
    89  	}
    90  	defer resp.Body.Close()
    91  	if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
    92  		return err
    93  	}
    94  	fd, err := os.OpenFile(dstPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	dst := newDownloadWriter(fd, resp.ContentLength)
    99  	_, err = io.Copy(dst, resp.Body)
   100  	dst.Close()
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	return db.Verify(dstPath)
   106  }
   107  
   108  type downloadWriter struct {
   109  	file    *os.File
   110  	dstBuf  *bufio.Writer
   111  	size    int64
   112  	written int64
   113  	lastpct int64
   114  }
   115  
   116  func newDownloadWriter(dst *os.File, size int64) *downloadWriter {
   117  	return &downloadWriter{
   118  		file:   dst,
   119  		dstBuf: bufio.NewWriter(dst),
   120  		size:   size,
   121  	}
   122  }
   123  
   124  func (w *downloadWriter) Write(buf []byte) (int, error) {
   125  	n, err := w.dstBuf.Write(buf)
   126  
   127  	// Report progress.
   128  	w.written += int64(n)
   129  	pct := w.written * 10 / w.size * 10
   130  	if pct != w.lastpct {
   131  		if w.lastpct != 0 {
   132  			fmt.Print("...")
   133  		}
   134  		fmt.Print(pct, "%")
   135  		w.lastpct = pct
   136  	}
   137  	return n, err
   138  }
   139  
   140  func (w *downloadWriter) Close() error {
   141  	if w.lastpct > 0 {
   142  		fmt.Println() // Finish the progress line.
   143  	}
   144  	flushErr := w.dstBuf.Flush()
   145  	closeErr := w.file.Close()
   146  	if flushErr != nil {
   147  		return flushErr
   148  	}
   149  	return closeErr
   150  }