getwtxt/getwtxt

View on GitHub
registry/query.go

Summary

Maintainability
A
0 mins
Test Coverage
/*
Copyright (c) 2019 Ben Morrison (gbmor)

This file is part of Registry.

Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Registry.  If not, see <https://www.gnu.org/licenses/>.
*/

package registry // import "git.sr.ht/~gbmor/getwtxt/registry"

import (
    "fmt"
    "sort"
    "strings"
    "time"
)

// QueryUser checks the Registry for usernames
// or user URLs that contain the term provided as an argument. Entries
// are returned sorted by the date they were added to the Registry. If
// the argument provided is blank, return all users.
func (registry *Registry) QueryUser(term string) ([]string, error) {
    if registry == nil {
        return nil, fmt.Errorf("can't query empty registry for user")
    }

    term = strings.ToLower(term)
    timekey := NewTimeMap()
    keys := make(TimeSlice, 0)
    var users []string

    registry.Mu.RLock()
    defer registry.Mu.RUnlock()

    for k, v := range registry.Users {
        if registry.Users[k] == nil {
            continue
        }
        v.Mu.RLock()
        if strings.Contains(strings.ToLower(v.Nick), term) || strings.Contains(strings.ToLower(k), term) {
            thetime, err := time.Parse(time.RFC3339, v.Date)
            if err != nil {
                v.Mu.RUnlock()
                continue
            }
            timekey[thetime] = v.Nick + "\t" + k + "\t" + v.Date + "\n"
            keys = append(keys, thetime)
        }
        v.Mu.RUnlock()
    }

    sort.Sort(keys)
    for _, e := range keys {
        users = append(users, timekey[e])
    }

    return users, nil
}

// QueryInStatus returns all statuses in the Registry
// that contain the provided substring (tag, mention URL, etc).
func (registry *Registry) QueryInStatus(substring string) ([]string, error) {
    if substring == "" {
        return nil, fmt.Errorf("cannot query for empty tag")
    } else if registry == nil {
        return nil, fmt.Errorf("can't query statuses of empty registry")
    }

    statusmap := make([]TimeMap, 0)

    registry.Mu.RLock()
    defer registry.Mu.RUnlock()

    for _, v := range registry.Users {
        statusmap = append(statusmap, v.FindInStatus(substring))
    }

    sorted, err := SortByTime(statusmap...)
    if err != nil {
        return nil, err
    }

    return sorted, nil
}

// QueryAllStatuses returns all statuses in the Registry
// as a slice of strings sorted by timestamp.
func (registry *Registry) QueryAllStatuses() ([]string, error) {
    if registry == nil {
        return nil, fmt.Errorf("can't get latest statuses from empty registry")
    }

    statusmap, err := registry.GetStatuses()
    if err != nil {
        return nil, err
    }

    sorted, err := SortByTime(statusmap)
    if err != nil {
        return nil, err
    }

    if sorted == nil {
        sorted = make([]string, 1)
    }

    return sorted, nil
}

// ReduceToPage returns the passed 'page' worth of output.
// One page is twenty items. For example, if 2 is passed,
// it will return data[20:40]. According to the twtxt
// registry specification, queries should accept a "page"
// value.
func ReduceToPage(page int, data []string) []string {
    end := 20 * page
    if end > len(data) || end < 1 {
        end = len(data)
    }

    beg := end - 20
    if beg > len(data)-1 || beg < 0 {
        beg = 0
    }

    return data[beg:end]
}

// FindInStatus takes a user's statuses and looks for a given substring.
// Returns the statuses that include the substring as a TimeMap.
func (userdata *User) FindInStatus(substring string) TimeMap {
    if userdata == nil {
        return nil
    } else if len(substring) > 140 {
        return nil
    }

    substring = strings.ToLower(substring)
    statuses := NewTimeMap()

    userdata.Mu.RLock()
    defer userdata.Mu.RUnlock()

    for k, e := range userdata.Status {
        if _, ok := userdata.Status[k]; !ok {
            continue
        }

        parts := strings.Split(strings.ToLower(e), "\t")
        if strings.Contains(parts[3], substring) {
            statuses[k] = e
        }
    }

    return statuses
}

// SortByTime returns a string slice of the query results,
// sorted by timestamp in descending order (newest first).
func SortByTime(tm ...TimeMap) ([]string, error) {
    if tm == nil {
        return nil, fmt.Errorf("can't sort nil TimeMaps")
    }

    var times = make(TimeSlice, 0)
    var data []string

    for _, e := range tm {
        for k := range e {
            times = append(times, k)
        }
    }

    sort.Sort(times)

    for k := range tm {
        for _, e := range times {
            if _, ok := tm[k][e]; ok {
                data = append(data, tm[k][e])
            }
        }
    }

    return data, nil
}