265 lignes
8,9 Kio
Go
265 lignes
8,9 Kio
Go
package search
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"time"
|
|
|
|
"github.com/NyaaPantsu/nyaa/config"
|
|
"github.com/NyaaPantsu/nyaa/models"
|
|
"github.com/NyaaPantsu/nyaa/models/torrents"
|
|
"github.com/NyaaPantsu/nyaa/utils/cache"
|
|
"github.com/NyaaPantsu/nyaa/utils/log"
|
|
"github.com/NyaaPantsu/nyaa/utils/search/structs"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
var searchOperator string
|
|
var useTSQuery bool
|
|
|
|
// Configure : initialize search
|
|
func Configure(conf *config.SearchConfig) (err error) {
|
|
useTSQuery = false
|
|
// Postgres needs ILIKE for case-insensitivity
|
|
if models.ORM.Dialect().GetName() == "postgres" {
|
|
searchOperator = "ILIKE ?"
|
|
//useTSQuery = true
|
|
// !!DISABLED!! because this makes search a lot stricter
|
|
// (only matches at word borders)
|
|
} else {
|
|
searchOperator = "LIKE ?"
|
|
}
|
|
return
|
|
}
|
|
|
|
func stringIsASCII(input string) bool {
|
|
for _, char := range input {
|
|
if char > 127 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ByQueryNoUser : search torrents according to request without user
|
|
func ByQueryNoUser(c *gin.Context, pagenum int) (search structs.TorrentParam, tor []models.Torrent, count int, err error) {
|
|
search, tor, count, err = ByQuery(c, pagenum, true, false, false, false)
|
|
return
|
|
}
|
|
|
|
// ByQueryWithUser : search torrents according to request with user
|
|
func ByQueryWithUser(c *gin.Context, pagenum int) (search structs.TorrentParam, tor []models.Torrent, count int, err error) {
|
|
search, tor, count, err = ByQuery(c, pagenum, true, true, false, false)
|
|
return
|
|
}
|
|
|
|
// ByQueryNoCount : search torrents according to request without user and count
|
|
func ByQueryNoCount(c *gin.Context, pagenum int) (search structs.TorrentParam, tor []models.Torrent, err error) {
|
|
search, tor, _, err = ByQuery(c, pagenum, false, false, false, false)
|
|
return
|
|
}
|
|
|
|
// ByQueryDeleted : search deleted torrents according to request with user and count
|
|
func ByQueryDeleted(c *gin.Context, pagenum int) (search structs.TorrentParam, tor []models.Torrent, count int, err error) {
|
|
search, tor, count, err = ByQuery(c, pagenum, true, true, true, false)
|
|
return
|
|
}
|
|
|
|
// ByQueryNoHidden : search torrents and filter those hidden
|
|
func ByQueryNoHidden(c *gin.Context, pagenum int) (search structs.TorrentParam, tor []models.Torrent, count int, err error) {
|
|
search, tor, count, err = ByQuery(c, pagenum, true, false, false, true)
|
|
return
|
|
}
|
|
|
|
// TODO Clean this up
|
|
// Some fields are postgres specific (countAll, withUser)
|
|
// elasticsearch always provide a count to how many hits
|
|
// ES doesn't store users
|
|
// deleted is unused because es doesn't index deleted torrents
|
|
func ByQuery(c *gin.Context, pagenum int, countAll bool, withUser bool, deleted bool, hidden bool) (structs.TorrentParam, []models.Torrent, int, error) {
|
|
var err error
|
|
if config.Get().Search.EnableElasticSearch && models.ElasticSearchClient != nil && !deleted {
|
|
var torrentParam structs.TorrentParam
|
|
torrentParam.FromRequest(c)
|
|
torrentParam.Offset = uint32(pagenum)
|
|
torrentParam.Hidden = hidden
|
|
torrentParam.Full = withUser
|
|
if found, ok := cache.C.Get(torrentParam.Identifier()); ok {
|
|
torrentCache := found.(*structs.TorrentCache)
|
|
return torrentParam, torrentCache.Torrents, torrentCache.Count, nil
|
|
}
|
|
totalHits, tor, err := torrentParam.Find(models.ElasticSearchClient)
|
|
if totalHits > 0 {
|
|
cache.C.Set(torrentParam.Identifier(), &structs.TorrentCache{tor, int(totalHits)}, 5*time.Minute)
|
|
}
|
|
// Convert back to non-json torrents
|
|
return torrentParam, tor, int(totalHits), err
|
|
}
|
|
log.Errorf("Unable to create elasticsearch client: %s", err)
|
|
log.Errorf("Falling back to postgresql query")
|
|
return byQueryPostgres(c, pagenum, countAll, withUser, deleted, hidden)
|
|
}
|
|
|
|
func byQueryPostgres(c *gin.Context, pagenum int, countAll bool, withUser bool, deleted bool, hidden bool) (
|
|
search structs.TorrentParam, tor []models.Torrent, count int, err error,
|
|
) {
|
|
search.FromRequest(c)
|
|
search.Offset = uint32(pagenum)
|
|
search.Hidden = hidden
|
|
search.Deleted = deleted
|
|
search.Full = withUser
|
|
|
|
orderBy := search.Sort.ToDBField()
|
|
if search.Sort == structs.Date {
|
|
search.NotNull = search.Sort.ToDBField() + " IS NOT NULL"
|
|
}
|
|
if found, ok := cache.C.Get(search.Identifier()); ok {
|
|
torrentCache := found.(*structs.TorrentCache)
|
|
tor = torrentCache.Torrents
|
|
count = torrentCache.Count
|
|
return
|
|
}
|
|
|
|
orderBy += " "
|
|
|
|
switch search.Order {
|
|
case true:
|
|
orderBy += "asc"
|
|
if models.ORM.Dialect().GetName() == "postgres" {
|
|
orderBy += " NULLS FIRST"
|
|
}
|
|
case false:
|
|
orderBy += "desc"
|
|
if models.ORM.Dialect().GetName() == "postgres" {
|
|
orderBy += " NULLS LAST"
|
|
}
|
|
}
|
|
|
|
parameters := structs.WhereParams{
|
|
Params: make([]interface{}, 0, 64),
|
|
}
|
|
conditions := make([]string, 0, 64)
|
|
if len(search.Category) > 0 {
|
|
conditionsOr := make([]string, len(search.Category))
|
|
for key, val := range search.Category {
|
|
if val.Main > 0 {
|
|
conditionsOr[key] = "(category = ?"
|
|
parameters.Params = append(parameters.Params, val.Main)
|
|
if val.Sub > 0 {
|
|
conditionsOr[key] += " AND sub_category = ?"
|
|
parameters.Params = append(parameters.Params, val.Sub)
|
|
}
|
|
conditionsOr[key] += ")"
|
|
}
|
|
}
|
|
conditions = append(conditions, strings.Join(conditionsOr, " OR "))
|
|
}
|
|
if len(search.Languages) > 0 {
|
|
langs := ""
|
|
for key, val := range search.Languages {
|
|
langs += val.Code
|
|
if key+1 < len(search.Languages) {
|
|
langs += ","
|
|
}
|
|
}
|
|
conditions = append(conditions, "language "+searchOperator)
|
|
parameters.Params = append(parameters.Params, "%"+langs+"%")
|
|
}
|
|
|
|
if c.Query("userID") != "" {
|
|
if search.UserID > 0 {
|
|
conditions = append(conditions, "uploader = ?")
|
|
parameters.Params = append(parameters.Params, search.UserID)
|
|
if search.Hidden {
|
|
conditions = append(conditions, "hidden = ?")
|
|
parameters.Params = append(parameters.Params, false)
|
|
}
|
|
} else if search.UserID == 0 {
|
|
conditions = append(conditions, "(uploader = ? OR hidden = ?)")
|
|
parameters.Params = append(parameters.Params, search.UserID)
|
|
parameters.Params = append(parameters.Params, true)
|
|
}
|
|
}
|
|
if search.FromID != 0 {
|
|
conditions = append(conditions, "torrent_id > ?")
|
|
parameters.Params = append(parameters.Params, search.FromID)
|
|
}
|
|
if search.FromDate != "" {
|
|
conditions = append(conditions, "date >= ?")
|
|
parameters.Params = append(parameters.Params, search.FromDate)
|
|
}
|
|
if search.ToDate != "" {
|
|
conditions = append(conditions, "date <= ?")
|
|
parameters.Params = append(parameters.Params, search.ToDate)
|
|
}
|
|
if search.Status != 0 {
|
|
if search.Status == structs.FilterRemakes {
|
|
conditions = append(conditions, "status <> ?")
|
|
} else {
|
|
conditions = append(conditions, "status >= ?")
|
|
}
|
|
parameters.Params = append(parameters.Params, strconv.Itoa(int(search.Status)+1))
|
|
}
|
|
if len(search.NotNull) > 0 {
|
|
conditions = append(conditions, search.NotNull)
|
|
}
|
|
if search.MinSize > 0 {
|
|
conditions = append(conditions, "filesize >= ?")
|
|
parameters.Params = append(parameters.Params, uint64(search.MinSize))
|
|
}
|
|
if search.MaxSize > 0 {
|
|
conditions = append(conditions, "filesize <= ?")
|
|
parameters.Params = append(parameters.Params, uint64(search.MaxSize))
|
|
}
|
|
|
|
querySplit := strings.Fields(search.NameLike)
|
|
for _, word := range querySplit {
|
|
firstRune, _ := utf8.DecodeRuneInString(word)
|
|
if len(word) == 1 && unicode.IsPunct(firstRune) {
|
|
// some queries have a single punctuation character
|
|
// which causes a full scan instead of using the index
|
|
// and yields no meaningful results.
|
|
// due to len() == 1 we're just looking at 1-byte/ascii
|
|
// punctuation characters.
|
|
continue
|
|
}
|
|
|
|
if useTSQuery && stringIsASCII(word) {
|
|
conditions = append(conditions, "torrent_name @@ plainto_tsquery(?)")
|
|
parameters.Params = append(parameters.Params, word)
|
|
} else {
|
|
// TODO: possible to make this faster?
|
|
conditions = append(conditions, "torrent_name "+searchOperator)
|
|
parameters.Params = append(parameters.Params, "%"+word+"%")
|
|
}
|
|
}
|
|
|
|
parameters.Conditions = strings.Join(conditions[:], " AND ")
|
|
|
|
log.Infof("SQL query is :: %s\n", parameters.Conditions)
|
|
|
|
if deleted {
|
|
tor, count, err = torrents.FindDeleted(¶meters, orderBy, int(search.Max), int(search.Max*(search.Offset-1)))
|
|
} else if countAll && !withUser {
|
|
tor, count, err = torrents.FindOrderBy(¶meters, orderBy, int(search.Max), int(search.Max*(search.Offset-1)))
|
|
} else if countAll && withUser {
|
|
tor, count, err = torrents.FindWithUserOrderBy(¶meters, orderBy, int(search.Max), int(search.Max*(search.Offset-1)))
|
|
} else {
|
|
tor, err = torrents.FindOrderByNoCount(¶meters, orderBy, int(search.Max), int(search.Max*(search.Offset-1)))
|
|
}
|
|
if len(tor) > 0 {
|
|
cache.C.Set(search.Identifier(), &structs.TorrentCache{tor, count}, 5*time.Minute)
|
|
}
|
|
return
|
|
}
|
|
|
|
// AuthorizedQuery return a seach byquery according to the bool. If false, it doesn't look for hidden torrents, else it looks for every torrents
|
|
func AuthorizedQuery(c *gin.Context, pagenum int, authorized bool) (structs.TorrentParam, []models.Torrent, int, error) {
|
|
if !authorized {
|
|
return ByQuery(c, pagenum, true, true, false, true)
|
|
}
|
|
return ByQuery(c, pagenum, true, true, false, false)
|
|
}
|