Albirew/nyaa-pantsu
Archivé
1
0
Bifurcation 0

Search query caching

Cette révision appartient à :
bakape 2017-05-10 12:03:49 +03:00
Parent 8b56f2e76e
révision 6bf7ee3438
4 fichiers modifiés avec 140 ajouts et 135 suppressions

24
cache/cache.go externe
Voir le fichier

@ -5,12 +5,12 @@ import (
"sync" "sync"
"time" "time"
"github.com/ewhal/nyaa/common"
"github.com/ewhal/nyaa/model" "github.com/ewhal/nyaa/model"
"github.com/ewhal/nyaa/util/search"
) )
var ( var (
cache = make(map[search.SearchParam]*list.Element, 10) cache = make(map[common.SearchParam]*list.Element, 10)
ll = list.New() ll = list.New()
totalUsed int totalUsed int
mu sync.Mutex mu sync.Mutex
@ -31,18 +31,17 @@ type Key struct {
// Single cache entry // Single cache entry
type store struct { type store struct {
// Controls general access to the contents of the struct, except for size sync.Mutex // Controls general access to the contents of the struct
sync.Mutex
lastFetched time.Time lastFetched time.Time
key search.SearchParam key common.SearchParam
data []model.Torrents data []model.Torrent
size int size int
} }
// Check the cache for and existing record. If miss, run fn to retrieve fresh // Check the cache for and existing record. If miss, run fn to retrieve fresh
// values. // values.
func CheckCache(key search.SearchParam, fn func() ([]model.Torrents, error)) ( func Get(key common.SearchParam, fn func() ([]model.Torrent, error)) (
[]model.Torrents, error, []model.Torrent, error,
) { ) {
s := getStore(key) s := getStore(key)
@ -59,12 +58,12 @@ func CheckCache(key search.SearchParam, fn func() ([]model.Torrents, error)) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.lastFetched = time.Now() s.update(data)
return data, nil return data, nil
} }
// Retrieve a store from the cache or create a new one // Retrieve a store from the cache or create a new one
func getStore(k search.SearchParam) (s *store) { func getStore(k common.SearchParam) (s *store) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@ -85,7 +84,7 @@ func Clear() {
defer mu.Unlock() defer mu.Unlock()
ll = list.New() ll = list.New()
cache = make(map[search.SearchParam]*list.Element, 10) cache = make(map[common.SearchParam]*list.Element, 10)
} }
// Update the total used memory counter and evict, if over limit // Update the total used memory counter and evict, if over limit
@ -109,7 +108,7 @@ func (s *store) isFresh() bool {
// Stores the new values of s. Calculates and stores the new size. Passes the // Stores the new values of s. Calculates and stores the new size. Passes the
// delta to the central cache to fire eviction checks. // delta to the central cache to fire eviction checks.
func (s *store) update(data []model.Torrents) { func (s *store) update(data []model.Torrent) {
newSize := 0 newSize := 0
for _, d := range data { for _, d := range data {
newSize += d.Size() newSize += d.Size()
@ -117,6 +116,7 @@ func (s *store) update(data []model.Torrents) {
s.data = data s.data = data
delta := newSize - s.size delta := newSize - s.size
s.size = newSize s.size = newSize
s.lastFetched = time.Now()
// Technically it is possible to update the size even when the store is // Technically it is possible to update the size even when the store is
// already evicted, but that should never happen, unless you have a very // already evicted, but that should never happen, unless you have a very

46
common/search.go Fichier normal
Voir le fichier

@ -0,0 +1,46 @@
package common
import "strconv"
type Status uint8
const (
ShowAll Status = iota
FilterRemakes
Trusted
APlus
)
type SortMode uint8
const (
ID SortMode = iota
Name
Date
Downloads
Size
)
type Category struct {
Main, Sub uint8
}
func (c Category) String() (s string) {
if c.Main != 0 {
s += strconv.Itoa(int(c.Main))
}
s += "_"
if c.Sub != 0 {
s += strconv.Itoa(int(c.Sub))
}
return
}
type SearchParam struct {
Order bool // True means acsending
Status Status
Sort SortMode
Category Category
Max uint
Query string
}

Voir le fichier

@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/ewhal/nyaa/common"
"github.com/ewhal/nyaa/model" "github.com/ewhal/nyaa/model"
"github.com/ewhal/nyaa/service/captcha" "github.com/ewhal/nyaa/service/captcha"
"github.com/ewhal/nyaa/service/user" "github.com/ewhal/nyaa/service/user"
userForms "github.com/ewhal/nyaa/service/user/form" userForms "github.com/ewhal/nyaa/service/user/form"
"github.com/ewhal/nyaa/util/search"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@ -87,7 +87,7 @@ type UserLoginFormVariables struct {
type UserProfileVariables struct { type UserProfileVariables struct {
UserProfile *model.User UserProfile *model.User
FormInfos map[string][]string FormInfos map[string][]string
Search SearchForm Search SearchForm
Navigation Navigation Navigation Navigation
User *model.User User *model.User
@ -114,22 +114,22 @@ type UploadTemplateVariables struct {
} }
type PanelIndexVbs struct { type PanelIndexVbs struct {
Torrents []model.Torrent Torrents []model.Torrent
Users []model.User Users []model.User
Comments []model.Comment Comments []model.Comment
} }
type PanelTorrentListVbs struct { type PanelTorrentListVbs struct {
Torrents []model.Torrent Torrents []model.Torrent
} }
type PanelUserListVbs struct { type PanelUserListVbs struct {
Users []model.User Users []model.User
} }
type PanelCommentListVbs struct { type PanelCommentListVbs struct {
Comments []model.Comment Comments []model.Comment
} }
type PanelTorrentEdVbs struct { type PanelTorrentEdVbs struct {
Torrent model.Torrent Torrent model.Torrent
} }
/* /*
@ -143,7 +143,7 @@ type Navigation struct {
} }
type SearchForm struct { type SearchForm struct {
search.SearchParam common.SearchParam
Category string Category string
HideAdvancedSearch bool HideAdvancedSearch bool
} }

Voir le fichier

@ -1,71 +1,33 @@
package search package search
import ( import (
"github.com/ewhal/nyaa/db"
"github.com/ewhal/nyaa/model"
"github.com/ewhal/nyaa/service/torrent"
"github.com/ewhal/nyaa/util/log"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"github.com/ewhal/nyaa/cache"
"github.com/ewhal/nyaa/common"
"github.com/ewhal/nyaa/db"
"github.com/ewhal/nyaa/model"
"github.com/ewhal/nyaa/service/torrent"
"github.com/ewhal/nyaa/util/log"
) )
type Status uint8 func SearchByQuery(r *http.Request, pagenum int) (search common.SearchParam, tor []model.Torrent, count int, err error) {
const (
ShowAll Status = iota
FilterRemakes
Trusted
APlus
)
type SortMode uint8
const (
ID SortMode = iota
Name
Date
Downloads
Size
)
type Category struct {
Main, Sub uint8
}
func (c Category) String() (s string) {
if c.Main != 0 {
s += strconv.Itoa(int(c.Main))
}
s += "_"
if c.Sub != 0 {
s += strconv.Itoa(int(c.Sub))
}
return
}
type SearchParam struct {
Order bool // True means acsending
Status Status
Sort SortMode
Category Category
Max uint
Query string
}
func SearchByQuery(r *http.Request, pagenum int) (search SearchParam, tor []model.Torrent, count int, err error) {
search, tor, count, err = searchByQuery(r, pagenum, true) search, tor, count, err = searchByQuery(r, pagenum, true)
return return
} }
func SearchByQueryNoCount(r *http.Request, pagenum int) (search SearchParam, tor []model.Torrent, err error) { func SearchByQueryNoCount(r *http.Request, pagenum int) (search common.SearchParam, tor []model.Torrent, err error) {
search, tor, _, err = searchByQuery(r, pagenum, false) search, tor, _, err = searchByQuery(r, pagenum, false)
return return
} }
func searchByQuery(r *http.Request, pagenum int, countAll bool) (search SearchParam, tor []model.Torrent, count int, err error) { func searchByQuery(r *http.Request, pagenum int, countAll bool) (
search common.SearchParam, tor []model.Torrent, count int, err error,
) {
max, err := strconv.ParseUint(r.URL.Query().Get("max"), 10, 32) max, err := strconv.ParseUint(r.URL.Query().Get("max"), 10, 32)
if err != nil { if err != nil {
max = 50 // default Value maxPerPage max = 50 // default Value maxPerPage
@ -78,11 +40,11 @@ func searchByQuery(r *http.Request, pagenum int, countAll bool) (search SearchPa
switch s := r.URL.Query().Get("s"); s { switch s := r.URL.Query().Get("s"); s {
case "1": case "1":
search.Status = FilterRemakes search.Status = common.FilterRemakes
case "2": case "2":
search.Status = Trusted search.Status = common.Trusted
case "3": case "3":
search.Status = APlus search.Status = common.APlus
} }
catString := r.URL.Query().Get("c") catString := r.URL.Query().Get("c")
@ -107,16 +69,16 @@ func searchByQuery(r *http.Request, pagenum int, countAll bool) (search SearchPa
switch s := r.URL.Query().Get("sort"); s { switch s := r.URL.Query().Get("sort"); s {
case "1": case "1":
search.Sort = Name search.Sort = common.Name
orderBy += "torrent_name" orderBy += "torrent_name"
case "2": case "2":
search.Sort = Date search.Sort = common.Date
orderBy += "date" orderBy += "date"
case "3": case "3":
search.Sort = Downloads search.Sort = common.Downloads
orderBy += "downloads" orderBy += "downloads"
case "4": case "4":
search.Sort = Size search.Sort = common.Size
orderBy += "filesize" orderBy += "filesize"
default: default:
orderBy += "torrent_id" orderBy += "torrent_id"
@ -132,66 +94,63 @@ func searchByQuery(r *http.Request, pagenum int, countAll bool) (search SearchPa
orderBy += "desc" orderBy += "desc"
} }
userID := r.URL.Query().Get("userID") tor, err = cache.Get(search, func() (tor []model.Torrent, err error) {
parameters := torrentService.WhereParams{
Params: make([]interface{}, 0, 64),
}
conditions := make([]string, 0, 64)
if search.Category.Main != 0 {
conditions = append(conditions, "category = ?")
parameters.Params = append(parameters.Params, string(catString[0]))
}
if search.Category.Sub != 0 {
conditions = append(conditions, "sub_category = ?")
parameters.Params = append(parameters.Params, string(catString[2]))
}
if search.Status != 0 {
if search.Status == 3 {
conditions = append(conditions, "status != ?")
} else {
conditions = append(conditions, "status = ?")
}
parameters.Params = append(parameters.Params, strconv.Itoa(int(search.Status)+1))
}
parameters := torrentService.WhereParams{ searchQuerySplit := strings.Fields(search.Query)
Params: make([]interface{}, 0, 64), for i, word := range searchQuerySplit {
} firstRune, _ := utf8.DecodeRuneInString(word)
conditions := make([]string, 0, 64) if len(word) == 1 && unicode.IsPunct(firstRune) {
if search.Category.Main != 0 { // some queries have a single punctuation character
conditions = append(conditions, "category = ?") // which causes a full scan instead of using the index
parameters.Params = append(parameters.Params, string(catString[0])) // and yields no meaningful results.
} // due to len() == 1 we're just looking at 1-byte/ascii
if search.Category.Sub != 0 { // punctuation characters.
conditions = append(conditions, "sub_category = ?") continue
parameters.Params = append(parameters.Params, string(catString[2])) }
}
if userID != "" { // SQLite has case-insensitive LIKE, but no ILIKE
conditions = append(conditions, "uploader = ?") var operator string
parameters.Params = append(parameters.Params, userID) if db.ORM.Dialect().GetName() == "sqlite3" {
} operator = "LIKE ?"
if search.Status != 0 { } else {
if search.Status == 3 { operator = "ILIKE ?"
conditions = append(conditions, "status != ?") }
// TODO: make this faster ?
conditions = append(conditions, "torrent_name "+operator)
parameters.Params = append(parameters.Params, "%"+searchQuerySplit[i]+"%")
}
parameters.Conditions = strings.Join(conditions[:], " AND ")
log.Infof("SQL query is :: %s\n", parameters.Conditions)
if countAll {
tor, count, err = torrentService.GetTorrentsOrderBy(&parameters, orderBy, int(search.Max), int(search.Max)*(pagenum-1))
} else { } else {
conditions = append(conditions, "status = ?") tor, err = torrentService.GetTorrentsOrderByNoCount(&parameters, orderBy, int(search.Max), int(search.Max)*(pagenum-1))
}
parameters.Params = append(parameters.Params, strconv.Itoa(int(search.Status)+1))
}
searchQuerySplit := strings.Fields(search.Query)
for i, word := range searchQuerySplit {
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
} }
// TEMP: Workaround to at least make SQLite search testable for return
// development. })
// TODO: Actual case-insensitive search for SQLite
var operator string
if db.ORM.Dialect().GetName() == "sqlite3" {
operator = "LIKE ?"
} else {
operator = "ILIKE ?"
}
// TODO: make this faster ?
conditions = append(conditions, "torrent_name "+operator)
parameters.Params = append(parameters.Params, "%"+searchQuerySplit[i]+"%")
}
parameters.Conditions = strings.Join(conditions[:], " AND ")
log.Infof("SQL query is :: %s\n", parameters.Conditions)
if countAll {
tor, count, err = torrentService.GetTorrentsOrderBy(&parameters, orderBy, int(search.Max), int(search.Max)*(pagenum-1))
} else {
tor, err = torrentService.GetTorrentsOrderByNoCount(&parameters, orderBy, int(search.Max), int(search.Max)*(pagenum-1))
}
return return
} }