github.com/core-coin/go-core/v2@v2.1.9/internal/build/download.go (about)

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