github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/plugins/usermd/cache.go (about) 1 // Copyright (c) 2020-2021 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 usermd 6 7 import ( 8 "encoding/json" 9 "errors" 10 "fmt" 11 "os" 12 "path/filepath" 13 "strings" 14 15 backend "github.com/decred/politeia/politeiad/backendv2" 16 ) 17 18 const ( 19 // fnUserCache is the filename for the cached userCache data that 20 // is saved to the plugin data dir. 21 fnUserCache = "{userid}.json" 22 ) 23 24 // userCache contains cached user metadata. The userCache JSON is saved to disk 25 // in the user plugin data dir. The user ID is included in the filename. 26 // 27 // The Unvetted and Vetted fields contain the records that have been submitted 28 // by the user. All record tokens are sorted by the timestamp of their most 29 // recent status change from newest to oldest. 30 type userCache struct { 31 Unvetted []string `json:"unvetted"` 32 Vetted []string `json:"vetted"` 33 } 34 35 // userCachePath returns the filepath to the userCache for the specified user. 36 func (p *usermdPlugin) userCachePath(userID string) string { 37 fn := strings.Replace(fnUserCache, "{userid}", userID, 1) 38 return filepath.Join(p.dataDir, fn) 39 } 40 41 // userCacheLocked returns the userCache for the specified user. 42 // 43 // This function must be called WITH the lock held. 44 func (p *usermdPlugin) userCacheLocked(userID string) (*userCache, error) { 45 fp := p.userCachePath(userID) 46 b, err := os.ReadFile(fp) 47 if err != nil { 48 var e *os.PathError 49 if errors.As(err, &e) && !os.IsExist(err) { 50 // File does't exist. Return an empty userCache. 51 return &userCache{ 52 Unvetted: []string{}, 53 Vetted: []string{}, 54 }, nil 55 } 56 } 57 58 var uc userCache 59 err = json.Unmarshal(b, &uc) 60 if err != nil { 61 return nil, err 62 } 63 64 return &uc, nil 65 } 66 67 // userCacheLocked returns the userCache for the specified user. 68 // 69 // This function must be called WITHOUT the lock held. 70 func (p *usermdPlugin) userCache(userID string) (*userCache, error) { 71 p.Lock() 72 defer p.Unlock() 73 74 return p.userCacheLocked(userID) 75 } 76 77 // userCacheSave saves the provided userCache to the plugin data dir. 78 // 79 // This function must be called WITHOUT the lock held. 80 func (p *usermdPlugin) userCacheSave(userID string, uc userCache) error { 81 p.Lock() 82 defer p.Unlock() 83 84 return p.userCacheSaveLocked(userID, uc) 85 } 86 87 // userCacheSaveLocked saves the provided userCache to the plugin data dir. 88 // 89 // This function must be called WITH the lock held. 90 func (p *usermdPlugin) userCacheSaveLocked(userID string, uc userCache) error { 91 b, err := json.Marshal(uc) 92 if err != nil { 93 return err 94 } 95 96 fp := p.userCachePath(userID) 97 return os.WriteFile(fp, b, 0664) 98 } 99 100 // userCacheAddToken adds a token to a user cache. 101 // 102 // This function must be called WITHOUT the lock held. 103 func (p *usermdPlugin) userCacheAddToken(userID string, state backend.StateT, token string) error { 104 p.Lock() 105 defer p.Unlock() 106 107 // Get current user data 108 uc, err := p.userCacheLocked(userID) 109 if err != nil { 110 return err 111 } 112 113 // Add token 114 switch state { 115 case backend.StateUnvetted: 116 uc.Unvetted = append(uc.Unvetted, token) 117 case backend.StateVetted: 118 uc.Vetted = append(uc.Vetted, token) 119 default: 120 return fmt.Errorf("invalid state %v", state) 121 } 122 123 // Save changes 124 err = p.userCacheSaveLocked(userID, *uc) 125 if err != nil { 126 return err 127 } 128 129 log.Debugf("User cache add %v %v %v", backend.States[state], userID, token) 130 131 return nil 132 } 133 134 // userCacheDelToken deletes a token from a user cache. 135 // 136 // This function must be called WITHOUT the lock held. 137 func (p *usermdPlugin) userCacheDelToken(userID string, state backend.StateT, token string) error { 138 p.Lock() 139 defer p.Unlock() 140 141 // Get current user data 142 uc, err := p.userCacheLocked(userID) 143 if err != nil { 144 return err 145 } 146 147 switch state { 148 case backend.StateUnvetted: 149 tokens, err := delToken(uc.Vetted, token) 150 if err != nil { 151 return fmt.Errorf("delToken %v %v: %v", 152 userID, state, err) 153 } 154 uc.Unvetted = tokens 155 case backend.StateVetted: 156 tokens, err := delToken(uc.Vetted, token) 157 if err != nil { 158 return fmt.Errorf("delToken %v %v: %v", 159 userID, state, err) 160 } 161 uc.Vetted = tokens 162 default: 163 return fmt.Errorf("invalid state %v", state) 164 } 165 166 // Save changes 167 err = p.userCacheSaveLocked(userID, *uc) 168 if err != nil { 169 return err 170 } 171 172 log.Debugf("User cache del %v %v %v", backend.States[state], userID, token) 173 174 return nil 175 } 176 177 // userCacheMoveTokenToVetted moves a record token from the unvetted to vetted 178 // list in the userCache. 179 func (p *usermdPlugin) userCacheMoveTokenToVetted(userID string, token string) error { 180 p.Lock() 181 defer p.Unlock() 182 183 // Get current user data 184 uc, err := p.userCacheLocked(userID) 185 if err != nil { 186 return err 187 } 188 189 // Del token from unvetted 190 uc.Unvetted, err = delToken(uc.Unvetted, token) 191 if err != nil { 192 return fmt.Errorf("delToken %v: %v", userID, err) 193 } 194 195 // Add token to vetted 196 uc.Vetted = append(uc.Vetted, token) 197 198 // Save changes 199 err = p.userCacheSaveLocked(userID, *uc) 200 if err != nil { 201 return err 202 } 203 204 log.Debugf("User cache move to vetted %v %v", userID, token) 205 206 return nil 207 } 208 209 // delToken deletes the tokenToDel from the tokens list. An error is returned 210 // if the token is not found. 211 func delToken(tokens []string, tokenToDel string) ([]string, error) { 212 // Find token index 213 var i int 214 var found bool 215 for k, v := range tokens { 216 if v == tokenToDel { 217 i = k 218 found = true 219 break 220 } 221 } 222 if !found { 223 return nil, fmt.Errorf("user token not found %v", tokenToDel) 224 } 225 226 // Del token (linear time) 227 copy(tokens[i:], tokens[i+1:]) // Shift t[i+1:] left one index 228 tokens[len(tokens)-1] = "" // Erase last element 229 tokens = tokens[:len(tokens)-1] // Truncate slice 230 231 return tokens, nil 232 }