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

     1  package cache
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"strconv"
     9  
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/version"
    11  )
    12  
    13  // Here we define a struct that we want to write or read from the cache
    14  type ExampleBlock struct {
    15  	BlockNumber uint64
    16  	Date        string
    17  	Name        string
    18  	Timestamp   uint64
    19  }
    20  
    21  // We now make ExampleBlock implement CacheMarshaler interface, by
    22  // defining MarshalCache. MarshalCache job is to serialize the data more
    23  // complex than uint8, uint64, ..., int64, etc.
    24  func (e *ExampleBlock) MarshalCache(writer io.Writer) (err error) {
    25  	// WriteValue serializes its second argument. It works for
    26  	// fixed-size numeric types, slices and structs that implement
    27  	// CacheMarshaler interface (and slices of such types).
    28  	if err = WriteValue(writer, e.BlockNumber); err != nil {
    29  		return err
    30  	}
    31  	if err = WriteValue(writer, e.Name); err != nil {
    32  		return err
    33  	}
    34  	if err = WriteValue(writer, e.Timestamp); err != nil {
    35  		return err
    36  	}
    37  
    38  	return
    39  }
    40  
    41  var minimalVersion version.Version
    42  
    43  func init() {
    44  	minimalVersion = version.NewVersion("GHC-TrueBlocks//0.10.0-beta")
    45  }
    46  
    47  // Now we make ExampleBlock implement CacheUnmarshaler interface, which is like
    48  // CacheMarshaler, but for reading
    49  func (e *ExampleBlock) UnmarshalCache(itemVersion uint64, reader io.Reader) (err error) {
    50  	// We will see where version is extracted in Example() below
    51  	if itemVersion < minimalVersion.Uint64() {
    52  		// In real life we would read back level
    53  		return errors.New("unsupported version")
    54  	}
    55  
    56  	if err = ReadValue(reader, &e.BlockNumber, itemVersion); err != nil {
    57  		return err
    58  	}
    59  	if err = ReadValue(reader, &e.Name, itemVersion); err != nil {
    60  		return err
    61  	}
    62  	if err = ReadValue(reader, &e.Timestamp, itemVersion); err != nil {
    63  		return err
    64  	}
    65  	return
    66  }
    67  
    68  // We we also implement all the methods needed for ExampleBlock to become Locator.
    69  // Locator interface job is to locate an item in cache.
    70  
    71  func (e *ExampleBlock) CacheLocations() (string, string, string) {
    72  	return "example", strconv.FormatUint(e.BlockNumber, 10), "bin"
    73  }
    74  
    75  func Example() {
    76  	// This example shows the usage of Locator interface, which is higher level
    77  	// abstraction over Cache(un)Marshaler
    78  
    79  	// Let's create something to store
    80  	block := &ExampleBlock{
    81  		BlockNumber: 4436721,
    82  		Name:        "Test block",
    83  		Timestamp:   1688667358,
    84  	}
    85  
    86  	// We made `BlockNumber` our cache ID
    87  	_, id, _ := block.CacheLocations()
    88  	fmt.Println(id)
    89  
    90  	// we will store it in memory cache (if StoreOptions is nil, then file system
    91  	// cache is used)
    92  	cacheStore, err := NewStore(&StoreOptions{Location: MemoryCache})
    93  	if err != nil {
    94  		panic(err)
    95  	}
    96  
    97  	if err := cacheStore.Write(block, nil); err != nil {
    98  		panic(err)
    99  	}
   100  
   101  	readFromCache := &ExampleBlock{
   102  		BlockNumber: 4436721,
   103  	}
   104  	if err := cacheStore.Read(readFromCache, nil); err != nil {
   105  		panic(err)
   106  	}
   107  
   108  	fmt.Printf("%+v\n", readFromCache)
   109  
   110  	// Output:
   111  	// 4436721
   112  	// &{BlockNumber:4436721 Date: Name:Test block Timestamp:1688667358}
   113  }
   114  
   115  func ExampleMarshaler() {
   116  	// We start with a simple example of storing uint64 serialized to binary
   117  
   118  	// First, we create a fake file (we need something that implements io.ReadWriter)
   119  	timestampsCacheFile := new(bytes.Buffer)
   120  	// Now we point our cache item to use this "file". This is temporary, it will be
   121  	// handled by some higher abstraction (CacheLayout perhaps?)
   122  	timestampsCacheItem := NewItem(timestampsCacheFile)
   123  	// We encode (serialize to binary) the value. The cache header is written automaticaly
   124  	// by Encode()
   125  	if err := timestampsCacheItem.Encode(uint64(1688667358)); err != nil {
   126  		panic(err)
   127  	}
   128  
   129  	// Let's read the value
   130  	var timestamp uint64
   131  	// Decode checks cache header automaticaly
   132  	if err := timestampsCacheItem.Decode(&timestamp); err != nil {
   133  		panic(err)
   134  	}
   135  	fmt.Println(timestamp)
   136  
   137  	// Now we will write a structure which implements Cache(un)Marshaler
   138  	block := &ExampleBlock{
   139  		BlockNumber: 17636511,
   140  		Date:        "2023-07-06",
   141  		Name:        "Nice Block",
   142  		Timestamp:   1688667358,
   143  	}
   144  
   145  	// Again, we create a fake file and cache item linked to it. And again, it'll be
   146  	// more automated later
   147  	blocksCacheFile := new(bytes.Buffer)
   148  	blocksCacheItem := NewItem(blocksCacheFile)
   149  	// We call Encode() to serialize to  Cache header is written by default
   150  	if err := blocksCacheItem.Encode(block); err != nil {
   151  		panic(err)
   152  	}
   153  
   154  	// Now we can read the struct back
   155  	decodedBlock := &ExampleBlock{}
   156  	if err := blocksCacheItem.Decode(decodedBlock); err != nil {
   157  		panic(err)
   158  	}
   159  	fmt.Printf("%+v\n", decodedBlock)
   160  
   161  	// Output:
   162  	// 1688667358
   163  	// &{BlockNumber:17636511 Date: Name:Nice Block Timestamp:1688667358}
   164  }