summaryrefslogtreecommitdiff
path: root/backend.go
diff options
context:
space:
mode:
Diffstat (limited to 'backend.go')
-rw-r--r--backend.go205
1 files changed, 205 insertions, 0 deletions
diff --git a/backend.go b/backend.go
new file mode 100644
index 0000000..0291ffa
--- /dev/null
+++ b/backend.go
@@ -0,0 +1,205 @@
+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
+}