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(×tamp); 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 }