github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/internal/build/download.go (about)

     1  // Copyright 2021 The adkgo Authors
     2  // This file is part of the adkgo library (adapted for adkgo from go--ethereum v1.10.8).
     3  //
     4  // the adkgo 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 adkgo 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 adkgo 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 {
    87  		return fmt.Errorf("download error: %v", err)
    88  	} else if resp.StatusCode != http.StatusOK {
    89  		return fmt.Errorf("download error: status %d", resp.StatusCode)
    90  	}
    91  	defer resp.Body.Close()
    92  	if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
    93  		return err
    94  	}
    95  	fd, err := os.OpenFile(dstPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
    96  	if err != nil {
    97  		return err
    98  	}
    99  	dst := newDownloadWriter(fd, resp.ContentLength)
   100  	_, err = io.Copy(dst, resp.Body)
   101  	dst.Close()
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	return db.Verify(dstPath)
   107  }
   108  
   109  type downloadWriter struct {
   110  	file    *os.File
   111  	dstBuf  *bufio.Writer
   112  	size    int64
   113  	written int64
   114  	lastpct int64
   115  }
   116  
   117  func newDownloadWriter(dst *os.File, size int64) *downloadWriter {
   118  	return &downloadWriter{
   119  		file:   dst,
   120  		dstBuf: bufio.NewWriter(dst),
   121  		size:   size,
   122  	}
   123  }
   124  
   125  func (w *downloadWriter) Write(buf []byte) (int, error) {
   126  	n, err := w.dstBuf.Write(buf)
   127  
   128  	// Report progress.
   129  	w.written += int64(n)
   130  	pct := w.written * 10 / w.size * 10
   131  	if pct != w.lastpct {
   132  		if w.lastpct != 0 {
   133  			fmt.Print("...")
   134  		}
   135  		fmt.Print(pct, "%")
   136  		w.lastpct = pct
   137  	}
   138  	return n, err
   139  }
   140  
   141  func (w *downloadWriter) Close() error {
   142  	if w.lastpct > 0 {
   143  		fmt.Println() // Finish the progress line.
   144  	}
   145  	flushErr := w.dstBuf.Flush()
   146  	closeErr := w.file.Close()
   147  	if flushErr != nil {
   148  		return flushErr
   149  	}
   150  	return closeErr
   151  }