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 }