github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/word_filters.go (about)

     1  package common
     2  
     3  import (
     4  	"database/sql"
     5  	"sync/atomic"
     6  
     7  	qgen "github.com/Azareal/Gosora/query_gen"
     8  )
     9  
    10  // TODO: Move some features into methods on this?
    11  type WordFilter struct {
    12  	ID      int
    13  	Find    string
    14  	Replace string
    15  }
    16  type WordFilterDiff struct {
    17  	BeforeFind    string
    18  	BeforeReplace string
    19  	AfterFind     string
    20  	AfterReplace  string
    21  }
    22  
    23  var WordFilters WordFilterStore
    24  
    25  type WordFilterStore interface {
    26  	ReloadAll() error
    27  	GetAll() (filters map[int]*WordFilter, err error)
    28  	Get(id int) (*WordFilter, error)
    29  	Create(find, replace string) (int, error)
    30  	Delete(id int) error
    31  	Update(id int, find, replace string) error
    32  	Length() int
    33  	EstCount() int
    34  	Count() (count int)
    35  }
    36  
    37  type DefaultWordFilterStore struct {
    38  	box atomic.Value // An atomic value holding a WordFilterMap
    39  
    40  	getAll *sql.Stmt
    41  	get    *sql.Stmt
    42  	create *sql.Stmt
    43  	delete *sql.Stmt
    44  	update *sql.Stmt
    45  	count  *sql.Stmt
    46  }
    47  
    48  func NewDefaultWordFilterStore(acc *qgen.Accumulator) (*DefaultWordFilterStore, error) {
    49  	wf := "word_filters"
    50  	store := &DefaultWordFilterStore{
    51  		getAll: acc.Select(wf).Columns("wfid,find,replacement").Prepare(),
    52  		get:    acc.Select(wf).Columns("find,replacement").Where("wfid=?").Prepare(),
    53  		create: acc.Insert(wf).Columns("find,replacement").Fields("?,?").Prepare(),
    54  		delete: acc.Delete(wf).Where("wfid=?").Prepare(),
    55  		update: acc.Update(wf).Set("find=?,replacement=?").Where("wfid=?").Prepare(),
    56  		count:  acc.Count(wf).Prepare(),
    57  	}
    58  	// TODO: Should we initialise this elsewhere?
    59  	if acc.FirstError() == nil {
    60  		acc.RecordError(store.ReloadAll())
    61  	}
    62  	return store, acc.FirstError()
    63  }
    64  
    65  // ReloadAll drops all the items in the memory cache and replaces them with fresh copies from the database
    66  func (s *DefaultWordFilterStore) ReloadAll() error {
    67  	wordFilters := make(map[int]*WordFilter)
    68  	filters, err := s.bypassGetAll()
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	for _, filter := range filters {
    74  		wordFilters[filter.ID] = filter
    75  	}
    76  
    77  	s.box.Store(wordFilters)
    78  	return nil
    79  }
    80  
    81  // ? - Return pointers to word filters intead to save memory? -- A map is a pointer.
    82  func (s *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err error) {
    83  	rows, err := s.getAll.Query()
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	defer rows.Close()
    88  
    89  	for rows.Next() {
    90  		f := &WordFilter{ID: 0}
    91  		err := rows.Scan(&f.ID, &f.Find, &f.Replace)
    92  		if err != nil {
    93  			return filters, err
    94  		}
    95  		filters = append(filters, f)
    96  	}
    97  	return filters, rows.Err()
    98  }
    99  
   100  // GetAll returns all of the word filters in a map. Do note mutate this map (or maps returned from any store not explicitly noted as copies) as multiple threads may be accessing it at once
   101  func (s *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err error) {
   102  	return s.box.Load().(map[int]*WordFilter), nil
   103  }
   104  
   105  func (s *DefaultWordFilterStore) Get(id int) (*WordFilter, error) {
   106  	wf := &WordFilter{ID: id}
   107  	err := s.get.QueryRow(id).Scan(&wf.Find, &wf.Replace)
   108  	return wf, err
   109  }
   110  
   111  // Create adds a new word filter to the database and refreshes the memory cache
   112  func (s *DefaultWordFilterStore) Create(find, replace string) (int, error) {
   113  	res, err := s.create.Exec(find, replace)
   114  	if err != nil {
   115  		return 0, err
   116  	}
   117  	id64, err := res.LastInsertId()
   118  	if err != nil {
   119  		return 0, err
   120  	}
   121  	return int(id64), s.ReloadAll()
   122  }
   123  
   124  // Delete removes a word filter from the database and refreshes the memory cache
   125  func (s *DefaultWordFilterStore) Delete(id int) error {
   126  	_, err := s.delete.Exec(id)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	return s.ReloadAll()
   131  }
   132  
   133  func (s *DefaultWordFilterStore) Update(id int, find, replace string) error {
   134  	_, err := s.update.Exec(find, replace, id)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	return s.ReloadAll()
   139  }
   140  
   141  // Length gets the number of word filters currently in memory, for the DefaultWordFilterStore, this should be all of them
   142  func (s *DefaultWordFilterStore) Length() int {
   143  	return len(s.box.Load().(map[int]*WordFilter))
   144  }
   145  
   146  // EstCount provides the same result as Length(), intended for alternate implementations of WordFilterStore, so that Length is the number of items in cache, if only a subset is held there and EstCount is the total count
   147  func (s *DefaultWordFilterStore) EstCount() int {
   148  	return len(s.box.Load().(map[int]*WordFilter))
   149  }
   150  
   151  // Count gets the total number of word filters directly from the database
   152  func (s *DefaultWordFilterStore) Count() (count int) {
   153  	err := s.count.QueryRow().Scan(&count)
   154  	if err != nil {
   155  		LogError(err)
   156  	}
   157  	return count
   158  }