metanews/backend.go

206 lines
4.5 KiB
Go

package main
import (
"errors"
"strconv"
"strings"
"github.com/dustin/go-nntp"
nntpserver "github.com/dustin/go-nntp/server"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
)
func NewMetaBackend(logger log.Logger, backends ...nntpserver.Backend) (nntpserver.Backend, error) {
mb := &metaBackend{logger: logger, groups: make(map[string]int)}
for _, b := range backends {
if err := mb.add(b); err != nil {
return nil, err
}
}
return mb, nil
}
type metaBackend struct {
logger log.Logger
backends []nntpserver.Backend
groups map[string]int
}
func (mb metaBackend) debug(keyvals ...any) error { return level.Debug(mb.logger).Log(keyvals...) }
func (mb metaBackend) info(keyvals ...any) error { return level.Info(mb.logger).Log(keyvals...) }
func (mb metaBackend) warn(keyvals ...any) error { return level.Warn(mb.logger).Log(keyvals...) }
func (mb metaBackend) err(keyvals ...any) error { return level.Error(mb.logger).Log(keyvals...) }
func (mb *metaBackend) add(b nntpserver.Backend) error {
grps, err := b.ListGroups(1_000_000)
if err != nil {
return err
}
_ = mb.info("msg", "adding backend", "group_count", len(grps))
i := len(mb.backends)
mb.backends = append(mb.backends, b)
for _, grp := range grps {
mb.groups[grp.Name] = i
}
return nil
}
func (mb metaBackend) ListGroups(max int) ([]*nntp.Group, error) {
var groups []*nntp.Group
for _, b := range mb.backends {
grps, err := b.ListGroups(max - len(groups))
if err != nil {
return nil, err
}
groups = append(groups, grps...)
if len(groups) == max {
break
}
}
_ = mb.info(
"msg", "metaBackend method",
"method", "ListGroups",
"count", len(groups),
)
return groups, nil
}
func (mb metaBackend) GetGroup(name string) (*nntp.Group, error) {
b, err := mb.backendFor(name)
if err != nil {
return nil, err
}
grp, err := b.GetGroup(name)
_ = mb.info(
"msg", "metaBackend method",
"method", "GetGroup",
"name", name,
"err", err,
)
return grp, err
}
func (mb metaBackend) GetArticles(group *nntp.Group, from, to int64) ([]nntpserver.NumberedArticle, error) {
b, err := mb.backendFor(group.Name)
if err != nil {
return nil, err
}
articles, err := b.GetArticles(group, from, to)
_ = mb.info(
"msg", "metaBackend method",
"method", "GetArticles",
"group", group.Name,
"from-to", strconv.Itoa(int(from))+"-"+strconv.Itoa(int(to)),
"count", len(articles),
"err", err,
)
return articles, err
}
func (mb metaBackend) GetArticle(group *nntp.Group, messageID string) (*nntp.Article, error) {
b, err := mb.backendFor(group.Name)
if err != nil {
return nil, err
}
article, err := b.GetArticle(group, messageID)
_ = mb.info(
"msg", "metaBackend method",
"method", "GetArticle",
"group", group.Name,
"messageID", messageID,
"err", err,
)
return article, err
}
func (mb metaBackend) Post(article *nntp.Article) error {
groupNames := strings.Split(article.Header.Get("Newsgroups"), ",")
for i := range groupNames {
groupNames[i] = strings.Trim(groupNames[i], " ")
}
bes, err := mb.backendsFor(groupNames)
if err != nil {
return err
}
var errs []error
for _, b := range bes {
// TODO: need to filter the "Newsgroups" header to only the
// groups relevant to each backend?
errs = append(errs, b.Post(article))
}
err = errors.Join(errs...)
_ = mb.info(
"msg", "metaBackend method",
"method", "Post",
"groups", article.Header.Get("Newsgroups"),
"backends", len(bes),
"err", err,
)
return err
}
func (mb metaBackend) Authorized() bool {
_ = mb.info(
"msg", "metaBackend method",
"method", "Authorized",
)
return true
}
func (mb metaBackend) AllowPost() bool {
_ = mb.info(
"msg", "metaBackend method",
"method", "AllowPost",
)
return true
}
func (mb metaBackend) Authenticate(user, _ string) (nntpserver.Backend, error) {
_ = mb.info(
"msg", "metaBackend method",
"method", "Authenticate",
"user", user,
)
return nil, nil
}
func (mb metaBackend) backendFor(name string) (nntpserver.Backend, error) {
i, ok := mb.groups[name]
if !ok {
return nil, nntpserver.ErrNoSuchGroup
}
return mb.backends[i], nil
}
func (mb metaBackend) backendsFor(names []string) ([]nntpserver.Backend, error) {
tbl := make([]bool, len(mb.backends))
for _, name := range names {
i, ok := mb.groups[name]
if !ok {
return nil, nntpserver.ErrNoSuchGroup
}
tbl[i] = true
}
backends := make([]nntpserver.Backend, 0, len(mb.backends))
for i, y := range tbl {
if y {
backends = append(backends, mb.backends[i])
}
}
return backends, nil
}