github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/tslib/establish.go (about)

     1  package tslib
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"path"
    11  	"time"
    12  
    13  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/debug"
    17  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
    18  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    19  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/manifest"
    20  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/sigintTrap"
    21  )
    22  
    23  func EstablishTimestamps(chain string, publisher base.Address) error {
    24  	tsPath := config.PathToTimestamps(chain)
    25  	if file.FileExists(tsPath) {
    26  		return nil
    27  	}
    28  
    29  	database := chain + "-ts"
    30  	cid, err := manifest.ReadUnchainedIndex(chain, publisher, database)
    31  	if err != nil {
    32  		return err
    33  	} else if len(cid) == 0 {
    34  		return fmt.Errorf("no record found in the Unchained Index for database %s from publisher %s", database, publisher.Hex())
    35  	}
    36  
    37  	tmpTsPath := tsPath + ".tmp"
    38  	if err, canceled := downloadTimestamps(chain, database, tmpTsPath, cid); err != nil || canceled {
    39  		os.Remove(tmpTsPath)
    40  		return err
    41  	}
    42  
    43  	os.Remove(tsPath)
    44  	_ = os.Rename(tmpTsPath, tsPath)
    45  
    46  	return err
    47  }
    48  
    49  // downloadTimestamps downloads a CID to a binary file
    50  func downloadTimestamps(chain, database, outputFn, cid string) (error, bool) {
    51  	gatewayUrl := config.GetChain(chain).IpfsGateway
    52  
    53  	url, err := url.Parse(gatewayUrl)
    54  	if err != nil {
    55  		return err, false
    56  	}
    57  	url.Path = path.Join(url.Path, cid)
    58  
    59  	debug.DebugCurlStr(url.String())
    60  
    61  	logger.InfoTable("Chain:", chain)
    62  	logger.InfoTable("Database:", database)
    63  	logger.InfoTable("Gateway:", gatewayUrl)
    64  	logger.InfoTable("URL:", url.String())
    65  	logger.InfoTable("CID:", cid)
    66  	logger.Info(fmt.Sprintf("%sDownloading database %s (%s). This may take a moment...%s", colors.Yellow, database, cid, colors.Off))
    67  
    68  	header, err := http.Head(url.String())
    69  	if err != nil {
    70  		return err, false
    71  	}
    72  	if header.StatusCode != 200 {
    73  		return fmt.Errorf("CID not found: %d status", header.StatusCode), false
    74  	}
    75  
    76  	if header.ContentLength <= file.FileSize(outputFn) {
    77  		// The file on disc is larger than the one we will download which means it has more
    78  		// timestamps in it, so we don't download it.
    79  		return nil, false
    80  	}
    81  
    82  	//Get the response bytes from the url
    83  	response, err := http.Get(url.String())
    84  	if err != nil {
    85  		return err, false
    86  	}
    87  	defer response.Body.Close()
    88  	if response.StatusCode != 200 {
    89  		return fmt.Errorf("CID not found: %d status", header.StatusCode), false
    90  	}
    91  
    92  	logger.Info(fmt.Sprintf("%sDownloading complete %s (%s). Writing file...%s", colors.Yellow, database, cid, colors.Off))
    93  
    94  	userHitCtrlC := false
    95  	ctx, cancel := context.WithCancel(context.Background())
    96  	cleanOnQuit := func() {
    97  		userHitCtrlC = true
    98  		logger.Warn(sigintTrap.TrapMessage)
    99  	}
   100  
   101  	go func() {
   102  		for {
   103  			if file.FileSize(outputFn) >= header.ContentLength {
   104  				break
   105  			}
   106  			pct := int(float64(file.FileSize(outputFn)) / float64(header.ContentLength) * 100)
   107  			msg := colors.Yellow + fmt.Sprintf("Downloading timestamps. Please wait... %d%%", pct) + colors.Off
   108  			if userHitCtrlC {
   109  				msg = colors.Yellow + fmt.Sprintf("Finishing work. please wait... %d%%                                 ", pct) + colors.Off
   110  				cancel()
   111  			}
   112  			logger.Progress(true, msg)
   113  			time.Sleep(500 * time.Microsecond)
   114  		}
   115  	}()
   116  
   117  	trapChannel := sigintTrap.Enable(ctx, cancel, cleanOnQuit)
   118  	defer sigintTrap.Disable(trapChannel)
   119  
   120  	ff, err := os.OpenFile(outputFn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   121  	if err != nil {
   122  		return err, userHitCtrlC
   123  	}
   124  	defer ff.Close()
   125  
   126  	_, err = io.Copy(ff, response.Body)
   127  	return err, userHitCtrlC
   128  }