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 }