github.com/bigzoro/my_simplechain@v0.0.0-20240315012955-8ad0a2a29bb9/internal/build/download.go (about)

     1  // Copyright 2019 The go-simplechain Authors
     2  // This file is part of the go-simplechain library.
     3  //
     4  // The go-simplechain 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-simplechain 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-simplechain 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  	"io/ioutil"
    26  	"log"
    27  	"net/http"
    28  	"os"
    29  	"path/filepath"
    30  	"strings"
    31  )
    32  
    33  // ChecksumDB keeps file checksums.
    34  type ChecksumDB struct {
    35  	allChecksums []string
    36  }
    37  
    38  // MustLoadChecksums loads a file containing checksums.
    39  func MustLoadChecksums(file string) *ChecksumDB {
    40  	content, err := ioutil.ReadFile(file)
    41  	if err != nil {
    42  		log.Fatal("can't load checksum file: " + err.Error())
    43  	}
    44  	return &ChecksumDB{strings.Split(string(content), "\n")}
    45  }
    46  
    47  // Verify checks whether the given file is valid according to the checksum database.
    48  func (db *ChecksumDB) Verify(path string) error {
    49  	fd, err := os.Open(path)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	defer fd.Close()
    54  
    55  	h := sha256.New()
    56  	if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil {
    57  		return err
    58  	}
    59  	fileHash := hex.EncodeToString(h.Sum(nil))
    60  	if !db.findHash(filepath.Base(path), fileHash) {
    61  		return fmt.Errorf("invalid file hash %s", fileHash)
    62  	}
    63  	return nil
    64  }
    65  
    66  func (db *ChecksumDB) findHash(basename, hash string) bool {
    67  	want := hash + "  " + basename
    68  	for _, line := range db.allChecksums {
    69  		if strings.TrimSpace(line) == want {
    70  			return true
    71  		}
    72  	}
    73  	return false
    74  }
    75  
    76  // DownloadFile downloads a file and verifies its checksum.
    77  func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
    78  	if err := db.Verify(dstPath); err == nil {
    79  		fmt.Printf("%s is up-to-date\n", dstPath)
    80  		return nil
    81  	}
    82  	fmt.Printf("%s is stale\n", dstPath)
    83  	fmt.Printf("downloading from %s\n", url)
    84  
    85  	resp, err := http.Get(url)
    86  	if err != nil || resp.StatusCode != http.StatusOK {
    87  		return fmt.Errorf("download error: code %d, err %v", resp.StatusCode, err)
    88  	}
    89  	defer resp.Body.Close()
    90  	if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
    91  		return err
    92  	}
    93  	fd, err := os.OpenFile(dstPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	dst := newDownloadWriter(fd, resp.ContentLength)
    98  	_, err = io.Copy(dst, resp.Body)
    99  	dst.Close()
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	return db.Verify(dstPath)
   105  }
   106  
   107  type downloadWriter struct {
   108  	file    *os.File
   109  	dstBuf  *bufio.Writer
   110  	size    int64
   111  	written int64
   112  	lastpct int64
   113  }
   114  
   115  func newDownloadWriter(dst *os.File, size int64) *downloadWriter {
   116  	return &downloadWriter{
   117  		file:   dst,
   118  		dstBuf: bufio.NewWriter(dst),
   119  		size:   size,
   120  	}
   121  }
   122  
   123  func (w *downloadWriter) Write(buf []byte) (int, error) {
   124  	n, err := w.dstBuf.Write(buf)
   125  
   126  	// Report progress.
   127  	w.written += int64(n)
   128  	pct := w.written * 10 / w.size * 10
   129  	if pct != w.lastpct {
   130  		if w.lastpct != 0 {
   131  			fmt.Print("...")
   132  		}
   133  		fmt.Print(pct, "%")
   134  		w.lastpct = pct
   135  	}
   136  	return n, err
   137  }
   138  
   139  func (w *downloadWriter) Close() error {
   140  	if w.lastpct > 0 {
   141  		fmt.Println() // Finish the progress line.
   142  	}
   143  	flushErr := w.dstBuf.Flush()
   144  	closeErr := w.file.Close()
   145  	if flushErr != nil {
   146  		return flushErr
   147  	}
   148  	return closeErr
   149  }