mirror of
https://github.com/ChronosX88/yans.git
synced 2024-11-21 19:32:17 +00:00
Implement LISTGROUP command
This commit is contained in:
parent
163f2bcba6
commit
10320d169a
@ -27,7 +27,7 @@
|
|||||||
- :x: `HELP`
|
- :x: `HELP`
|
||||||
- :x: `IHAVE`
|
- :x: `IHAVE`
|
||||||
- :x: `LAST`
|
- :x: `LAST`
|
||||||
- :x: `LISTGROUP`
|
- :heavy_check_mark: `LISTGROUP`
|
||||||
- :heavy_check_mark: `NEWGROUPS`
|
- :heavy_check_mark: `NEWGROUPS`
|
||||||
- :x: `NEWNEWS`
|
- :x: `NEWNEWS`
|
||||||
- :x: `NEXT`
|
- :x: `NEXT`
|
||||||
|
@ -72,17 +72,17 @@ func (sb *SQLiteBackend) ListGroupsByPattern(pattern string) ([]models.Group, er
|
|||||||
return groups, sb.db.Select(&groups, "SELECT * FROM groups WHERE group_name REGEXP ?", r.String())
|
return groups, sb.db.Select(&groups, "SELECT * FROM groups WHERE group_name REGEXP ?", r.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *SQLiteBackend) GetArticlesCount(g models.Group) (int, error) {
|
func (sb *SQLiteBackend) GetArticlesCount(g *models.Group) (int, error) {
|
||||||
var count int
|
var count int
|
||||||
return count, sb.db.Get(&count, "SELECT COUNT(*) FROM articles_to_groups WHERE group_id = ?", g.ID)
|
return count, sb.db.Get(&count, "SELECT COUNT(*) FROM articles_to_groups WHERE group_id = ?", g.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *SQLiteBackend) GetGroupHighWaterMark(g models.Group) (int, error) {
|
func (sb *SQLiteBackend) GetGroupHighWaterMark(g *models.Group) (int, error) {
|
||||||
var waterMark int
|
var waterMark int
|
||||||
return waterMark, sb.db.Get(&waterMark, "SELECT article_id FROM articles_to_groups WHERE group_id = ? ORDER BY article_id DESC LIMIT 1", g.ID)
|
return waterMark, sb.db.Get(&waterMark, "SELECT article_id FROM articles_to_groups WHERE group_id = ? ORDER BY article_id DESC LIMIT 1", g.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *SQLiteBackend) GetGroupLowWaterMark(g models.Group) (int, error) {
|
func (sb *SQLiteBackend) GetGroupLowWaterMark(g *models.Group) (int, error) {
|
||||||
var waterMark int
|
var waterMark int
|
||||||
return waterMark, sb.db.Get(&waterMark, "SELECT article_id FROM articles_to_groups WHERE group_id = ? ORDER BY article_id LIMIT 1", g.ID)
|
return waterMark, sb.db.Get(&waterMark, "SELECT article_id FROM articles_to_groups WHERE group_id = ? ORDER BY article_id LIMIT 1", g.ID)
|
||||||
}
|
}
|
||||||
@ -134,3 +134,29 @@ func (sb *SQLiteBackend) GetArticle(messageID string) (models.Article, error) {
|
|||||||
}
|
}
|
||||||
return a, json.Unmarshal([]byte(a.HeaderRaw), &a.Header)
|
return a, json.Unmarshal([]byte(a.HeaderRaw), &a.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sb *SQLiteBackend) GetArticleNumbers(g *models.Group, low, high int64) ([]int64, error) {
|
||||||
|
var numbers []int64
|
||||||
|
|
||||||
|
if high == 0 && low == 0 {
|
||||||
|
if err := sb.db.Select(&numbers, "SELECT article_id FROM articles_to_groups WHERE group_id = ?", g.ID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if low == -1 && high != 0 {
|
||||||
|
if err := sb.db.Select(&numbers, "SELECT article_id FROM articles_to_groups WHERE group_id = ? AND article_id = ?", g.ID, high); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if low != 0 && high == -1 {
|
||||||
|
if err := sb.db.Select(&numbers, "SELECT article_id FROM articles_to_groups WHERE group_id = ? AND article_id > ?", g.ID, low); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if low == -1 && high == -1 {
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
if err := sb.db.Select(&numbers, "SELECT article_id FROM articles_to_groups WHERE group_id = ? AND article_id > ? AND article_id < ?", g.ID, low, high); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numbers, nil
|
||||||
|
}
|
||||||
|
@ -11,9 +11,10 @@ type StorageBackend interface {
|
|||||||
ListGroupsByPattern(pattern string) ([]models.Group, error)
|
ListGroupsByPattern(pattern string) ([]models.Group, error)
|
||||||
GetGroup(groupName string) (models.Group, error)
|
GetGroup(groupName string) (models.Group, error)
|
||||||
GetNewGroupsSince(timestamp int64) ([]models.Group, error)
|
GetNewGroupsSince(timestamp int64) ([]models.Group, error)
|
||||||
GetArticlesCount(g models.Group) (int, error)
|
GetArticlesCount(g *models.Group) (int, error)
|
||||||
GetGroupLowWaterMark(g models.Group) (int, error)
|
GetGroupLowWaterMark(g *models.Group) (int, error)
|
||||||
GetGroupHighWaterMark(g models.Group) (int, error)
|
GetGroupHighWaterMark(g *models.Group) (int, error)
|
||||||
SaveArticle(article models.Article, groups []string) error
|
SaveArticle(article models.Article, groups []string) error
|
||||||
GetArticle(messageID string) (models.Article, error)
|
GetArticle(messageID string) (models.Article, error)
|
||||||
|
GetArticleNumbers(g *models.Group, low, high int64) ([]int64, error)
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ const (
|
|||||||
CommandGroup = "GROUP"
|
CommandGroup = "GROUP"
|
||||||
CommandNewGroups = "NEWGROUPS"
|
CommandNewGroups = "NEWGROUPS"
|
||||||
CommandPost = "POST"
|
CommandPost = "POST"
|
||||||
|
CommandListGroup = "LISTGROUP"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -74,7 +75,6 @@ const (
|
|||||||
MessageNNTPServiceReadyPostingProhibited = "201 YANS NNTP Service Ready, posting prohibited"
|
MessageNNTPServiceReadyPostingProhibited = "201 YANS NNTP Service Ready, posting prohibited"
|
||||||
MessageReaderModePostingProhibited = "201 Reader mode, posting prohibited"
|
MessageReaderModePostingProhibited = "201 Reader mode, posting prohibited"
|
||||||
MessageNNTPServiceExitsNormally = "205 NNTP Service exits normally, bye!"
|
MessageNNTPServiceExitsNormally = "205 NNTP Service exits normally, bye!"
|
||||||
MessageUnknownCommand = "500 Unknown command"
|
|
||||||
MessageErrorHappened = "403 Failed to process command:"
|
MessageErrorHappened = "403 Failed to process command:"
|
||||||
MessageListOfNewsgroupsFollows = "215 list of newsgroups follows"
|
MessageListOfNewsgroupsFollows = "215 list of newsgroups follows"
|
||||||
MessageNoSuchGroup = "411 No such newsgroup"
|
MessageNoSuchGroup = "411 No such newsgroup"
|
||||||
|
@ -7,9 +7,11 @@ import (
|
|||||||
"github.com/ChronosX88/yans/internal/backend"
|
"github.com/ChronosX88/yans/internal/backend"
|
||||||
"github.com/ChronosX88/yans/internal/models"
|
"github.com/ChronosX88/yans/internal/models"
|
||||||
"github.com/ChronosX88/yans/internal/protocol"
|
"github.com/ChronosX88/yans/internal/protocol"
|
||||||
|
"github.com/ChronosX88/yans/internal/utils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"io"
|
"io"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -32,6 +34,7 @@ func NewHandler(b backend.StorageBackend, serverDomain string) *Handler {
|
|||||||
protocol.CommandGroup: h.handleGroup,
|
protocol.CommandGroup: h.handleGroup,
|
||||||
protocol.CommandNewGroups: h.handleNewGroups,
|
protocol.CommandNewGroups: h.handleNewGroups,
|
||||||
protocol.CommandPost: h.handlePost,
|
protocol.CommandPost: h.handlePost,
|
||||||
|
protocol.CommandListGroup: h.handleListgroup,
|
||||||
}
|
}
|
||||||
h.serverDomain = serverDomain
|
h.serverDomain = serverDomain
|
||||||
return h
|
return h
|
||||||
@ -85,16 +88,16 @@ func (h *Handler) handleList(s *Session, arguments []string, id uint) error {
|
|||||||
sb.Write([]byte(protocol.MessageListOfNewsgroupsFollows + protocol.CRLF))
|
sb.Write([]byte(protocol.MessageListOfNewsgroupsFollows + protocol.CRLF))
|
||||||
for _, v := range groups {
|
for _, v := range groups {
|
||||||
// TODO set actual post permission status
|
// TODO set actual post permission status
|
||||||
c, err := h.backend.GetArticlesCount(v)
|
c, err := h.backend.GetArticlesCount(&v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c > 0 {
|
if c > 0 {
|
||||||
highWaterMark, err := h.backend.GetGroupHighWaterMark(v)
|
highWaterMark, err := h.backend.GetGroupHighWaterMark(&v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lowWaterMark, err := h.backend.GetGroupLowWaterMark(v)
|
lowWaterMark, err := h.backend.GetGroupLowWaterMark(&v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -169,15 +172,15 @@ func (h *Handler) handleGroup(s *Session, arguments []string, id uint) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
highWaterMark, err := h.backend.GetGroupHighWaterMark(g)
|
highWaterMark, err := h.backend.GetGroupHighWaterMark(&g)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lowWaterMark, err := h.backend.GetGroupLowWaterMark(g)
|
lowWaterMark, err := h.backend.GetGroupLowWaterMark(&g)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
articlesCount, err := h.backend.GetArticlesCount(g)
|
articlesCount, err := h.backend.GetArticlesCount(&g)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -230,16 +233,16 @@ func (h *Handler) handleNewGroups(s *Session, arguments []string, id uint) error
|
|||||||
dw.Write([]byte(protocol.NNTPResponse{Code: 231, Message: "list of new newsgroups follows"}.String() + protocol.CRLF))
|
dw.Write([]byte(protocol.NNTPResponse{Code: 231, Message: "list of new newsgroups follows"}.String() + protocol.CRLF))
|
||||||
for _, v := range g {
|
for _, v := range g {
|
||||||
// TODO set actual post permission status
|
// TODO set actual post permission status
|
||||||
c, err := h.backend.GetArticlesCount(v)
|
c, err := h.backend.GetArticlesCount(&v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c > 0 {
|
if c > 0 {
|
||||||
highWaterMark, err := h.backend.GetGroupHighWaterMark(v)
|
highWaterMark, err := h.backend.GetGroupHighWaterMark(&v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lowWaterMark, err := h.backend.GetGroupLowWaterMark(v)
|
lowWaterMark, err := h.backend.GetGroupLowWaterMark(&v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -321,6 +324,66 @@ func (h *Handler) handlePost(s *Session, arguments []string, id uint) error {
|
|||||||
return s.tconn.PrintfLine(protocol.MessageArticleReceived)
|
return s.tconn.PrintfLine(protocol.MessageArticleReceived)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleListgroup(s *Session, arguments []string, id uint) error {
|
||||||
|
s.tconn.StartResponse(id)
|
||||||
|
defer s.tconn.EndResponse(id)
|
||||||
|
|
||||||
|
currentGroup := s.currentGroup
|
||||||
|
var low, high int64
|
||||||
|
if len(arguments) == 1 {
|
||||||
|
g, err := h.backend.GetGroup(arguments[0])
|
||||||
|
if err != nil {
|
||||||
|
return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 411, Message: "No such newsgroup"}.String())
|
||||||
|
}
|
||||||
|
currentGroup = &g
|
||||||
|
} else if len(arguments) == 2 {
|
||||||
|
g, err := h.backend.GetGroup(arguments[0])
|
||||||
|
if err != nil {
|
||||||
|
return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 411, Message: "No such newsgroup"}.String())
|
||||||
|
}
|
||||||
|
currentGroup = &g
|
||||||
|
|
||||||
|
low, high, err = utils.ParseRange(arguments[1])
|
||||||
|
if err != nil {
|
||||||
|
low = 0
|
||||||
|
high = 0
|
||||||
|
}
|
||||||
|
if high != -1 && low > high {
|
||||||
|
low = -1
|
||||||
|
high = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentGroup == nil {
|
||||||
|
return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 412, Message: "No newsgroup selected"}.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
highWaterMark, err := h.backend.GetGroupHighWaterMark(currentGroup)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lowWaterMark, err := h.backend.GetGroupLowWaterMark(currentGroup)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
articlesCount, err := h.backend.GetArticlesCount(currentGroup)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nums, err := h.backend.GetArticleNumbers(currentGroup, low, high)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dw := s.tconn.DotWriter()
|
||||||
|
dw.Write([]byte(protocol.NNTPResponse{Code: 211, Message: fmt.Sprintf("%d %d %d %s list follows%s", articlesCount, lowWaterMark, highWaterMark, currentGroup.GroupName, protocol.CRLF)}.String()))
|
||||||
|
for _, v := range nums {
|
||||||
|
dw.Write([]byte(strconv.FormatInt(v, 10) + protocol.CRLF))
|
||||||
|
}
|
||||||
|
return dw.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) Handle(s *Session, message string, id uint) error {
|
func (h *Handler) Handle(s *Session, message string, id uint) error {
|
||||||
splittedMessage := strings.Split(message, " ")
|
splittedMessage := strings.Split(message, " ")
|
||||||
for i, v := range splittedMessage {
|
for i, v := range splittedMessage {
|
||||||
@ -331,7 +394,7 @@ func (h *Handler) Handle(s *Session, message string, id uint) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
s.tconn.StartResponse(id)
|
s.tconn.StartResponse(id)
|
||||||
defer s.tconn.EndResponse(id)
|
defer s.tconn.EndResponse(id)
|
||||||
return s.tconn.PrintfLine(protocol.MessageUnknownCommand)
|
return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 500, Message: "Unknown command"}.String())
|
||||||
}
|
}
|
||||||
return handler(s, splittedMessage[1:], id)
|
return handler(s, splittedMessage[1:], id)
|
||||||
}
|
}
|
||||||
|
27
internal/utils/range.go
Normal file
27
internal/utils/range.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseRange(spec string) (int64, int64, error) {
|
||||||
|
if spec == "" {
|
||||||
|
return 0, 0, fmt.Errorf("no range specified")
|
||||||
|
}
|
||||||
|
parts := strings.Split(spec, "-")
|
||||||
|
if len(parts) == 1 {
|
||||||
|
h, err := strconv.ParseInt(parts[0], 10, 64)
|
||||||
|
return -1, h, err
|
||||||
|
}
|
||||||
|
l, err := strconv.ParseInt(parts[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
if parts[1] == "" {
|
||||||
|
return l, -1, nil
|
||||||
|
}
|
||||||
|
h, err := strconv.ParseInt(parts[1], 10, 64)
|
||||||
|
return l, h, err
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user