Commit Diff


commit - f00d51cf8cc1331170bf6c84bfa23bbe7e8f0e98
commit + de59b21200a5aa169b2dc799bca29b8f75c8e231
blob - d606a708d25244cbad788ab9b159bb707cc35ea1 (mode 644)
blob + /dev/null
--- examples/couchserver/couchserver.go
+++ /dev/null
@@ -1,344 +0,0 @@
-package main
-
-import (
-	"bytes"
-	"encoding/base64"
-	"encoding/json"
-	"flag"
-	"fmt"
-	"io"
-	"log"
-	"log/syslog"
-	"net"
-	"net/textproto"
-	"net/url"
-	"strconv"
-	"strings"
-	"sync"
-	"sync/atomic"
-	"time"
-
-	"github.com/dustin/go-nntp"
-	"github.com/dustin/go-nntp/server"
-
-	"github.com/dustin/go-couch"
-)
-
-var groupCacheTimeout = flag.Int("groupTimeout", 300,
-	"Time (in seconds), group cache is valid")
-var optimisticPost = flag.Bool("optimistic", false,
-	"Optimistically return success on store before storing")
-var useSyslog = flag.Bool("syslog", false,
-	"Log to syslog")
-
-type groupRow struct {
-	Group string        `json:"key"`
-	Value []interface{} `json:"value"`
-}
-
-type groupResults struct {
-	Rows []groupRow
-}
-
-type attachment struct {
-	Type string `json:"content-type"`
-	Data []byte `json:"data"`
-}
-
-func removeSpace(r rune) rune {
-	if r == ' ' || r == '\n' || r == '\r' {
-		return -1
-	}
-	return r
-}
-
-func (a *attachment) MarshalJSON() ([]byte, error) {
-	m := map[string]string{
-		"content_type": a.Type,
-		"data":         strings.Map(removeSpace, base64.StdEncoding.EncodeToString(a.Data)),
-	}
-	return json.Marshal(m)
-}
-
-type article struct {
-	MsgID       string                 `json:"_id"`
-	DocType     string                 `json:"type"`
-	Headers     map[string][]string    `json:"headers"`
-	Bytes       int                    `json:"bytes"`
-	Lines       int                    `json:"lines"`
-	Nums        map[string]int64       `json:"nums"`
-	Attachments map[string]*attachment `json:"_attachments"`
-	Added       time.Time              `json:"added"`
-}
-
-type articleResults struct {
-	Rows []struct {
-		Key     []interface{} `json:"key"`
-		Article article       `json:"doc"`
-	}
-}
-
-type couchBackend struct {
-	db        *couch.Database
-	groups    map[string]*nntp.Group
-	grouplock sync.Mutex
-}
-
-func (cb *couchBackend) clearGroups() {
-	cb.grouplock.Lock()
-	defer cb.grouplock.Unlock()
-
-	log.Printf("Dumping group cache")
-	cb.groups = nil
-}
-
-func (cb *couchBackend) fetchGroups() error {
-	cb.grouplock.Lock()
-	defer cb.grouplock.Unlock()
-
-	if cb.groups != nil {
-		return nil
-	}
-
-	log.Printf("Filling group cache")
-
-	results := groupResults{}
-	err := cb.db.Query("_design/groups/_view/active", map[string]interface{}{
-		"group": true,
-	}, &results)
-	if err != nil {
-		return err
-	}
-	cb.groups = make(map[string]*nntp.Group)
-	for _, gr := range results.Rows {
-		if gr.Value[0].(string) != "" {
-			group := nntp.Group{
-				Name:        gr.Group,
-				Description: gr.Value[0].(string),
-				Count:       int64(gr.Value[1].(float64)),
-				Low:         int64(gr.Value[2].(float64)),
-				High:        int64(gr.Value[3].(float64)),
-				Posting:     nntp.PostingPermitted,
-			}
-			cb.groups[group.Name] = &group
-		}
-	}
-
-	go func() {
-		time.Sleep(time.Duration(*groupCacheTimeout) * time.Second)
-		cb.clearGroups()
-	}()
-
-	return nil
-}
-
-func (cb *couchBackend) ListGroups(max int) ([]*nntp.Group, error) {
-	if cb.groups == nil {
-		if err := cb.fetchGroups(); err != nil {
-			return nil, err
-		}
-	}
-	rv := make([]*nntp.Group, 0, len(cb.groups))
-	for _, g := range cb.groups {
-		rv = append(rv, g)
-	}
-	return rv, nil
-}
-
-func (cb *couchBackend) GetGroup(name string) (*nntp.Group, error) {
-	if cb.groups == nil {
-		if err := cb.fetchGroups(); err != nil {
-			return nil, err
-		}
-	}
-	g, exists := cb.groups[name]
-	if !exists {
-		return nil, nntpserver.ErrNoSuchGroup
-	}
-	return g, nil
-}
-
-func (cb *couchBackend) mkArticle(ar article) *nntp.Article {
-	url := fmt.Sprintf("%s/%s/article", cb.db.DBURL(), cleanupID(ar.MsgID, true))
-	return &nntp.Article{
-		Header: textproto.MIMEHeader(ar.Headers),
-		Body:   &lazyOpener{url, nil, nil},
-		Bytes:  ar.Bytes,
-		Lines:  ar.Lines,
-	}
-}
-
-func (cb *couchBackend) GetArticle(group *nntp.Group, id string) (*nntp.Article, error) {
-	var ar article
-	if intid, err := strconv.ParseInt(id, 10, 64); err == nil {
-		results := articleResults{}
-		cb.db.Query("_design/articles/_view/list", map[string]interface{}{
-			"include_docs": true,
-			"reduce":       false,
-			"key":          []interface{}{group.Name, intid},
-		}, &results)
-
-		if len(results.Rows) != 1 {
-			return nil, nntpserver.ErrInvalidArticleNumber
-		}
-
-		ar = results.Rows[0].Article
-	} else {
-		err := cb.db.Retrieve(cleanupID(id, false), &ar)
-		if err != nil {
-			return nil, nntpserver.ErrInvalidMessageID
-		}
-	}
-
-	return cb.mkArticle(ar), nil
-}
-
-func (cb *couchBackend) GetArticles(group *nntp.Group,
-	from, to int64) ([]nntpserver.NumberedArticle, error) {
-
-	rv := make([]nntpserver.NumberedArticle, 0, 100)
-
-	results := articleResults{}
-	cb.db.Query("_design/articles/_view/list", map[string]interface{}{
-		"include_docs": true,
-		"reduce":       false,
-		"start_key":    []interface{}{group.Name, from},
-		"end_key":      []interface{}{group.Name, to},
-	}, &results)
-
-	for _, r := range results.Rows {
-		rv = append(rv, nntpserver.NumberedArticle{
-			Num:     int64(r.Key[1].(float64)),
-			Article: cb.mkArticle(r.Article),
-		})
-	}
-
-	return rv, nil
-}
-
-func (cb *couchBackend) AllowPost() bool {
-	return true
-}
-
-func cleanupID(msgid string, escapedAt bool) string {
-	s := strings.TrimFunc(msgid, func(r rune) bool {
-		return r == ' ' || r == '<' || r == '>'
-	})
-	qe := url.QueryEscape(s)
-	if escapedAt {
-		return qe
-	}
-	return strings.Replace(qe, "%40", "@", -1)
-}
-
-func (cb *couchBackend) Post(art *nntp.Article) error {
-	a := article{
-		DocType:     "article",
-		Headers:     map[string][]string(art.Header),
-		Nums:        make(map[string]int64),
-		MsgID:       cleanupID(art.Header.Get("Message-Id"), false),
-		Attachments: make(map[string]*attachment),
-		Added:       time.Now(),
-	}
-
-	b := []byte{}
-	buf := bytes.NewBuffer(b)
-	n, err := io.Copy(buf, art.Body)
-	if err != nil {
-		return err
-	}
-	log.Printf("Read %d bytes of body", n)
-
-	b = buf.Bytes()
-	a.Bytes = len(b)
-	a.Lines = bytes.Count(b, []byte{'\n'})
-
-	a.Attachments["article"] = &attachment{"text/plain", b}
-
-	for _, g := range strings.Split(art.Header.Get("Newsgroups"), ",") {
-		g = strings.TrimSpace(g)
-		group, err := cb.GetGroup(g)
-		if err == nil {
-			a.Nums[g] = atomic.AddInt64(&group.High, 1)
-			atomic.AddInt64(&group.Count, 1)
-		} else {
-			log.Printf("Error getting group %q:  %v", g, err)
-		}
-	}
-
-	if len(a.Nums) == 0 {
-		log.Printf("Found no matching groups in %v",
-			art.Header["Newsgroups"])
-		return nntpserver.ErrPostingFailed
-	}
-
-	if *optimisticPost {
-		go func() {
-			_, _, err = cb.db.Insert(&a)
-			if err != nil {
-				log.Printf("error optimistically posting article: %v", err)
-			}
-		}()
-	} else {
-		_, _, err = cb.db.Insert(&a)
-		if err != nil {
-			log.Printf("error posting article: %v", err)
-			return nntpserver.ErrPostingFailed
-		}
-	}
-
-	return nil
-}
-
-func (cb *couchBackend) Authorized() bool {
-	return true
-}
-
-func (cb *couchBackend) 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() {
-	couchURL := flag.String("couch", "http://localhost:5984/news",
-		"Couch DB.")
-
-	flag.Parse()
-
-	if *useSyslog {
-		sl, err := syslog.New(syslog.LOG_INFO, "nntpd")
-		if err != nil {
-			log.Fatalf("Error initializing syslog: %v", err)
-		}
-		log.SetOutput(sl)
-		log.SetFlags(0)
-	}
-
-	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()
-
-	db, err := couch.Connect(*couchURL)
-	maybefatal(err, "Can't connect to the couch: %v", err)
-	err = ensureViews(&db)
-	maybefatal(err, "Error setting up views: %v", err)
-
-	backend := couchBackend{
-		db: &db,
-	}
-
-	s := nntpserver.NewServer(&backend)
-
-	for {
-		c, err := l.AcceptTCP()
-		maybefatal(err, "Error accepting connection: %v", err)
-		go s.Process(c)
-	}
-}
blob - 86da305cfcfa3d3637e329d51112998dd7a19366 (mode 644)
blob + /dev/null
--- examples/couchserver/encoder_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"testing"
-)
-
-func TestJSONMarshalling(t *testing.T) {
-	a := attachment{
-		"application/octet-stream",
-		[]byte("some bytes"),
-	}
-	b, err := json.Marshal(&a)
-	if err != nil {
-		t.Fatalf("Error marshalling attachment: %v", err)
-	}
-	t.Logf("Marshalled to %v", string(b))
-}
blob - d839bb686c590a66d3c8f79166920285e99e01a5 (mode 644)
blob + /dev/null
--- examples/couchserver/groupcreator/creator.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package main
-
-import (
-	"bufio"
-	"flag"
-	"io"
-	"log"
-	"os"
-	"strings"
-	"sync"
-
-	"github.com/dustin/go-couch"
-)
-
-var wg sync.WaitGroup
-
-type agroup struct {
-	Type        string `json:"type"`
-	Name        string `json:"_id"`
-	Description string `json:"description"`
-}
-
-func process(db *couch.Database, line string) {
-	defer wg.Done()
-	parts := strings.SplitN(strings.TrimSpace(line), " ", 2)
-	if len(parts) != 2 {
-		log.Printf("Error parsing %v", line)
-		return
-	}
-	log.Printf("Processing %#v", parts)
-	g := agroup{
-		Type:        "group",
-		Name:        parts[0],
-		Description: parts[1],
-	}
-	_, _, err := db.Insert(g)
-	if err != nil {
-		log.Printf("Error saving %#v: %v", g, err)
-	}
-}
-
-func main() {
-
-	couchURL := flag.String("couch", "http://localhost:5984/news",
-		"Couch DB.")
-	flag.Parse()
-
-	db, err := couch.Connect(*couchURL)
-	if err != nil {
-		log.Fatalf("Can't connect to couch: %v", err)
-	}
-
-	br := bufio.NewReader(os.Stdin)
-	for {
-		line, err := br.ReadString('\n')
-		if err == io.EOF {
-			break
-		}
-		if err != nil {
-			log.Fatalf("Error reading line: %v", err)
-		}
-
-		wg.Add(1)
-		go process(&db, line)
-	}
-
-	wg.Wait()
-}
blob - 7175ee798dbd54b70bc817fef65cd8b4c13901db (mode 644)
blob + /dev/null
--- examples/couchserver/lazyopen.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package main
-
-import (
-	"errors"
-	"io"
-	"io/ioutil"
-	"net/http"
-)
-
-type lazyOpener struct {
-	url  string
-	data []byte
-	err  error
-}
-
-func (l *lazyOpener) init() {
-	res, err := http.Get(l.url)
-	l.err = err
-	if err == nil {
-		defer res.Body.Close()
-		if res.StatusCode == 200 {
-			l.data, l.err = ioutil.ReadAll(res.Body)
-		} else {
-			l.err = errors.New(res.Status)
-		}
-	}
-}
-
-func (l *lazyOpener) Read(p []byte) (n int, err error) {
-	if l.data == nil && l.err == nil {
-		l.init()
-	}
-	if l.err != nil {
-		return 0, err
-	}
-	if len(l.data) == 0 {
-		return 0, io.EOF
-	}
-	copied := copy(p, l.data)
-	l.data = l.data[copied:]
-	return copied, nil
-}
blob - 6f007215f3a724c804786198deafb0e49b293ed8 (mode 644)
blob + /dev/null
--- examples/couchserver/views.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package main
-
-import (
-	"errors"
-	"fmt"
-	"log"
-	"net/http"
-	"strings"
-
-	"github.com/dustin/go-couch"
-)
-
-const groupsjson = `{
-   "_id": "_design/groups",
-   "language": "javascript",
-   "views": {
-       "discovered": {
-           "map": "function(doc) {\n  if (doc.type === \"group\") {\n    emit(doc._id, [doc.description, 0, 0, 0]);\n  } else if (doc.type === \"article\") {\n    var groups = doc.headers[\"Newsgroups\"][0].split(\",\")\n    for (var i = 0; i < groups.length; i++) {\n        var g = groups[i].replace(/\\s+/g, '');\n        emit(g, [\"\", 1, doc.nums[g], doc.nums[g]]);\n    }\n  }\n}\n",
-           "reduce": "function (key, values) {\n    var result = [\"\", 0, 0, 0];\n\n    values.forEach(function(p) {\n        if (p[0].length > result[0].length) {\n            result[0] = p[0];\n        }\n        result[1] += p[1];\n\tresult[2] = Math.min(result[2], p[2]);\n\tresult[3] = Math.max(result[3], p[3]);\n        // Dumb special case\n        if (result[2] === 0 && result[1] != 0) {\n          result[2] = 1;\n        }\n    });\n\n    return result;\n}"
-       },
-       "active": {
-           "map": "function(doc) {\n  if (doc.type === \"group\") {\n    emit(doc._id, [doc.description, 0, 0, 0]);\n  } else if (doc.type === \"article\") {\n    for (var g in doc.nums) {\n        emit(g, [\"\", 1, doc.nums[g], doc.nums[g]]);\n    }\n  }\n}\n",
-           "reduce": "function (key, values) {\n    var result = [\"\", 0, 0, 0];\n\n    values.forEach(function(p) {\n        if (p[0].length > result[0].length) {\n            result[0] = p[0];\n        }\n        result[1] += p[1];\n\tresult[2] = Math.min(result[2], p[2]);\n\tresult[3] = Math.max(result[3], p[3]);\n        // Dumb special case\n        if (result[2] === 0 && result[1] != 0) {\n          result[2] = 1;\n        }\n    });\n\n    return result;\n}"
-       }
-   }
-}`
-
-const articlesjson = `{
-   "_id": "_design/articles",
-   "language": "javascript",
-   "views": {
-       "list": {
-           "map": "function(doc) {\n  if (doc.type === \"article\") {\n    for (var g in doc.nums) {\n        emit([g, doc.nums[g]], null);\n    }\n  }\n}\n",
-           "reduce": "_count"
-       }
-   }
-}`
-
-func viewUpdateOK(i int) bool {
-	return i == 200 || i == 409
-}
-
-func updateView(db *couch.Database, viewdata string) error {
-	r, err := http.Post(db.DBURL(), "application/json", strings.NewReader(viewdata))
-	if r == nil {
-		defer r.Body.Close()
-	} else {
-		return err
-	}
-	if !viewUpdateOK(r.StatusCode) {
-		return fmt.Errorf("error updating view:  %v", r.Status)
-	}
-	return nil
-}
-
-func ensureViews(db *couch.Database) error {
-	errg := updateView(db, groupsjson)
-	if errg != nil {
-		log.Printf("Error creating groups view %v", errg)
-	}
-
-	erra := updateView(db, articlesjson)
-	if erra != nil {
-		log.Printf("Error creating articles view %v", erra)
-	}
-
-	if erra != nil || errg != nil {
-		return errors.New("error making views")
-	}
-
-	return nil
-}
blob - d362d8e50a008d020dc4a07c259cd0c16c4781c0
blob + 958970b38ad3ef4603fdd61e0e977eb5a14bd3f8
--- go.mod
+++ go.mod
@@ -1,8 +1,3 @@
 module github.com/dustin/go-nntp
 
 go 1.16
-
-require (
-	github.com/dustin/go-couch v0.0.0-20160816170231-8251128dab73
-	github.com/dustin/httputil v0.0.0-20170305193905-c47743f54f89 // indirect
-)
blob - 7add7f12513a6f787e71ae7387e487b7b3af1428
blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
--- go.sum
+++ go.sum
@@ -1,4 +0,0 @@
-github.com/dustin/go-couch v0.0.0-20160816170231-8251128dab73 h1:YKyWSyEhJ3DYKgSpjOXpQgpxD3N+1EfIanJZj1ZEhpM=
-github.com/dustin/go-couch v0.0.0-20160816170231-8251128dab73/go.mod h1:WG/TWzFd/MRvOZ4jjna3FQ+K8AKhb2jOw4S2JMw9VKI=
-github.com/dustin/httputil v0.0.0-20170305193905-c47743f54f89 h1:A740DRjmFFdm3+GeYVfs4QN/QMOAbMw8KdsZMDhUCjQ=
-github.com/dustin/httputil v0.0.0-20170305193905-c47743f54f89/go.mod h1:ZoDWdnxro8Kesk3zrCNOHNFWtajFPSnDMjVEjGjQu/0=