2017-05-15 01:31:17 +02:00
package common
2017-05-26 01:48:14 +02:00
import (
"context"
"encoding/json"
"net/http"
"strconv"
2017-06-01 00:38:29 +02:00
"strings"
2017-06-02 16:25:45 +02:00
"time"
2017-05-26 01:48:14 +02:00
2017-05-30 00:28:21 +02:00
"github.com/gorilla/mux"
elastic "gopkg.in/olivere/elastic.v5"
2017-05-26 01:48:14 +02:00
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/model"
"github.com/NyaaPantsu/nyaa/util/log"
)
2017-05-15 01:31:17 +02:00
// TorrentParam defines all parameters that can be provided when searching for a torrent
type TorrentParam struct {
All bool // True means ignore everything but Max and Offset
Full bool // True means load all members
2017-05-26 01:48:14 +02:00
Order bool // True means ascending
2017-05-15 01:31:17 +02:00
Status Status
Sort SortMode
Category Category
Max uint32
Offset uint32
UserID uint32
TorrentID uint32
2017-05-30 14:12:42 +02:00
FromID uint32
2017-06-02 16:10:31 +02:00
FromDate string
ToDate string
2017-05-15 01:31:17 +02:00
NotNull string // csv
Null string // csv
NameLike string // csv
}
2017-05-30 00:28:21 +02:00
// FromRequest : parse a request in torrent param
2017-05-26 01:48:14 +02:00
// TODO Should probably return an error ?
func ( p * TorrentParam ) FromRequest ( r * http . Request ) {
var err error
2017-06-01 00:38:29 +02:00
nameLike := strings . TrimSpace ( r . URL . Query ( ) . Get ( "q" ) )
2017-05-26 01:48:14 +02:00
page := mux . Vars ( r ) [ "page" ]
pagenum , err := strconv . ParseUint ( page , 10 , 32 )
if err != nil {
pagenum = 1
}
max , err := strconv . ParseUint ( r . URL . Query ( ) . Get ( "max" ) , 10 , 32 )
if err != nil {
2017-05-31 04:21:57 +02:00
max = uint64 ( config . Conf . Navigation . TorrentsPerPage )
} else if max > uint64 ( config . Conf . Navigation . MaxTorrentsPerPage ) {
max = uint64 ( config . Conf . Navigation . MaxTorrentsPerPage )
2017-05-26 01:48:14 +02:00
}
// FIXME 0 means no userId defined
2017-05-30 00:28:21 +02:00
userID , err := strconv . ParseUint ( r . URL . Query ( ) . Get ( "userID" ) , 10 , 32 )
2017-05-26 01:48:14 +02:00
if err != nil {
2017-05-30 00:28:21 +02:00
userID = 0
}
// FIXME 0 means no userId defined
2017-05-30 14:12:42 +02:00
fromID , err := strconv . ParseUint ( r . URL . Query ( ) . Get ( "fromID" ) , 10 , 32 )
2017-05-30 00:28:21 +02:00
if err != nil {
2017-05-30 14:12:42 +02:00
fromID = 0
2017-05-26 01:48:14 +02:00
}
var status Status
status . Parse ( r . URL . Query ( ) . Get ( "s" ) )
2017-06-02 16:25:45 +02:00
maxage , err := strconv . Atoi ( r . URL . Query ( ) . Get ( "maxage" ) )
if err != nil {
p . FromDate = r . URL . Query ( ) . Get ( "fromDate" )
p . ToDate = r . URL . Query ( ) . Get ( "toDate" )
} else {
p . FromDate = time . Now ( ) . AddDate ( 0 , 0 , - maxage ) . Format ( "2006-01-02" )
}
2017-06-02 16:10:31 +02:00
2017-05-26 01:48:14 +02:00
var category Category
category . Parse ( r . URL . Query ( ) . Get ( "c" ) )
var sortMode SortMode
sortMode . Parse ( r . URL . Query ( ) . Get ( "sort" ) )
ascending := false
if r . URL . Query ( ) . Get ( "order" ) == "true" {
ascending = true
}
p . NameLike = nameLike
p . Offset = uint32 ( pagenum )
p . Max = uint32 ( max )
2017-05-30 00:28:21 +02:00
p . UserID = uint32 ( userID )
2017-05-26 01:48:14 +02:00
// TODO Use All
p . All = false
// TODO Use Full
p . Full = false
p . Order = ascending
p . Status = status
p . Sort = sortMode
p . Category = category
// FIXME 0 means no TorrentId defined
2017-05-30 14:12:42 +02:00
// Do we even need that ?
p . TorrentID = 0
// Needed to display result after a certain torrentID
p . FromID = uint32 ( fromID )
2017-05-26 01:48:14 +02:00
}
2017-05-30 00:28:21 +02:00
// ToFilterQuery : Builds a query string with for es query string query defined here
2017-05-26 01:48:14 +02:00
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html
func ( p * TorrentParam ) ToFilterQuery ( ) string {
// Don't set sub category unless main category is set
query := ""
if p . Category . IsMainSet ( ) {
query += "category:" + strconv . FormatInt ( int64 ( p . Category . Main ) , 10 )
if p . Category . IsSubSet ( ) {
query += " sub_category:" + strconv . FormatInt ( int64 ( p . Category . Sub ) , 10 )
}
}
if p . UserID != 0 {
2017-05-27 15:07:10 +02:00
query += " uploader_id:" + strconv . FormatInt ( int64 ( p . UserID ) , 10 )
2017-05-26 01:48:14 +02:00
}
if p . Status != ShowAll {
2017-05-30 02:15:49 +02:00
if p . Status != FilterRemakes {
query += " status:" + p . Status . ToString ( )
} else {
/ * From the old nyaa behavior , FilterRemake means everything BUT
* remakes
* /
query += " !status:" + p . Status . ToString ( )
}
2017-05-26 01:48:14 +02:00
}
2017-05-30 00:28:21 +02:00
2017-05-30 14:12:42 +02:00
if p . FromID != 0 {
query += " id:>" + strconv . FormatInt ( int64 ( p . FromID ) , 10 )
2017-05-30 00:28:21 +02:00
}
2017-06-02 16:10:31 +02:00
if p . FromDate != "" && p . ToDate != "" {
query += " date: [" + p . FromDate + " " + p . ToDate + "]"
} else if p . FromDate != "" {
query += " date: [" + p . FromDate + " *]"
} else if p . ToDate != "" {
query += " date: [* " + p . ToDate + "]"
}
2017-05-26 01:48:14 +02:00
return query
}
2017-05-30 00:28:21 +02:00
// Find :
2017-05-26 01:48:14 +02:00
/ * Uses elasticsearch to find the torrents based on TorrentParam
* We decided to fetch only the ids from ES and then query these ids to the
* database
* /
func ( p * TorrentParam ) Find ( client * elastic . Client ) ( int64 , [ ] model . Torrent , error ) {
// TODO Why is it needed, what does it do ?
ctx := context . Background ( )
2017-06-01 00:38:29 +02:00
var query elastic . Query
if p . NameLike == "" {
query = elastic . NewMatchAllQuery ( )
} else {
query = elastic . NewSimpleQueryStringQuery ( p . NameLike ) .
Field ( "name" ) .
Analyzer ( config . Conf . Search . ElasticsearchAnalyzer ) .
DefaultOperator ( "AND" )
}
2017-05-26 01:48:14 +02:00
// TODO Find a better way to keep in sync with mapping in ansible
search := client . Search ( ) .
2017-05-31 04:21:57 +02:00
Index ( config . Conf . Search . ElasticsearchIndex ) .
2017-05-26 01:48:14 +02:00
Query ( query ) .
2017-05-31 04:21:57 +02:00
Type ( config . Conf . Search . ElasticsearchType ) .
2017-05-26 01:48:14 +02:00
From ( int ( ( p . Offset - 1 ) * p . Max ) ) .
Size ( int ( p . Max ) ) .
Sort ( p . Sort . ToESField ( ) , p . Order ) .
2017-06-05 03:33:02 +02:00
Sort ( "_score" , false ) // Don't put _score before the field sort, it messes with the sorting
2017-05-26 01:48:14 +02:00
filterQueryString := p . ToFilterQuery ( )
if filterQueryString != "" {
filterQuery := elastic . NewQueryStringQuery ( filterQueryString ) .
DefaultOperator ( "AND" )
search = search . PostFilter ( filterQuery )
}
result , err := search . Do ( ctx )
if err != nil {
return 0 , nil , err
}
log . Infof ( "Query '%s' took %d milliseconds." , p . NameLike , result . TookInMillis )
log . Infof ( "Amount of results %d." , result . TotalHits ( ) )
2017-06-05 03:33:02 +02:00
torrents := make ( [ ] model . Torrent , len ( result . Hits . Hits ) )
if len ( result . Hits . Hits ) <= 0 {
return 0 , nil , nil
}
for i , hit := range result . Hits . Hits {
// Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}).
var tJson model . TorrentJSON
err := json . Unmarshal ( * hit . Source , & tJson )
if err != nil {
log . Errorf ( "Cannot unmarshal elasticsearch torrent: %s" , err )
2017-05-26 01:48:14 +02:00
}
2017-06-05 03:33:02 +02:00
torrent := tJson . ToTorrent ( )
torrents [ i ] = torrent
2017-05-26 01:48:14 +02:00
}
2017-06-05 03:33:02 +02:00
return result . TotalHits ( ) , torrents , nil
2017-05-26 01:48:14 +02:00
}
2017-05-30 00:28:21 +02:00
// Clone : To clone a torrent params
2017-05-15 01:31:17 +02:00
func ( p * TorrentParam ) Clone ( ) TorrentParam {
return TorrentParam {
Order : p . Order ,
Status : p . Status ,
Sort : p . Sort ,
Category : p . Category ,
Max : p . Max ,
Offset : p . Offset ,
UserID : p . UserID ,
TorrentID : p . TorrentID ,
2017-05-30 14:12:42 +02:00
FromID : p . FromID ,
2017-06-02 16:10:31 +02:00
FromDate : p . FromDate ,
ToDate : p . ToDate ,
2017-05-15 01:31:17 +02:00
NotNull : p . NotNull ,
Null : p . Null ,
NameLike : p . NameLike ,
}
}