github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/plugins/ticketvote/summaries.go (about) 1 // Copyright (c) 2022 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package ticketvote 6 7 import ( 8 "encoding/json" 9 "errors" 10 "strings" 11 12 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins" 13 "github.com/decred/politeia/politeiad/plugins/ticketvote" 14 "github.com/decred/politeia/util" 15 ) 16 17 var ( 18 // errSummaryNotFound is returned when a vote summary is not found in the 19 // cache for a record. 20 errSummaryNotFound = errors.New("summary not found") 21 ) 22 23 // summariesClient provides an API for interacting with the vote summaries 24 // cache. The data is saved to the TstoreClient provided plugin cache. 25 // 26 // Vote summaries are only cached once the vote has finished and additional 27 // updates to the record's vote data are no longer possible. 28 // 29 // tstore does not provide plugins with a sql transaction that can be used 30 // to execute multiple database requests atomically during cache updates. 31 // Concurrent access must be controlled locally using a mutex, but since the 32 // summaries are only written once and never updated, we don't need to worry 33 // about concurrency issues. 34 type summariesClient struct { 35 tstore plugins.TstoreClient 36 } 37 38 // newSummariesClient returns a new summariesClient. 39 func newSummariesClient(tstore plugins.TstoreClient) *summariesClient { 40 return &summariesClient{ 41 tstore: tstore, 42 } 43 } 44 45 // Save saves a vote summary to the cache. 46 func (c *summariesClient) Save(token string, s ticketvote.SummaryReply) error { 47 key, err := buildSummaryKey(token) 48 if err != nil { 49 return err 50 } 51 b, err := json.Marshal(s) 52 if err != nil { 53 return err 54 } 55 err = c.tstore.CachePut(map[string][]byte{key: b}, false) 56 if err != nil { 57 return err 58 } 59 60 log.Debugf("Vote summary saved for %v", token) 61 62 return nil 63 } 64 65 // Del deletes a vote summary from the cache. 66 // 67 // An error is not returned if an entry is not found in the cache for the 68 // provided token. 69 func (c *summariesClient) Del(token string) error { 70 key, err := buildSummaryKey(token) 71 if err != nil { 72 return err 73 } 74 return c.tstore.CacheDel([]string{key}) 75 } 76 77 // Get retrieves a vote summary from the cache. 78 // 79 // An errSummaryNotFound is returned if a vote summary is not found in the 80 // cache for the record. 81 func (c *summariesClient) Get(token string) (*ticketvote.SummaryReply, error) { 82 key, err := buildSummaryKey(token) 83 if err != nil { 84 return nil, err 85 } 86 entries, err := c.tstore.CacheGet([]string{key}) 87 if err != nil { 88 return nil, err 89 } 90 b, ok := entries[key] 91 if !ok { 92 return nil, errSummaryNotFound 93 } 94 var sr ticketvote.SummaryReply 95 err = json.Unmarshal(b, &sr) 96 if err != nil { 97 return nil, err 98 } 99 return &sr, nil 100 } 101 102 const ( 103 // summaryKey is the key-value store key for an entry in the vote summaries 104 // cache. Each vote summary entry is saved as an individual database entry. 105 // The "{shorttoken}" is replaced with the record's short token. 106 summaryKey = "summary-{shorttoken}" 107 ) 108 109 // buildSummaryKey returns the key-value store key for an entry in the vote 110 // summaries cache. 111 func buildSummaryKey(token string) (string, error) { 112 s, err := util.ShortTokenString(token) 113 if err != nil { 114 return "", err 115 } 116 return strings.Replace(summaryKey, "{shorttoken}", s, 1), nil 117 }