From 679c515ad123aa2d4c5ebf7521f463c497a88086 Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Sat, 5 Feb 2022 19:19:43 +0300 Subject: [PATCH] Implement LAST command --- README.md | 2 +- 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, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4120b78..7f610f7 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ - :construction: Group and Article Selection - :heavy_check_mark: `GROUP` - :heavy_check_mark: `LISTGROUP` - - :x: `LAST` + - :heavy_check_mark: `LAST` - :x: `NEXT` - :construction: The LIST Commands - :heavy_check_mark: `LIST ACTIVE` diff --git a/internal/backend/sqlite/sqlite.go b/internal/backend/sqlite/sqlite.go index e879e0e..e30219c 100644 --- a/internal/backend/sqlite/sqlite.go +++ b/internal/backend/sqlite/sqlite.go @@ -173,6 +173,17 @@ func (sb *SQLiteBackend) GetArticleNumbers(g *models.Group, low, high int64) ([] return numbers, nil } +func (sb *SQLiteBackend) GetLastArticleByNum(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 19ea3a2..176e057 100644 --- a/internal/backend/storage_backend.go +++ b/internal/backend/storage_backend.go @@ -19,4 +19,5 @@ type StorageBackend interface { GetArticleByNumber(g *models.Group, num int) (models.Article, error) GetArticleNumbers(g *models.Group, low, high int64) ([]int64, error) GetNewArticlesSince(timestamp int64) ([]string, error) + GetLastArticleByNum(g *models.Group, a *models.Article) (models.Article, error) } diff --git a/internal/protocol/constants.go b/internal/protocol/constants.go index 6e37c32..d3ae68a 100644 --- a/internal/protocol/constants.go +++ b/internal/protocol/constants.go @@ -62,6 +62,7 @@ const ( CommandStat = "STAT" CommandHelp = "HELP" CommandNewNews = "NEWNEWS" + CommandLast = "LAST" ) const ( diff --git a/internal/server/handler.go b/internal/server/handler.go index e0d5b5f..af287a6 100644 --- a/internal/server/handler.go +++ b/internal/server/handler.go @@ -42,6 +42,7 @@ func NewHandler(b backend.StorageBackend, serverDomain string) *Handler { protocol.CommandStat: h.handleArticle, protocol.CommandHelp: h.handleHelp, protocol.CommandNewNews: h.handleNewNews, + protocol.CommandLast: h.handleLast, } h.serverDomain = serverDomain return h @@ -626,6 +627,42 @@ func (h *Handler) handleNewNews(s *Session, command string, arguments []string, return dw.Close() } +func (h *Handler) handleLast(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()) + } + + low, err := h.backend.GetGroupLowWaterMark(s.currentGroup) + if err != nil { + return err + } + + if s.currentArticle.ArticleNumber == low { + return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 422, Message: "No previous article to retrieve"}.String()) + } + + a, err := h.backend.GetLastArticleByNum(s.currentGroup, s.currentArticle) + if err != nil { + if err == sql.ErrNoRows { + return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 422, Message: "No previous 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 {