2017-05-05 16:39:15 +02:00
package search
2017-05-06 12:41:48 +02:00
import (
"net/http"
2017-05-05 16:39:15 +02:00
"strconv"
"strings"
2017-05-08 15:50:18 +02:00
"unicode"
"unicode/utf8"
2017-05-26 01:48:14 +02:00
elastic "gopkg.in/olivere/elastic.v5"
2017-05-05 16:39:15 +02:00
2017-05-17 07:58:40 +02:00
"github.com/NyaaPantsu/nyaa/cache"
"github.com/NyaaPantsu/nyaa/common"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/db"
"github.com/NyaaPantsu/nyaa/model"
"github.com/NyaaPantsu/nyaa/service"
"github.com/NyaaPantsu/nyaa/service/torrent"
"github.com/NyaaPantsu/nyaa/util/log"
2017-05-09 13:31:58 +02:00
)
2017-05-11 15:24:20 +02:00
var searchOperator string
2017-05-12 17:41:26 +02:00
var useTSQuery bool
2017-05-11 15:24:20 +02:00
2017-05-26 12:12:52 +02:00
// Configure : initialize search
2017-05-11 15:24:20 +02:00
func Configure ( conf * config . SearchConfig ) ( err error ) {
2017-05-12 19:38:08 +02:00
useTSQuery = false
2017-05-12 17:41:26 +02:00
// Postgres needs ILIKE for case-insensitivity
if db . ORM . Dialect ( ) . GetName ( ) == "postgres" {
2017-05-11 15:24:20 +02:00
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)
2017-05-12 17:41:26 +02:00
} else {
searchOperator = "LIKE ?"
2017-05-11 15:24:20 +02:00
}
return
}
2017-05-26 12:12:52 +02:00
func stringIsASCII ( input string ) bool {
2017-05-12 17:41:26 +02:00
for _ , char := range input {
if char > 127 {
return false
}
}
return true
}
2017-05-26 12:12:52 +02:00
// SearchByQuery : search torrents according to request without user
2017-05-10 11:03:49 +02:00
func SearchByQuery ( r * http . Request , pagenum int ) ( search common . SearchParam , tor [ ] model . Torrent , count int , err error ) {
2017-05-25 02:19:05 +02:00
search , tor , count , err = searchByQuery ( r , pagenum , true , false , false )
2017-05-20 13:45:15 +02:00
return
}
2017-05-26 12:12:52 +02:00
// SearchByQueryWithUser : search torrents according to request with user
2017-05-20 13:45:15 +02:00
func SearchByQueryWithUser ( r * http . Request , pagenum int ) ( search common . SearchParam , tor [ ] model . Torrent , count int , err error ) {
2017-05-25 02:19:05 +02:00
search , tor , count , err = searchByQuery ( r , pagenum , true , true , false )
2017-05-09 17:07:42 +02:00
return
}
2017-05-26 12:12:52 +02:00
// SearchByQueryNoCount : search torrents according to request without user and count
2017-05-10 11:03:49 +02:00
func SearchByQueryNoCount ( r * http . Request , pagenum int ) ( search common . SearchParam , tor [ ] model . Torrent , err error ) {
2017-05-25 02:19:05 +02:00
search , tor , _ , err = searchByQuery ( r , pagenum , false , false , false )
2017-05-09 17:07:42 +02:00
return
}
2017-05-26 12:12:52 +02:00
// SearchByQueryDeleted : search deleted torrents according to request with user and count
2017-05-25 02:19:05 +02:00
func SearchByQueryDeleted ( r * http . Request , pagenum int ) ( search common . SearchParam , tor [ ] model . Torrent , count int , err error ) {
search , tor , count , err = searchByQuery ( r , pagenum , true , true , true )
return
}
2017-05-26 01:48:14 +02:00
// TODO Clean this up
// FIXME Some fields are not used by elasticsearch (pagenum, countAll, deleted, withUser)
// pagenum is extracted from request in .FromRequest()
// elasticsearch always provide a count to how many hits
// deleted is unused because es doesn't index deleted torrents
2017-05-25 02:19:05 +02:00
func searchByQuery ( r * http . Request , pagenum int , countAll bool , withUser bool , deleted bool ) (
2017-05-10 11:03:49 +02:00
search common . SearchParam , tor [ ] model . Torrent , count int , err error ,
2017-05-26 01:48:14 +02:00
) {
client , err := elastic . NewClient ( )
if err == nil {
var torrentParam common . TorrentParam
torrentParam . FromRequest ( r )
totalHits , torrents , err := torrentParam . Find ( client )
searchParam := common . SearchParam {
Order : torrentParam . Order ,
Status : torrentParam . Status ,
Sort : torrentParam . Sort ,
Category : torrentParam . Category ,
Page : int ( torrentParam . Offset ) ,
UserID : uint ( torrentParam . UserID ) ,
Max : uint ( torrentParam . Max ) ,
NotNull : torrentParam . NotNull ,
Query : torrentParam . NameLike ,
}
// Convert back to non-json torrents
return searchParam , torrents , int ( totalHits ) , err
} else {
log . Errorf ( "Unable to create elasticsearch client: %s" , err )
log . Errorf ( "Falling back to postgresql query" )
return searchByQueryPostgres ( r , pagenum , countAll , withUser , deleted )
}
}
func searchByQueryPostgres ( r * http . Request , pagenum int , countAll bool , withUser bool , deleted bool ) (
search common . SearchParam , tor [ ] model . Torrent , count int , err error ,
2017-05-10 11:03:49 +02:00
) {
2017-05-09 13:31:58 +02:00
max , err := strconv . ParseUint ( r . URL . Query ( ) . Get ( "max" ) , 10 , 32 )
if err != nil {
max = 50 // default Value maxPerPage
} else if max > 300 {
max = 300
2017-05-05 16:39:15 +02:00
}
2017-05-09 13:31:58 +02:00
search . Max = uint ( max )
2017-05-10 11:32:49 +02:00
search . Page = pagenum
2017-05-09 13:31:58 +02:00
search . Query = r . URL . Query ( ) . Get ( "q" )
2017-05-11 03:52:36 +02:00
userID , _ := strconv . Atoi ( r . URL . Query ( ) . Get ( "userID" ) )
2017-05-11 13:40:50 +02:00
search . UserID = uint ( userID )
2017-05-06 12:41:48 +02:00
2017-05-09 13:31:58 +02:00
switch s := r . URL . Query ( ) . Get ( "s" ) ; s {
case "1" :
2017-05-10 11:03:49 +02:00
search . Status = common . FilterRemakes
2017-05-09 13:31:58 +02:00
case "2" :
2017-05-10 11:03:49 +02:00
search . Status = common . Trusted
2017-05-09 13:31:58 +02:00
case "3" :
2017-05-10 11:03:49 +02:00
search . Status = common . APlus
2017-05-06 12:41:48 +02:00
}
2017-05-05 16:39:15 +02:00
2017-05-09 13:31:58 +02:00
catString := r . URL . Query ( ) . Get ( "c" )
if s := catString ; len ( s ) > 1 && s != "_" {
var tmp uint64
tmp , err = strconv . ParseUint ( string ( s [ 0 ] ) , 10 , 8 )
if err != nil {
return
}
search . Category . Main = uint8 ( tmp )
2017-05-05 16:39:15 +02:00
2017-05-14 06:03:04 +02:00
if len ( s ) > 2 && len ( s ) < 5 {
tmp , err = strconv . ParseUint ( s [ 2 : ] , 10 , 8 )
2017-05-09 13:31:58 +02:00
if err != nil {
return
}
search . Category . Sub = uint8 ( tmp )
}
2017-05-05 16:39:15 +02:00
}
2017-05-08 16:39:41 +02:00
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy := ""
2017-05-09 13:31:58 +02:00
switch s := r . URL . Query ( ) . Get ( "sort" ) ; s {
case "1" :
2017-05-10 11:03:49 +02:00
search . Sort = common . Name
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy += "torrent_name"
2017-05-11 13:40:50 +02:00
break
2017-05-09 13:31:58 +02:00
case "2" :
2017-05-10 11:03:49 +02:00
search . Sort = common . Date
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy += "date"
2017-05-14 17:27:18 +02:00
search . NotNull = "date IS NOT NULL"
2017-05-11 13:40:50 +02:00
break
2017-05-09 13:31:58 +02:00
case "3" :
2017-05-10 11:03:49 +02:00
search . Sort = common . Downloads
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy += "downloads"
2017-05-11 13:40:50 +02:00
break
2017-05-09 13:31:58 +02:00
case "4" :
2017-05-10 11:03:49 +02:00
search . Sort = common . Size
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy += "filesize"
2017-05-14 17:27:18 +02:00
// avoid sorting completely breaking on postgres
2017-05-17 23:41:18 +02:00
search . NotNull = ""
2017-05-11 13:40:50 +02:00
break
2017-05-11 02:26:58 +02:00
case "5" :
2017-05-11 13:40:50 +02:00
search . Sort = common . Seeders
2017-05-11 02:26:58 +02:00
orderBy += "seeders"
2017-05-17 23:41:18 +02:00
search . NotNull = ""
2017-05-11 13:40:50 +02:00
break
2017-05-11 02:26:58 +02:00
case "6" :
2017-05-11 13:40:50 +02:00
search . Sort = common . Leechers
2017-05-11 02:26:58 +02:00
orderBy += "leechers"
2017-05-17 23:41:18 +02:00
search . NotNull = ""
2017-05-11 13:40:50 +02:00
break
2017-05-11 02:26:58 +02:00
case "7" :
2017-05-11 13:40:50 +02:00
search . Sort = common . Completed
2017-05-11 02:26:58 +02:00
orderBy += "completed"
2017-05-17 23:41:18 +02:00
search . NotNull = ""
2017-05-11 13:40:50 +02:00
break
2017-05-08 16:39:41 +02:00
default :
2017-05-11 13:40:50 +02:00
search . Sort = common . ID
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy += "torrent_id"
2017-05-05 16:39:15 +02:00
}
2017-05-08 16:39:41 +02:00
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy += " "
2017-05-09 13:31:58 +02:00
switch s := r . URL . Query ( ) . Get ( "order" ) ; s {
case "true" :
search . Order = true
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy += "asc"
2017-05-17 23:41:18 +02:00
if db . ORM . Dialect ( ) . GetName ( ) == "postgres" {
orderBy += " NULLS FIRST"
2017-05-24 09:11:13 +02:00
}
2017-05-08 16:39:41 +02:00
default :
Consistency, formatting, error checking, cleanup, and a couple bug fixes (#245)
* Checkpoint: it builds
The config, db, model, network, os, and public packages have had some
fixes to glaringly obvious flaws, dead code removed, and stylistic
changes.
* Style changes and old code removal in router
Router needs a lot of work done to its (lack of) error handling.
* Dead code removal and style changes
Now up to util/email/email.go. After I'm finished with the initial sweep
I'll go back and fix error handling and security issues. Then I'll fix
the broken API. Then I'll go through to add documentation and fix code
visibility.
* Finish dead code removal and style changes
Vendored libraries not touched. Everything still needs security fixes
and documentation. There's also one case of broken functionality.
* Fix accidental find-and-replace
* Style, error checking, saftey, bug fix changes
* Redo error checking erased during merge
* Re-add merge-erased fix. Make Safe safe.
2017-05-10 04:34:40 +02:00
orderBy += "desc"
2017-05-17 23:41:18 +02:00
if db . ORM . Dialect ( ) . GetName ( ) == "postgres" {
orderBy += " NULLS LAST"
2017-05-24 09:11:13 +02:00
}
}
2017-05-17 23:41:18 +02:00
2017-05-11 14:22:49 +02:00
parameters := serviceBase . WhereParams {
Params : make ( [ ] interface { } , 0 , 64 ) ,
}
conditions := make ( [ ] string , 0 , 64 )
2017-05-08 16:39:41 +02:00
2017-05-11 14:22:49 +02:00
if search . Category . Main != 0 {
conditions = append ( conditions , "category = ?" )
2017-05-14 06:03:04 +02:00
parameters . Params = append ( parameters . Params , search . Category . Main )
2017-05-11 14:22:49 +02:00
}
if search . UserID != 0 {
conditions = append ( conditions , "uploader = ?" )
parameters . Params = append ( parameters . Params , search . UserID )
}
if search . Category . Sub != 0 {
conditions = append ( conditions , "sub_category = ?" )
2017-05-14 06:03:04 +02:00
parameters . Params = append ( parameters . Params , search . Category . Sub )
2017-05-11 14:22:49 +02:00
}
if search . Status != 0 {
if search . Status == common . FilterRemakes {
2017-05-11 21:46:23 +02:00
conditions = append ( conditions , "status <> ?" )
2017-05-11 14:22:49 +02:00
} else {
conditions = append ( conditions , "status >= ?" )
2017-05-11 13:40:50 +02:00
}
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 )
}
2017-05-09 13:31:58 +02:00
2017-05-11 14:22:49 +02:00
searchQuerySplit := strings . Fields ( search . Query )
2017-05-12 17:41:26 +02:00
for _ , word := range searchQuerySplit {
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
}
2017-05-09 14:31:47 +02:00
2017-05-26 12:12:52 +02:00
if useTSQuery && stringIsASCII ( word ) {
2017-05-12 17:41:26 +02:00
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 15:24:20 +02:00
2017-05-11 14:22:49 +02:00
log . Infof ( "SQL query is :: %s\n" , parameters . Conditions )
2017-05-05 16:39:15 +02:00
2017-05-11 15:24:20 +02:00
tor , count , err = cache . Impl . Get ( search , func ( ) ( tor [ ] model . Torrent , count int , err error ) {
2017-05-25 02:19:05 +02:00
if deleted {
tor , count , err = torrentService . GetDeletedTorrents ( & parameters , orderBy , int ( search . Max ) , int ( search . Max ) * ( search . Page - 1 ) )
} else if countAll && ! withUser {
2017-05-10 11:32:49 +02:00
tor , count , err = torrentService . GetTorrentsOrderBy ( & parameters , orderBy , int ( search . Max ) , int ( search . Max ) * ( search . Page - 1 ) )
2017-05-20 13:45:15 +02:00
} else if withUser {
tor , count , err = torrentService . GetTorrentsWithUserOrderBy ( & parameters , orderBy , int ( search . Max ) , int ( search . Max ) * ( search . Page - 1 ) )
2017-05-09 14:31:47 +02:00
} else {
2017-05-10 11:32:49 +02:00
tor , err = torrentService . GetTorrentsOrderByNoCount ( & parameters , orderBy , int ( search . Max ) , int ( search . Max ) * ( search . Page - 1 ) )
2017-05-09 14:31:47 +02:00
}
2017-05-10 11:03:49 +02:00
return
} )
2017-05-09 13:31:58 +02:00
return
2017-05-12 17:41:26 +02:00
}