go-nntp-plusplus/examples/server/exampleserver.go

292 lines
5.7 KiB
Go

package main
import (
"bytes"
"container/ring"
"io"
"log"
"net"
"net/textproto"
"sort"
"strconv"
"strings"
"git.maride.cc/maride/go-nntp-plusplus"
"git.maride.cc/maride/go-nntp-plusplus/server"
)
const maxArticles = 100
type articleRef struct {
msgid string
num int64
}
type groupStorage struct {
group *nntp.Group
// article refs
articles *ring.Ring
}
type articleStorage struct {
headers textproto.MIMEHeader
body string
refcount int
}
type testBackendType struct {
// group name -> group storage
groups map[string]*groupStorage
// message ID -> article
articles map[string]*articleStorage
}
var testBackend = testBackendType{
groups: map[string]*groupStorage{},
articles: map[string]*articleStorage{},
}
func init() {
testBackend.groups["alt.test"] = &groupStorage{
group: &nntp.Group{
Name: "alt.test",
Description: "A test.",
Posting: nntp.PostingNotPermitted},
articles: ring.New(maxArticles),
}
testBackend.groups["misc.test"] = &groupStorage{
group: &nntp.Group{
Name: "misc.test",
Description: "More testing.",
Posting: nntp.PostingPermitted},
articles: ring.New(maxArticles),
}
}
func (tb *testBackendType) ListGroups(max int) ([]*nntp.Group, error) {
rv := []*nntp.Group{}
for _, g := range tb.groups {
rv = append(rv, g.group)
}
return rv, nil
}
func (tb *testBackendType) GetGroup(name string) (*nntp.Group, error) {
var group *nntp.Group
for _, g := range tb.groups {
if g.group.Name == name {
group = g.group
break
}
}
if group == nil {
return nil, nntpserver.ErrNoSuchGroup
}
return group, nil
}
func mkArticle(a *articleStorage) *nntp.Article {
return &nntp.Article{
Header: a.headers,
Body: strings.NewReader(a.body),
Bytes: len(a.body),
Lines: strings.Count(a.body, "\n"),
}
}
func findInRing(in *ring.Ring, f func(r interface{}) bool) *ring.Ring {
if f(in.Value) {
return in
}
for p := in.Next(); p != in; p = p.Next() {
if f(p.Value) {
return p
}
}
return nil
}
func (tb *testBackendType) GetArticle(group *nntp.Group, id string) (*nntp.Article, error) {
msgID := id
var a *articleStorage
if intid, err := strconv.ParseInt(id, 10, 64); err == nil {
msgID = ""
// by int ID. Gotta go find it.
if groupStorage, ok := tb.groups[group.Name]; ok {
r := findInRing(groupStorage.articles, func(v interface{}) bool {
if v != nil {
log.Printf("Looking at %v", v)
}
if aref, ok := v.(articleRef); ok && aref.num == intid {
return true
}
return false
})
if aref, ok := r.Value.(articleRef); ok {
msgID = aref.msgid
}
}
}
a = tb.articles[msgID]
if a == nil {
return nil, nntpserver.ErrInvalidMessageID
}
return mkArticle(a), nil
}
// Because I suck at ring, I'm going to just post-sort these.
type nalist []nntpserver.NumberedArticle
func (n nalist) Len() int {
return len(n)
}
func (n nalist) Less(i, j int) bool {
return n[i].Num < n[j].Num
}
func (n nalist) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func (tb *testBackendType) GetArticles(group *nntp.Group,
from, to int64) ([]nntpserver.NumberedArticle, error) {
gs, ok := tb.groups[group.Name]
if !ok {
return nil, nntpserver.ErrNoSuchGroup
}
log.Printf("Getting articles from %d to %d", from, to)
rv := []nntpserver.NumberedArticle{}
gs.articles.Do(func(v interface{}) {
if v != nil {
if aref, ok := v.(articleRef); ok {
if aref.num >= from && aref.num <= to {
a, ok := tb.articles[aref.msgid]
if ok {
article := mkArticle(a)
rv = append(rv,
nntpserver.NumberedArticle{
Num: aref.num,
Article: article})
}
}
}
}
})
sort.Sort(nalist(rv))
return rv, nil
}
func (tb *testBackendType) AllowPost() bool {
return true
}
func (tb *testBackendType) decr(msgid string) {
if a, ok := tb.articles[msgid]; ok {
a.refcount--
if a.refcount == 0 {
log.Printf("Getting rid of %v", msgid)
delete(tb.articles, msgid)
}
}
}
func (tb *testBackendType) Post(article *nntp.Article) error {
log.Printf("Got headers: %#v", article.Header)
b := []byte{}
buf := bytes.NewBuffer(b)
n, err := io.Copy(buf, article.Body)
if err != nil {
return err
}
log.Printf("Read %d bytes of body", n)
a := articleStorage{
headers: article.Header,
body: buf.String(),
refcount: 0,
}
msgID := a.headers.Get("Message-Id")
if _, ok := tb.articles[msgID]; ok {
return nntpserver.ErrPostingFailed
}
for _, g := range article.Header["Newsgroups"] {
if g, ok := tb.groups[g]; ok {
g.articles = g.articles.Next()
if g.articles.Value != nil {
aref := g.articles.Value.(articleRef)
tb.decr(aref.msgid)
}
if g.articles.Value != nil || g.group.Low == 0 {
g.group.Low++
}
g.group.High++
g.articles.Value = articleRef{
msgID,
g.group.High,
}
log.Printf("Placed %v", g.articles.Value)
a.refcount++
g.group.Count = int64(g.articles.Len())
log.Printf("Stored %v in %v", msgID, g.group.Name)
}
}
if a.refcount > 0 {
tb.articles[msgID] = &a
} else {
return nntpserver.ErrPostingFailed
}
return nil
}
func (tb *testBackendType) Authorized() bool {
return true
}
func (tb *testBackendType) Authenticate(user, pass string) (nntpserver.Backend, error) {
return nil, nntpserver.ErrAuthRejected
}
func maybefatal(err error, f string, a ...interface{}) {
if err != nil {
log.Fatalf(f, a...)
}
}
func main() {
a, err := net.ResolveTCPAddr("tcp", ":1119")
maybefatal(err, "Error resolving listener: %v", err)
l, err := net.ListenTCP("tcp", a)
maybefatal(err, "Error setting up listener: %v", err)
defer l.Close()
s := nntpserver.NewServer(&testBackend)
for {
c, err := l.AcceptTCP()
maybefatal(err, "Error accepting connection: %v", err)
go s.Process(c)
}
}