Api Last Torrent + xhr automatic refresh
* Added a fiel torrentID in search param * Search can be limited to torrentID > id provided * Templates creation through simple JS object * XHR management through simple JS object * Torrents object that interface with Templates and Query to get new torrent uploaded according to the search context
Cette révision appartient à :
Parent
517b59b02f
révision
402fce9f02
9 fichiers modifiés avec 225 ajouts et 52 suppressions
|
@ -156,13 +156,14 @@ func (c *Category) Parse(s string) (ok bool) {
|
|||
|
||||
// deprecated for TorrentParam
|
||||
type SearchParam struct {
|
||||
Order bool // True means acsending
|
||||
Status Status
|
||||
Sort SortMode
|
||||
Category Category
|
||||
Page int
|
||||
UserID uint
|
||||
Max uint
|
||||
NotNull string
|
||||
Query string
|
||||
TorrentID uint
|
||||
Order bool // True means acsending
|
||||
Status Status
|
||||
Sort SortMode
|
||||
Category Category
|
||||
Page int
|
||||
UserID uint
|
||||
Max uint
|
||||
NotNull string
|
||||
Query string
|
||||
}
|
||||
|
|
|
@ -3,11 +3,12 @@ package common
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
elastic "gopkg.in/olivere/elastic.v5"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
elastic "gopkg.in/olivere/elastic.v5"
|
||||
|
||||
"github.com/NyaaPantsu/nyaa/config"
|
||||
"github.com/NyaaPantsu/nyaa/db"
|
||||
"github.com/NyaaPantsu/nyaa/model"
|
||||
|
@ -31,6 +32,7 @@ type TorrentParam struct {
|
|||
NameLike string // csv
|
||||
}
|
||||
|
||||
// FromRequest : parse a request in torrent param
|
||||
// TODO Should probably return an error ?
|
||||
func (p *TorrentParam) FromRequest(r *http.Request) {
|
||||
var err error
|
||||
|
@ -54,9 +56,15 @@ func (p *TorrentParam) FromRequest(r *http.Request) {
|
|||
}
|
||||
|
||||
// FIXME 0 means no userId defined
|
||||
userId, err := strconv.ParseUint(r.URL.Query().Get("userID"), 10, 32)
|
||||
userID, err := strconv.ParseUint(r.URL.Query().Get("userID"), 10, 32)
|
||||
if err != nil {
|
||||
userId = 0
|
||||
userID = 0
|
||||
}
|
||||
|
||||
// FIXME 0 means no userId defined
|
||||
torrentID, err := strconv.ParseUint(r.URL.Query().Get("torrentID"), 10, 32)
|
||||
if err != nil {
|
||||
torrentID = 0
|
||||
}
|
||||
|
||||
var status Status
|
||||
|
@ -76,7 +84,7 @@ func (p *TorrentParam) FromRequest(r *http.Request) {
|
|||
p.NameLike = nameLike
|
||||
p.Offset = uint32(pagenum)
|
||||
p.Max = uint32(max)
|
||||
p.UserID = uint32(userId)
|
||||
p.UserID = uint32(userID)
|
||||
// TODO Use All
|
||||
p.All = false
|
||||
// TODO Use Full
|
||||
|
@ -86,11 +94,11 @@ func (p *TorrentParam) FromRequest(r *http.Request) {
|
|||
p.Sort = sortMode
|
||||
p.Category = category
|
||||
// FIXME 0 means no TorrentId defined
|
||||
// Do we even need that ?
|
||||
p.TorrentID = 0
|
||||
// Do we even need that ? I do now :p
|
||||
p.TorrentID = uint32(torrentID)
|
||||
}
|
||||
|
||||
// Builds a query string with for es query string query defined here
|
||||
// ToFilterQuery : Builds a query string with for es query string query defined here
|
||||
// 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
|
||||
|
@ -109,9 +117,14 @@ func (p *TorrentParam) ToFilterQuery() string {
|
|||
if p.Status != ShowAll {
|
||||
query += " status:" + p.Status.ToString()
|
||||
}
|
||||
|
||||
if p.TorrentID != 0 {
|
||||
query += " id:>" + strconv.FormatInt(int64(p.TorrentID), 10)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// Find :
|
||||
/* 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
|
||||
|
@ -136,7 +149,7 @@ func (p *TorrentParam) Find(client *elastic.Client) (int64, []model.Torrent, err
|
|||
From(int((p.Offset-1)*p.Max)).
|
||||
Size(int(p.Max)).
|
||||
Sort(p.Sort.ToESField(), p.Order).
|
||||
Sort("_score", false). // Don't put _score before the field sort, it messes with the sorting
|
||||
Sort("_score", false). // Don't put _score before the field sort, it messes with the sorting
|
||||
FetchSourceContext(fsc)
|
||||
|
||||
filterQueryString := p.ToFilterQuery()
|
||||
|
@ -191,6 +204,7 @@ func (p *TorrentParam) Find(client *elastic.Client) (int64, []model.Torrent, err
|
|||
|
||||
}
|
||||
|
||||
// Clone : To clone a torrent params
|
||||
func (p *TorrentParam) Clone() TorrentParam {
|
||||
return TorrentParam{
|
||||
Order: p.Order,
|
||||
|
|
|
@ -52,25 +52,27 @@ function toggleLayer(elem) {
|
|||
else
|
||||
elem.classList.add("hide");
|
||||
}
|
||||
function parseAllDates() {
|
||||
// Date formatting
|
||||
var lang = document.getElementsByTagName("html")[0].getAttribute("lang");
|
||||
var ymdOpt = { year: "numeric", month: "short", day: "numeric" };
|
||||
var hmOpt = { hour: "numeric", minute: "numeric" };
|
||||
|
||||
// Date formatting
|
||||
var lang = document.getElementsByTagName("html")[0].getAttribute("lang");
|
||||
var ymdOpt = { year: "numeric", month: "short", day: "numeric" };
|
||||
var hmOpt = { hour: "numeric", minute: "numeric" };
|
||||
var list = document.getElementsByClassName("date-short");
|
||||
for(var i in list) {
|
||||
var e = list[i];
|
||||
e.title = e.innerText;
|
||||
e.innerText = new Date(e.innerText).toLocaleString(lang, ymdOpt);
|
||||
}
|
||||
|
||||
var list = document.getElementsByClassName("date-short");
|
||||
for(var i in list) {
|
||||
var e = list[i];
|
||||
e.title = e.innerText;
|
||||
e.innerText = new Date(e.innerText).toLocaleString(lang, ymdOpt);
|
||||
}
|
||||
|
||||
var list = document.getElementsByClassName("date-full");
|
||||
for(var i in list) {
|
||||
var e = list[i];
|
||||
e.title = e.innerText;
|
||||
e.innerText = new Date(e.innerText).toLocaleString(lang);
|
||||
var list = document.getElementsByClassName("date-full");
|
||||
for(var i in list) {
|
||||
var e = list[i];
|
||||
e.title = e.innerText;
|
||||
e.innerText = new Date(e.innerText).toLocaleString(lang);
|
||||
}
|
||||
}
|
||||
parseAllDates();
|
||||
/*Fixed-Navbar offset fix*/
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
var shiftWindow = function() { scrollBy(0, -70) };
|
||||
|
|
16
public/js/query.js
Fichier normal
16
public/js/query.js
Fichier normal
|
@ -0,0 +1,16 @@
|
|||
var Query = {
|
||||
Get: function(url, renderer, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
console.log(url)
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = 'json';
|
||||
xhr.onload = function(e) {
|
||||
if (this.status == 200) {
|
||||
console.log("Getting results...")
|
||||
renderer(this.response);
|
||||
callback(this.response)
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
}
|
24
public/js/template.js
Fichier normal
24
public/js/template.js
Fichier normal
|
@ -0,0 +1,24 @@
|
|||
// Templates variable
|
||||
var Templates = {
|
||||
tmpl: [],
|
||||
Add: function(templateName, template) {
|
||||
this.tmpl[templateName] = template
|
||||
},
|
||||
Render: function(templateName, model) {
|
||||
console.log(model)
|
||||
return this.tmpl[templateName](model)
|
||||
},
|
||||
ApplyItemListRenderer: function(params) {
|
||||
return function(models) {
|
||||
console.log("Parsing results...")
|
||||
for (var i=0; i < models.length; i++) {
|
||||
var object = Templates.Render(params.templateName, models[i]);
|
||||
if (params.method == "append") {
|
||||
params.element.innerHTML = params.element.innerHTML + object
|
||||
} else if (params.method == "prepend") {
|
||||
params.element.innerHTML = object + params.element.innerHTML
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
51
public/js/torrents.js
Fichier normal
51
public/js/torrents.js
Fichier normal
|
@ -0,0 +1,51 @@
|
|||
var Torrents = {
|
||||
CanRefresh: false,
|
||||
timeout: undefined,
|
||||
Seconds: 3,
|
||||
SearchURL: "/api/search",
|
||||
Method: "prepend",
|
||||
LastID: 0,
|
||||
StopRefresh: function() {
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = undefined
|
||||
},
|
||||
StartRefresh: function() {
|
||||
console.log("Start Refresh...")
|
||||
this.timeout = setTimeout(function() {
|
||||
var searchArgs = (window.location.search != "") ? window.location.search.substr(1) : ""
|
||||
searchArgs = (Torrents.LastID > 0) ? "?torrentID="+Torrents.LastID+"&"+searchArgs : "?"+searchArgs
|
||||
Query.Get(Torrents.SearchURL+searchArgs,
|
||||
Templates.ApplyItemListRenderer({
|
||||
templateName: "torrents.item", method: "prepend", element: document.getElementById("torrentListResults")
|
||||
}), function(torrents) {
|
||||
for (var i =0; i < torrents.length; i++) { if (Torrents.LastID < torrents[i].id) Torrents.LastID = torrents[i].id; }
|
||||
parseAllDates();
|
||||
Torrents.StartRefresh()
|
||||
});
|
||||
}, this.Seconds*1000);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if (Torrents.CanRefresh) {
|
||||
Torrents.StartRefresh()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Credits to mpen (StackOverflow)
|
||||
function humanFileSize(bytes, si) {
|
||||
var thresh = si ? 1000 : 1024;
|
||||
if(Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
var units = si
|
||||
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
|
||||
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
||||
var u = -1;
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
return bytes.toFixed(1)+' '+units[u];
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/NyaaPantsu/nyaa/config"
|
||||
"github.com/NyaaPantsu/nyaa/model"
|
||||
"github.com/NyaaPantsu/nyaa/service/user/permission"
|
||||
"github.com/NyaaPantsu/nyaa/util"
|
||||
"github.com/NyaaPantsu/nyaa/util/categories"
|
||||
|
@ -193,11 +194,33 @@ var FuncMap = template.FuncMap{
|
|||
return config.DefaultUserSettings[s]
|
||||
},
|
||||
"makeTreeViewData": func(f *filelist.FileListFolder, nestLevel int, T publicSettings.TemplateTfunc, identifierChain string) interface{} {
|
||||
return struct{
|
||||
Folder *filelist.FileListFolder
|
||||
NestLevel int
|
||||
T publicSettings.TemplateTfunc
|
||||
return struct {
|
||||
Folder *filelist.FileListFolder
|
||||
NestLevel int
|
||||
T publicSettings.TemplateTfunc
|
||||
IdentifierChain string
|
||||
}{ f, nestLevel, T, identifierChain }
|
||||
}{f, nestLevel, T, identifierChain}
|
||||
},
|
||||
"lastID": func(currentUrl url.URL, torrents []model.TorrentJSON) int {
|
||||
values := currentUrl.Query()
|
||||
|
||||
order := false
|
||||
sort := "2"
|
||||
|
||||
if _, ok := values["order"]; ok {
|
||||
order, _ = strconv.ParseBool(values["order"][0])
|
||||
}
|
||||
if _, ok := values["sort"]; ok {
|
||||
sort = values["sort"][0]
|
||||
}
|
||||
lastID := 0
|
||||
if sort == "2" || sort == "" {
|
||||
if order {
|
||||
lastID = int(torrents[len(torrents)-1].ID)
|
||||
} else {
|
||||
lastID = int(torrents[0].ID)
|
||||
}
|
||||
}
|
||||
return lastID
|
||||
},
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ Your browser does not support the audio element.
|
|||
<th class="tr-date hide-xs"><a href="{{ genSearchWithOrdering .URL "2" }}">{{call $.T "date"}}<span class="sort-arrows">{{ genSortArrows .URL "2" }}</span></a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="torrentListResults">
|
||||
{{ range .Models}}
|
||||
<tr class="torrent-info
|
||||
{{if eq .Status 2}}remake{{end}}
|
||||
|
@ -84,6 +85,40 @@ Your browser does not support the audio element.
|
|||
<td class="tr-date home-td date-short hide-xs">{{.Date}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
{{ define "footer_js"}}
|
||||
<script type="text/javascript" src="{{ $.URL.Parse "/js/template.js" }}"></script>
|
||||
<script type="text/javascript" src="{{ $.URL.Parse "/js/query.js" }}"></script>
|
||||
<script type="text/javascript" src="{{ $.URL.Parse "/js/torrents.js" }}"></script>
|
||||
<!-- JS Template -->
|
||||
<script type="text/javascript">
|
||||
Templates.Add("torrents.item", function(torrent) {
|
||||
return "<tr class=\"torrent-info"+ ((torrent.status == 2) ? " remake" : ((torrent.status == 3) ? " trusted" : ((torrent.status == 3) ? " aplus" : "" )))+"\">"+
|
||||
"<td class=\"tr-cat home-td\">"+
|
||||
"<a href=\"{{$.URL.Parse "/search?c=" }}"+ torrent.category + "_" + torrent.sub_category +"\">"+
|
||||
{{ if Sukebei }}
|
||||
"<img src=\"{{ $.URL.Parse "/img/torrents/sukebei/" }}"+ torrent.category + torrent.sub_category+".png\" title=\""+ torrent.CategoryName +"\">"+
|
||||
{{ else }}
|
||||
"<img src=\"{{ $.URL.Parse "/img/torrents/" }}"+ torrent.sub_category +".png\" title=\""+ torrent.CategoryName +"\">"+
|
||||
{{ end }}
|
||||
"</a>"+
|
||||
"</td>"+
|
||||
"<td class=\"tr-name home-td\"><a href=\"/view/"+torrent.id+"\">"+ torrent.name +"</a></td>"+
|
||||
"<td class=\"tr-links home-td\">"+
|
||||
"<a href=\""+torrent.magnet +"\" title=\"{{ call $.T "magnet_link" }}\">"+
|
||||
"<div class=\"magnet-icon\"></div>"+
|
||||
"</a>"+(torrent.torrent != "" ? "<a href=\""+torrent.torrent+"\" title=\"{{ call $.T "torrent_file" }}\"><div class=\"download-icon\"></div></a>" : "") +
|
||||
"</td>"+
|
||||
"<td class=\"tr-size home-td hide-xs\">"+humanFileSize(torrent.filesize)+"</td>"+
|
||||
"<td class=\"tr-se home-td hide-xs\">"+torrent.seeders+"</td>"+
|
||||
"<td class=\"tr-le home-td hide-xs\">"+torrent.leechers+"</td>"+
|
||||
"<td class=\"tr-dl home-td hide-xs\">"+torrent.completed+"</td>"+
|
||||
"<td class=\"tr-date home-td date-short hide-xs\">"+torrent.date+"</td>"+
|
||||
"</tr>";
|
||||
});
|
||||
Torrents.LastID = {{ lastID .URL .Models }};
|
||||
</script>
|
||||
{{end}}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
elastic "gopkg.in/olivere/elastic.v5"
|
||||
|
||||
"github.com/NyaaPantsu/nyaa/cache"
|
||||
|
@ -83,23 +84,23 @@ func searchByQuery(r *http.Request, pagenum int, countAll bool, withUser bool, d
|
|||
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,
|
||||
TorrentID: uint(torrentParam.TorrentID),
|
||||
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)
|
||||
}
|
||||
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) (
|
||||
|
@ -117,6 +118,8 @@ func searchByQueryPostgres(r *http.Request, pagenum int, countAll bool, withUser
|
|||
search.Query = r.URL.Query().Get("q")
|
||||
userID, _ := strconv.Atoi(r.URL.Query().Get("userID"))
|
||||
search.UserID = uint(userID)
|
||||
torrentID, _ := strconv.Atoi(r.URL.Query().Get("torrentID"))
|
||||
search.TorrentID = uint(torrentID)
|
||||
|
||||
switch s := r.URL.Query().Get("s"); s {
|
||||
case "1":
|
||||
|
@ -216,6 +219,10 @@ func searchByQueryPostgres(r *http.Request, pagenum int, countAll bool, withUser
|
|||
conditions = append(conditions, "uploader = ?")
|
||||
parameters.Params = append(parameters.Params, search.UserID)
|
||||
}
|
||||
if search.TorrentID != 0 {
|
||||
conditions = append(conditions, "torrent_id > ?")
|
||||
parameters.Params = append(parameters.Params, search.TorrentID)
|
||||
}
|
||||
if search.Category.Sub != 0 {
|
||||
conditions = append(conditions, "sub_category = ?")
|
||||
parameters.Params = append(parameters.Params, search.Category.Sub)
|
||||
|
|
Référencer dans un nouveau ticket