2017-07-02 16:54:55 +02:00
package structs
2017-05-15 01:31:17 +02:00
2017-05-26 01:48:14 +02:00
import (
"context"
"encoding/json"
"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
elastic "gopkg.in/olivere/elastic.v5"
2017-05-26 01:48:14 +02:00
"github.com/NyaaPantsu/nyaa/config"
2017-06-29 13:15:23 +02:00
"github.com/NyaaPantsu/nyaa/models"
2017-07-02 16:54:55 +02:00
"github.com/NyaaPantsu/nyaa/utils/log"
2017-06-28 13:42:38 +02:00
"github.com/gin-gonic/gin"
2017-05-26 01:48:14 +02:00
)
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 ?
2017-06-28 13:42:38 +02:00
func ( p * TorrentParam ) FromRequest ( c * gin . Context ) {
2017-05-26 01:48:14 +02:00
var err error
2017-06-28 13:42:38 +02:00
nameLike := strings . TrimSpace ( c . Query ( "q" ) )
2017-07-10 14:11:05 +02:00
max , err := strconv . ParseUint ( c . DefaultQuery ( "limit" , strconv . Itoa ( config . Get ( ) . Navigation . TorrentsPerPage ) ) , 10 , 32 )
2017-05-26 01:48:14 +02:00
if err != nil {
2017-07-10 14:11:05 +02:00
max = uint64 ( config . Get ( ) . Navigation . TorrentsPerPage )
} else if max > uint64 ( config . Get ( ) . Navigation . MaxTorrentsPerPage ) {
max = uint64 ( config . Get ( ) . Navigation . MaxTorrentsPerPage )
2017-05-26 01:48:14 +02:00
}
// FIXME 0 means no userId defined
2017-06-28 13:42:38 +02:00
userID , err := strconv . ParseUint ( c . Query ( "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-06-28 13:42:38 +02:00
fromID , err := strconv . ParseUint ( c . Query ( "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
2017-06-28 13:42:38 +02:00
status . Parse ( c . Query ( "s" ) )
2017-05-26 01:48:14 +02:00
2017-06-28 13:42:38 +02:00
maxage , err := strconv . Atoi ( c . Query ( "maxage" ) )
2017-06-20 02:06:01 +02:00
fromDate , toDate := DateFilter ( "" ) , DateFilter ( "" )
2017-06-02 16:25:45 +02:00
if err != nil {
2017-06-20 02:06:01 +02:00
// if to xxx is not provided, fromDate is equal to from xxx
2017-06-28 13:42:38 +02:00
if c . Query ( "toDate" ) != "" {
fromDate . Parse ( c . Query ( "toDate" ) , c . Query ( "dateType" ) )
toDate . Parse ( c . Query ( "fromDate" ) , c . Query ( "dateType" ) )
2017-06-20 02:06:01 +02:00
} else {
2017-06-28 13:42:38 +02:00
fromDate . Parse ( c . Query ( "fromDate" ) , c . Query ( "dateType" ) )
2017-06-20 02:06:01 +02:00
}
2017-06-02 16:25:45 +02:00
} else {
2017-06-20 02:06:01 +02:00
fromDate = DateFilter ( time . Now ( ) . AddDate ( 0 , 0 , - maxage ) . Format ( "2006-01-02" ) )
2017-06-02 16:25:45 +02:00
}
2017-06-02 16:10:31 +02:00
2017-06-28 13:42:38 +02:00
categories := ParseCategories ( c . Query ( "c" ) )
2017-05-26 01:48:14 +02:00
var sortMode SortMode
2017-06-28 13:42:38 +02:00
sortMode . Parse ( c . Query ( "sort" ) )
2017-05-26 01:48:14 +02:00
2017-06-13 13:31:11 +02:00
var minSize SizeBytes
var maxSize SizeBytes
2017-06-20 02:06:01 +02:00
2017-06-28 13:42:38 +02:00
minSize . Parse ( c . Query ( "minSize" ) , c . Query ( "sizeType" ) )
maxSize . Parse ( c . Query ( "maxSize" ) , c . Query ( "sizeType" ) )
2017-06-13 13:31:11 +02:00
2017-05-26 01:48:14 +02:00
ascending := false
2017-06-28 13:42:38 +02:00
if c . Query ( "order" ) == "true" {
2017-05-26 01:48:14 +02:00
ascending = true
}
2017-07-07 14:08:16 +02:00
langs := c . QueryArray ( "lang" )
2017-05-26 01:48:14 +02:00
2017-07-07 14:08:16 +02:00
language := ParseLanguages ( strings . Join ( langs , "," ) )
2017-06-12 01:14:26 +02:00
2017-05-26 01:48:14 +02:00
p . NameLike = nameLike
p . Max = uint32 ( max )
2017-05-30 00:28:21 +02:00
p . UserID = uint32 ( userID )
2017-05-26 01:48:14 +02:00
p . Order = ascending
p . Status = status
p . Sort = sortMode
2017-06-18 00:30:12 +02:00
p . Category = categories
2017-07-07 14:06:51 +02:00
p . Languages = language
2017-06-20 02:06:01 +02:00
p . FromDate = fromDate
p . ToDate = toDate
2017-06-13 13:31:11 +02:00
p . MinSize = minSize
p . MaxSize = maxSize
2017-05-26 01:48:14 +02:00
// 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 {
2017-07-05 14:29:31 +02:00
// Don'p set sub category unless main category is set
2017-05-26 01:48:14 +02:00
query := ""
2017-06-18 00:30:12 +02:00
if len ( p . Category ) > 0 {
conditionsOr := make ( [ ] string , len ( p . Category ) )
for key , val := range p . Category {
if val . IsSubSet ( ) {
conditionsOr [ key ] = "(category: " + strconv . FormatInt ( int64 ( val . Main ) , 10 ) + " AND sub_category: " + strconv . FormatInt ( int64 ( val . Sub ) , 10 ) + ")"
2017-06-18 01:23:12 +02:00
} else {
2017-07-14 19:08:02 +02:00
if val . Main > 0 {
conditionsOr [ key ] = "(category: " + strconv . FormatInt ( int64 ( val . Main ) , 10 ) + ")"
}
2017-06-18 00:30:12 +02:00
}
2017-05-26 01:48:14 +02:00
}
2017-06-18 01:23:12 +02:00
query += "(" + strings . Join ( conditionsOr , " OR " ) + ")"
2017-05-26 01:48:14 +02:00
}
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
}
2017-06-21 03:58:54 +02:00
if p . Hidden {
query += " hidden:false"
}
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 != "" {
2017-06-20 02:06:01 +02:00
query += " date: [" + string ( p . FromDate ) + " " + string ( p . ToDate ) + "]"
2017-06-02 16:10:31 +02:00
} else if p . FromDate != "" {
2017-06-20 02:06:01 +02:00
query += " date: [" + string ( p . FromDate ) + " *]"
2017-06-02 16:10:31 +02:00
} else if p . ToDate != "" {
2017-06-20 02:06:01 +02:00
query += " date: [* " + string ( p . ToDate ) + "]"
2017-06-02 16:10:31 +02:00
}
2017-06-13 13:31:11 +02:00
sMinSize := strconv . FormatUint ( uint64 ( p . MinSize ) , 10 )
sMaxSize := strconv . FormatUint ( uint64 ( p . MaxSize ) , 10 )
if p . MinSize > 0 && p . MaxSize > 0 {
query += " filesize: [" + sMinSize + " " + sMaxSize + "]"
} else if p . MinSize > 0 {
query += " filesize: [" + sMinSize + " *]"
} else if p . MaxSize > 0 {
query += " filesize: [* " + sMaxSize + "]"
}
2017-07-07 14:08:16 +02:00
if len ( p . Languages ) > 0 {
2017-07-07 14:06:51 +02:00
for _ , val := range p . Languages {
query += " language: " + val . Code
}
2017-06-12 01:14:26 +02:00
}
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
* /
2017-07-01 23:09:35 +02:00
func ( p * TorrentParam ) Find ( client * elastic . Client ) ( int64 , [ ] models . Torrent , error ) {
2017-05-26 01:48:14 +02:00
// 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" ) .
2017-07-10 14:11:05 +02:00
Analyzer ( config . Get ( ) . Search . ElasticsearchAnalyzer ) .
2017-06-01 00:38:29 +02:00
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-07-10 14:11:05 +02:00
Index ( config . Get ( ) . Search . ElasticsearchIndex ) .
2017-05-26 01:48:14 +02:00
Query ( query ) .
2017-07-10 14:11:05 +02:00
Type ( config . Get ( ) . 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-07-05 14:29:31 +02:00
Sort ( "_score" , false ) // Don'p 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-07-13 14:23:28 +02:00
var torrents [ ] models . Torrent
2017-07-04 02:11:38 +02:00
var torrentCount int64
torrentCount = 0
2017-07-13 14:23:28 +02:00
//torrents := make([]models.Torrent, len(result.Hits.Hits))
2017-06-05 03:33:02 +02:00
if len ( result . Hits . Hits ) <= 0 {
return 0 , nil , nil
}
2017-07-04 02:11:38 +02:00
for _ , hit := range result . Hits . Hits {
2017-07-13 14:23:28 +02:00
var tJSON models . TorrentJSON
err := json . Unmarshal ( * hit . Source , & tJSON )
2017-07-04 02:11:38 +02:00
if err == nil {
2017-07-13 14:23:28 +02:00
torrents = append ( torrents , tJSON . ToTorrent ( ) )
torrentCount ++
2017-07-04 02:11:38 +02:00
} else {
log . Infof ( "Cannot unmarshal elasticsearch torrent: %s" , err )
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 ,
NameLike : p . NameLike ,
2017-07-07 14:08:16 +02:00
Languages : p . Languages ,
2017-06-13 13:31:11 +02:00
MinSize : p . MinSize ,
MaxSize : p . MaxSize ,
2017-05-15 01:31:17 +02:00
}
}