From 5c36bf1e71d60823f7d72fc7aba93879d64e7729 Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Sat, 5 Feb 2022 19:23:20 +0300 Subject: [PATCH] Implement NEXT command --- README.md | 4 ++-- internal/backend/sqlite/sqlite.go | 11 +++++++++ internal/backend/storage_backend.go | 1 + internal/protocol/constants.go | 1 + internal/server/handler.go | 37 +++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f610f7..b62f4e2 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,11 @@ - :x: `LIST OVERVIEW.FMT` - :x: `HDR` - :x: `LIST HEADERS` -- :construction: Group and Article Selection +- :heavy_check_mark: Group and Article Selection - :heavy_check_mark: `GROUP` - :heavy_check_mark: `LISTGROUP` - :heavy_check_mark: `LAST` - - :x: `NEXT` + - :heavy_check_mark: `NEXT` - :construction: The LIST Commands - :heavy_check_mark: `LIST ACTIVE` - :heavy_check_mark: `LIST NEWSGROUPS` diff --git a/internal/backend/sqlite/sqlite.go b/internal/backend/sqlite/sqlite.go index e30219c..f15a61a 100644 --- a/internal/backend/sqlite/sqlite.go +++ b/internal/backend/sqlite/sqlite.go @@ -184,6 +184,17 @@ func (sb *SQLiteBackend) GetLastArticleByNum(g *models.Group, a *models.Article) return lastArticle, json.Unmarshal([]byte(a.HeaderRaw), &a.Header) } +func (sb *SQLiteBackend) GetNextArticleByNum(g *models.Group, a *models.Article) (models.Article, error) { + var lastArticle models.Article + if err := sb.db.Get(&lastArticle, "SELECT articles.* FROM articles INNER JOIN articles_to_groups atg on atg.article_id = articles.id WHERE atg.article_number > ? AND atg.group_id = ? LIMIT 1", a.ArticleNumber, g.ID); err != nil { + return lastArticle, err + } + if err := sb.db.Get(&a.ArticleNumber, "SELECT article_number FROM articles_to_groups WHERE article_id = ?", a.ID); err != nil { + return lastArticle, err + } + return lastArticle, json.Unmarshal([]byte(a.HeaderRaw), &a.Header) +} + func (sb *SQLiteBackend) GetNewArticlesSince(timestamp int64) ([]string, error) { var articleIds []string return articleIds, sb.db.Select(&articleIds, "SELECT json_extract(articles.header, '$.Message-Id[0]') FROM articles WHERE created_at > datetime(?, 'unixepoch')", timestamp) diff --git a/internal/backend/storage_backend.go b/internal/backend/storage_backend.go index 176e057..ce9e0fc 100644 --- a/internal/backend/storage_backend.go +++ b/internal/backend/storage_backend.go @@ -20,4 +20,5 @@ type StorageBackend interface { GetArticleNumbers(g *models.Group, low, high int64) ([]int64, error) GetNewArticlesSince(timestamp int64) ([]string, error) GetLastArticleByNum(g *models.Group, a *models.Article) (models.Article, error) + GetNextArticleByNum(g *models.Group, a *models.Article) (models.Article, error) } diff --git a/internal/protocol/constants.go b/internal/protocol/constants.go index d3ae68a..02184c8 100644 --- a/internal/protocol/constants.go +++ b/internal/protocol/constants.go @@ -63,6 +63,7 @@ const ( CommandHelp = "HELP" CommandNewNews = "NEWNEWS" CommandLast = "LAST" + CommandNext = "NEXT" ) const ( diff --git a/internal/server/handler.go b/internal/server/handler.go index af287a6..2747f48 100644 --- a/internal/server/handler.go +++ b/internal/server/handler.go @@ -43,6 +43,7 @@ func NewHandler(b backend.StorageBackend, serverDomain string) *Handler { protocol.CommandHelp: h.handleHelp, protocol.CommandNewNews: h.handleNewNews, protocol.CommandLast: h.handleLast, + protocol.CommandNext: h.handleNext, } h.serverDomain = serverDomain return h @@ -663,6 +664,42 @@ func (h *Handler) handleLast(s *Session, command string, arguments []string, id return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 223, Message: fmt.Sprintf("%d %s retrieved", a.ArticleNumber, a.Header.Get("Message-ID"))}.String()) } +func (h *Handler) handleNext(s *Session, command string, arguments []string, id uint) error { + s.tconn.StartResponse(id) + defer s.tconn.EndResponse(id) + + if len(arguments) != 0 { + return s.tconn.PrintfLine(protocol.ErrSyntaxError.String()) + } + + if s.currentGroup == nil { + return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 412, Message: "no newsgroup selected"}.String()) + } + + if s.currentArticle == nil { + return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 420, Message: "No current article selected"}.String()) + } + + high, err := h.backend.GetGroupHighWaterMark(s.currentGroup) + if err != nil { + return err + } + + if s.currentArticle.ArticleNumber == high { + return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 421, Message: "No next article to retrieve"}.String()) + } + + a, err := h.backend.GetNextArticleByNum(s.currentGroup, s.currentArticle) + if err != nil { + if err == sql.ErrNoRows { + return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 421, Message: "No next article to retrieve"}.String()) + } + return err + } + + return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 223, Message: fmt.Sprintf("%d %s retrieved", a.ArticleNumber, a.Header.Get("Message-ID"))}.String()) +} + func (h *Handler) Handle(s *Session, message string, id uint) error { splittedMessage := strings.Split(message, " ") for i, v := range splittedMessage {