Albirew/nyaa-pantsu
Albirew
/
nyaa-pantsu
Archivé
1
0
Bifurcation 0
Ce dépôt a été archivé le 2022-05-07. Vous pouvez voir ses fichiers ou le cloner, mais pas ouvrir de ticket ou de demandes d'ajout, ni soumettre de changements.
nyaa-pantsu/utils/search/search.go

262 lignes
8.8 KiB
Go
Brut Vue normale Historique

package search
2017-05-06 12:41:48 +02:00
import (
"strconv"
"strings"
2017-05-08 15:50:18 +02:00
"unicode"
"unicode/utf8"
"time"
2017-05-17 07:58:40 +02:00
"github.com/NyaaPantsu/nyaa/config"
2017-06-29 13:15:23 +02:00
"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"
First batch of changes for the refactor (#1078) * First batch of changes for the refactor Added the support of gin in routes and other services/utils Begining implementation of JetHTML * Remove os folder * Move scrapers to own repo * Second batch of changes All .jet.html are the working templates. You can now test this PR, the index Page and upload works. If you want to complete the other html templates, you're welcome * Move captcha to util * Move uploadService to utils * Use govalidator instead of regex * Third batch of changes All the front end should as previously. I also fixed some minor things unrelated to the refactor (mostly style issues on static pages) Now errors can be accessed by importing the "errors" helpers and using the `yield errors(name="xxx")` command in templates. Same for infos. Templates are now more hierarchized with a base template "base.jet.html" which is extended depending on the context in "index_site" or "index_admin" layouts. Those layouts are extended than in every pages. Other helpers are captcha to render a captcha `yield captcha(captchaid="xxx")` And also csrf, with the command `yield csrf_field()` To translate, you don't have anymore to do `call $.T "xxx"`, you just have to do `T("xxx")`. Pages for the website part are in folders in the folder "templates/site". Pages for the admin part are in "templates/admin". Layouts are separated in "templates/layouts". Helpers and menu are in "templates/layouts/helpers" and "templates/layouts/menu". Error pages should be put in "templates/errors" * Added test on templates When adding a new template, you have to tell to template_test.go, the context of the new template (if it doesn't use the common context) * Panel admin works Now the templating part should work. The PR can now be fully tested. I think we should push the templating PR and do the routes/controllers/removal of services in another branch. So we know that this one is functional * Updated dependencies * Fixed test for modelhelper * Fix testing for commentlist * Fix travis :') * Just renamed router and removed network * Applying same SEO fix * Update form_validator.go * Added back regexp package
2017-06-28 13:42:38 +02:00
"github.com/gin-gonic/gin"
)
var searchOperator string
var useTSQuery bool
// Configure : initialize search
func Configure(conf *config.SearchConfig) (err error) {
2017-05-12 19:38:08 +02:00
useTSQuery = false
// Postgres needs ILIKE for case-insensitivity
if models.ORM.Dialect().GetName() == "postgres" {
searchOperator = "ILIKE ?"
2017-05-12 19:38:08 +02:00
//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)
2017-05-20 13:45:15 +02:00
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)
2017-05-09 17:07:42 +02:00
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)
2017-05-09 17:07:42 +02:00
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)
Deleted torrents mod done (#732) * Torrent Mass Edit Api (WIP) * Torrents can be deleted in mass from frontend with api post request * Torrents status can be edited from frontend with api post request -- Look to function doc for more info on how to use it It is a WIP so it might not work =D * Finished Mass mod Api As per suggestion of @yiiTT in #720, I added: * Changing torrents category * Deletion of reports with deletion of a torrent * Changing owner of multiple torrents Commit also add some new translation strings. * Make some changes * Reports can now be cleared for the torrents selected without having to delete them * Users with no admin rights can't delete reports * Fix moveto to status moveto deprecated in api * Tested and works! Changes: * Updates only the colomns of torrent table * Moved categories config in config/torrents.go * Forgot this file in last commit * Less useless queries The use of Save makes it that users are created and updates also all the associatiated models. Better to just update the colomns needed (less useless queries) * Some Updates * Added a new status of 5 for locking torrents * Modifying the list torrents view for using it in deleted torrents view * Added function to get deleted torrents * Torrents (and reports) can be definitely deleted * Some new translation string * Fixing * fix 2 * Added upload check for locked torrents If a user owns a torrent, has deleted it and try to repload it. As long as it has not been locked, he can. * Fixing wrong condition in isdeleted * Finished * Info messages on success when deletes or lock * Fixed double deleted_at is Null * Added Link to view of deleted torrents * Added new translation string
2017-05-25 02:19:05 +02:00
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) {
2017-07-05 19:41:16 +02:00
var err error
if models.ElasticSearchClient != nil && !deleted {
var torrentParam structs.TorrentParam
First batch of changes for the refactor (#1078) * First batch of changes for the refactor Added the support of gin in routes and other services/utils Begining implementation of JetHTML * Remove os folder * Move scrapers to own repo * Second batch of changes All .jet.html are the working templates. You can now test this PR, the index Page and upload works. If you want to complete the other html templates, you're welcome * Move captcha to util * Move uploadService to utils * Use govalidator instead of regex * Third batch of changes All the front end should as previously. I also fixed some minor things unrelated to the refactor (mostly style issues on static pages) Now errors can be accessed by importing the "errors" helpers and using the `yield errors(name="xxx")` command in templates. Same for infos. Templates are now more hierarchized with a base template "base.jet.html" which is extended depending on the context in "index_site" or "index_admin" layouts. Those layouts are extended than in every pages. Other helpers are captcha to render a captcha `yield captcha(captchaid="xxx")` And also csrf, with the command `yield csrf_field()` To translate, you don't have anymore to do `call $.T "xxx"`, you just have to do `T("xxx")`. Pages for the website part are in folders in the folder "templates/site". Pages for the admin part are in "templates/admin". Layouts are separated in "templates/layouts". Helpers and menu are in "templates/layouts/helpers" and "templates/layouts/menu". Error pages should be put in "templates/errors" * Added test on templates When adding a new template, you have to tell to template_test.go, the context of the new template (if it doesn't use the common context) * Panel admin works Now the templating part should work. The PR can now be fully tested. I think we should push the templating PR and do the routes/controllers/removal of services in another branch. So we know that this one is functional * Updated dependencies * Fixed test for modelhelper * Fix testing for commentlist * Fix travis :') * Just renamed router and removed network * Applying same SEO fix * Update form_validator.go * Added back regexp package
2017-06-28 13:42:38 +02:00
torrentParam.FromRequest(c)
2017-06-18 01:39:11 +02:00
torrentParam.Offset = uint32(pagenum)
torrentParam.Hidden = hidden
torrentParam.Full = withUser
2017-07-05 19:41:16 +02:00
if found, ok := cache.C.Get(torrentParam.Identifier()); ok {
torrentCache := found.(*structs.TorrentCache)
return torrentParam, torrentCache.Torrents, torrentCache.Count, nil
2017-07-05 19:41:16 +02:00
}
totalHits, tor, err := torrentParam.Find(models.ElasticSearchClient)
cache.C.Set(torrentParam.Identifier(), &structs.TorrentCache{tor, int(totalHits)}, 5*time.Minute)
// Convert back to non-json torrents
2017-07-05 19:41:16 +02:00
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,
2017-05-10 11:03:49 +02:00
) {
search.FromRequest(c)
search.Offset = uint32(pagenum)
search.Hidden = hidden
2017-07-15 01:44:09 +02:00
search.Deleted = deleted
search.Full = withUser
2017-07-07 00:18:11 +02:00
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" {
2017-05-17 23:41:18 +02:00
orderBy += " NULLS FIRST"
2017-05-24 09:11:13 +02:00
}
case false:
orderBy += "desc"
if models.ORM.Dialect().GetName() == "postgres" {
2017-05-17 23:41:18 +02:00
orderBy += " NULLS LAST"
2017-05-24 09:11:13 +02:00
}
}
2017-05-17 23:41:18 +02:00
parameters := structs.WhereParams{
2017-05-11 14:22:49 +02:00
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 "))
2017-05-11 14:22:49 +02:00
}
2017-07-07 14:06:51 +02:00
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+"%")
}
2017-07-21 02:13:41 +02:00
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)
}
2017-05-11 14:22:49 +02:00
if search.Status != 0 {
if search.Status == structs.FilterRemakes {
conditions = append(conditions, "status <> ?")
2017-05-11 14:22:49 +02:00
} else {
conditions = append(conditions, "status >= ?")
}
2017-05-11 14:22:49 +02:00
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 {
2017-05-11 14:22:49 +02:00
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
2017-05-08 15:50:18 +02:00
}
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+"%")
}
2017-05-11 14:22:49 +02:00
}
parameters.Conditions = strings.Join(conditions[:], " AND ")
2017-05-11 14:22:49 +02:00
log.Infof("SQL query is :: %s\n", parameters.Conditions)
2017-06-25 15:26:46 +02:00
if deleted {
tor, count, err = torrents.FindDeleted(&parameters, orderBy, int(search.Max), int(search.Max*(search.Offset-1)))
2017-06-25 15:26:46 +02:00
} else if countAll && !withUser {
tor, count, err = torrents.FindOrderBy(&parameters, orderBy, int(search.Max), int(search.Max*(search.Offset-1)))
} else if countAll && withUser {
tor, count, err = torrents.FindWithUserOrderBy(&parameters, orderBy, int(search.Max), int(search.Max*(search.Offset-1)))
2017-06-25 15:26:46 +02:00
} else {
tor, err = torrents.FindOrderByNoCount(&parameters, orderBy, int(search.Max), int(search.Max*(search.Offset-1)))
2017-06-25 15:26:46 +02:00
}
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)
}