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  }