| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- package geodata
- import "strings"
- // Categories returns the database's categories, filtered by a case-insensitive
- // substring of the category code. A non-positive limit returns all of them:
- // the category index is small even for the largest databases, and the panel
- // filters it client-side so typing in the search box costs no requests.
- func (s *Store) Categories(name, query string, offset, limit int) (GeoCategoryPage, error) {
- idx, err := s.index(name)
- if err != nil {
- return GeoCategoryPage{}, err
- }
- matched := idx.categories
- if query = strings.ToLower(strings.TrimSpace(query)); query != "" {
- matched = make([]GeoCategory, 0, len(idx.categories))
- for _, category := range idx.categories {
- if strings.Contains(category.Code, query) {
- matched = append(matched, category)
- }
- }
- }
- page := GeoCategoryPage{Total: len(matched), Items: []GeoCategory{}}
- from, to := categoryBounds(len(matched), offset, limit)
- page.Items = append(page.Items, matched[from:to]...)
- return page, nil
- }
- // Entries returns one page of a category's rules, filtered by a
- // case-insensitive substring of the rule value. The page is scanned out of the
- // file on each call: a category such as geosite's category-ads-all holds well
- // over a hundred thousand rules, and holding those in memory to serve one
- // screenful of them is what the panel cannot afford.
- func (s *Store) Entries(name, code, query string, offset, limit int) (GeoEntryPage, error) {
- idx, err := s.index(name)
- if err != nil {
- return GeoEntryPage{}, err
- }
- code = strings.ToLower(strings.TrimSpace(code))
- if _, ok := idx.byCode[code]; !ok {
- return GeoEntryPage{}, ErrUnknownCategory
- }
- if _, err := s.resolve(name); err != nil {
- return GeoEntryPage{}, err
- }
- if offset < 0 {
- offset = 0
- }
- if limit <= 0 || limit > MaxPageSize {
- limit = MaxPageSize
- }
- spans := idx.spans[code]
- if len(spans) == 0 {
- return GeoEntryPage{}, ErrUnknownCategory
- }
- s.scan.Lock()
- defer s.scan.Unlock()
- records, err := s.recordsLocked(name, code, spans)
- if err != nil {
- return GeoEntryPage{}, err
- }
- return scanEntries(records, idx.kind, code, strings.ToLower(strings.TrimSpace(query)), offset, limit)
- }
- // Lookup reports whether a category exists in the database, without paying for
- // the entry data. The code is matched verbatim apart from case: this backs the
- // routing-token validator, and the core does not forgive a stray space either,
- // so trimming one here would hide the very typo the validator exists to find.
- func (s *Store) Lookup(name, code string) (GeoCategory, error) {
- idx, err := s.index(name)
- if err != nil {
- return GeoCategory{}, err
- }
- category, ok := idx.byCode[strings.ToLower(code)]
- if !ok {
- return GeoCategory{}, ErrUnknownCategory
- }
- return category, nil
- }
- func categoryBounds(total, offset, limit int) (int, int) {
- if limit <= 0 {
- if offset < 0 {
- offset = 0
- }
- if offset > total {
- offset = total
- }
- return offset, total
- }
- return sliceBounds(total, offset, limit)
- }
- func sliceBounds(total, offset, limit int) (int, int) {
- if offset < 0 {
- offset = 0
- }
- if offset > total {
- offset = total
- }
- if limit <= 0 || limit > MaxPageSize {
- limit = MaxPageSize
- }
- to := offset + limit
- if to > total {
- to = total
- }
- return offset, to
- }
- func (s *Store) recordsLocked(name, code string, spans []byteSpan) ([][]byte, error) {
- info, err := s.resolve(name)
- if err != nil {
- return nil, err
- }
- key := fileKey{name: name, size: info.Size(), modTime: info.ModTime().UnixNano()}
- if s.hot.key == key && s.hot.code == code {
- return s.hot.records, nil
- }
- records, err := readSpans(s.dir, name, spans)
- if err != nil {
- return nil, err
- }
- s.hot = hotRecord{key: key, code: code, records: records}
- return records, nil
- }
|