mirror of
https://github.com/ChronosX88/yans.git
synced 2024-11-21 19:32:17 +00:00
Implement wildmat support
This commit is contained in:
parent
dda82b7916
commit
7ae9a7af35
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- :heavy_check_mark: Wildmat support
|
||||||
- :heavy_check_mark: Database (SQLite)
|
- :heavy_check_mark: Database (SQLite)
|
||||||
- :construction: Articles posting
|
- :construction: Articles posting
|
||||||
- :x: Transit mode
|
- :x: Transit mode
|
||||||
|
5
go.mod
5
go.mod
@ -10,4 +10,7 @@ require (
|
|||||||
github.com/pressly/goose/v3 v3.5.0
|
github.com/pressly/goose/v3 v3.5.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/pkg/errors v0.9.1 // indirect
|
require (
|
||||||
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -40,6 +40,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package sqlite
|
package sqlite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"embed"
|
"embed"
|
||||||
"github.com/ChronosX88/yans/internal/config"
|
"github.com/ChronosX88/yans/internal/config"
|
||||||
"github.com/ChronosX88/yans/internal/models"
|
"github.com/ChronosX88/yans/internal/models"
|
||||||
|
"github.com/ChronosX88/yans/internal/utils"
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/mattn/go-sqlite3"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"github.com/pressly/goose/v3"
|
"github.com/pressly/goose/v3"
|
||||||
)
|
)
|
||||||
@ -16,8 +20,19 @@ type SQLiteBackend struct {
|
|||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func regexHelper(re, s string) (bool, error) {
|
||||||
|
return regexp2.MustCompile(re, regexp2.None).MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
func NewSQLiteBackend(cfg config.SQLiteBackendConfig) (*SQLiteBackend, error) {
|
func NewSQLiteBackend(cfg config.SQLiteBackendConfig) (*SQLiteBackend, error) {
|
||||||
db, err := sqlx.Open("sqlite3", cfg.Path)
|
sql.Register("sqlite3_with_regexp",
|
||||||
|
&sqlite3.SQLiteDriver{
|
||||||
|
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
||||||
|
return conn.RegisterFunc("regexp", regexHelper, true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
db, err := sqlx.Open("sqlite3_with_regexp", cfg.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -41,6 +56,19 @@ func (sb *SQLiteBackend) ListGroups() ([]models.Group, error) {
|
|||||||
return groups, sb.db.Select(&groups, "SELECT * FROM groups")
|
return groups, sb.db.Select(&groups, "SELECT * FROM groups")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sb *SQLiteBackend) ListGroupsByPattern(pattern string) ([]models.Group, error) {
|
||||||
|
var groups []models.Group
|
||||||
|
w, err := utils.ParseWildmat(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r, err := w.ToRegex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
@ -8,6 +8,7 @@ const (
|
|||||||
|
|
||||||
type StorageBackend interface {
|
type StorageBackend interface {
|
||||||
ListGroups() ([]models.Group, error)
|
ListGroups() ([]models.Group, error)
|
||||||
|
ListGroupsByPattern(pattern string) ([]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)
|
||||||
|
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ChronosX88/yans/internal/backend"
|
"github.com/ChronosX88/yans/internal/backend"
|
||||||
|
"github.com/ChronosX88/yans/internal/models"
|
||||||
"github.com/ChronosX88/yans/internal/protocol"
|
"github.com/ChronosX88/yans/internal/protocol"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -60,13 +61,20 @@ func (h *Handler) handleList(s *Session, arguments []string, id uint) error {
|
|||||||
fallthrough
|
fallthrough
|
||||||
case "ACTIVE":
|
case "ACTIVE":
|
||||||
{
|
{
|
||||||
groups, err := h.backend.ListGroups()
|
var groups []models.Group
|
||||||
|
var err error
|
||||||
|
if len(arguments) == 2 {
|
||||||
|
groups, err = h.backend.ListGroupsByPattern(arguments[1])
|
||||||
|
} else {
|
||||||
|
groups, err = h.backend.ListGroups()
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sb.Write([]byte(protocol.MessageListOfNewsgroupsFollows + protocol.CRLF))
|
sb.Write([]byte(protocol.MessageListOfNewsgroupsFollows + protocol.CRLF))
|
||||||
for _, v := range groups {
|
for _, v := range groups {
|
||||||
// TODO set high/low mark and posting status to actual values
|
// 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
|
||||||
@ -82,16 +90,24 @@ func (h *Handler) handleList(s *Session, arguments []string, id uint) error {
|
|||||||
}
|
}
|
||||||
sb.Write([]byte(fmt.Sprintf("%s %d %d n"+protocol.CRLF, v.GroupName, highWaterMark, lowWaterMark)))
|
sb.Write([]byte(fmt.Sprintf("%s %d %d n"+protocol.CRLF, v.GroupName, highWaterMark, lowWaterMark)))
|
||||||
} else {
|
} else {
|
||||||
sb.Write([]byte(fmt.Sprintf("%s 0 0 n"+protocol.CRLF, v.GroupName)))
|
sb.Write([]byte(fmt.Sprintf("%s 0 1 n"+protocol.CRLF, v.GroupName)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "NEWSGROUPS":
|
case "NEWSGROUPS":
|
||||||
{
|
{
|
||||||
groups, err := h.backend.ListGroups()
|
var groups []models.Group
|
||||||
|
var err error
|
||||||
|
if len(arguments) == 2 {
|
||||||
|
groups, err = h.backend.ListGroupsByPattern(arguments[1])
|
||||||
|
} else {
|
||||||
|
groups, err = h.backend.ListGroups()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.Write([]byte(protocol.MessageListOfNewsgroupsFollows + protocol.CRLF))
|
||||||
for _, v := range groups {
|
for _, v := range groups {
|
||||||
desc := ""
|
desc := ""
|
||||||
if v.Description == nil {
|
if v.Description == nil {
|
||||||
|
83
internal/utils/wildmat.go
Normal file
83
internal/utils/wildmat.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Wildmat struct {
|
||||||
|
patterns []*WildmatPattern
|
||||||
|
}
|
||||||
|
|
||||||
|
type WildmatPattern struct {
|
||||||
|
negated bool
|
||||||
|
pattern string
|
||||||
|
regex *regexp2.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
func regexpEscape(str string) string {
|
||||||
|
restrictedChars := []string{"(\\", "+", "|", "{", "[", "(", ")", "^", "$", ".", "#"}
|
||||||
|
for _, v := range restrictedChars {
|
||||||
|
str = strings.ReplaceAll(str, v, "\\"+v)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertWildmatToRegex(pat string) (*regexp2.Regexp, error) {
|
||||||
|
regex := ""
|
||||||
|
pat = regexpEscape(pat)
|
||||||
|
patRunes := []rune(pat)
|
||||||
|
for _, v := range patRunes {
|
||||||
|
switch v {
|
||||||
|
case '?':
|
||||||
|
regex += "."
|
||||||
|
case '*':
|
||||||
|
regex += ".?"
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
regex += string(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return regexp2.Compile(regex, regexp2.None)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseWildmat(wildmat string) (*Wildmat, error) {
|
||||||
|
res := &Wildmat{}
|
||||||
|
for _, v := range strings.Split(wildmat, ",") {
|
||||||
|
if len(v) > 0 && v[0] == '!' {
|
||||||
|
r, err := convertWildmatToRegex(v[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.patterns = append(res.patterns, &WildmatPattern{pattern: v[1:], negated: true, regex: r})
|
||||||
|
} else {
|
||||||
|
r, err := convertWildmatToRegex(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.patterns = append(res.patterns, &WildmatPattern{pattern: v, negated: false, regex: r})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Wildmat) ToRegex() (*regexp2.Regexp, error) {
|
||||||
|
res := "(%s)%s"
|
||||||
|
include := ""
|
||||||
|
exclude := ""
|
||||||
|
for _, v := range w.patterns {
|
||||||
|
if v.negated {
|
||||||
|
exclude += fmt.Sprintf("(?!%s)", v.regex.String())
|
||||||
|
} else {
|
||||||
|
if len(include) != 0 {
|
||||||
|
include += fmt.Sprintf("|(%s)", v.regex.String())
|
||||||
|
} else {
|
||||||
|
include += fmt.Sprintf("(%s)", v.regex.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = fmt.Sprintf(res, include, exclude)
|
||||||
|
return regexp2.Compile(res, regexp2.None)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user