diff --git a/common/search.go b/common/search.go index f2399c5f..d09ca9c8 100644 --- a/common/search.go +++ b/common/search.go @@ -156,13 +156,15 @@ 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 + FromID uint // Search for torrentID > FromID + Order bool // True means acsending + Status Status + Sort SortMode + Category Category + Page int + UserID uint + Max uint + NotNull string + Query string } diff --git a/common/torrent.go b/common/torrent.go index 01526121..38c1efac 100644 --- a/common/torrent.go +++ b/common/torrent.go @@ -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" @@ -26,11 +27,13 @@ type TorrentParam struct { Offset uint32 UserID uint32 TorrentID uint32 + FromID uint32 NotNull string // csv Null string // csv 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 +57,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 + fromID, err := strconv.ParseUint(r.URL.Query().Get("fromID"), 10, 32) + if err != nil { + fromID = 0 } var status Status @@ -76,7 +85,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 @@ -88,9 +97,11 @@ func (p *TorrentParam) FromRequest(r *http.Request) { // FIXME 0 means no TorrentId defined // Do we even need that ? p.TorrentID = 0 + // Needed to display result after a certain torrentID + p.FromID = uint32(fromID) } -// 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 @@ -116,9 +127,14 @@ func (p *TorrentParam) ToFilterQuery() string { query += " !status:" + p.Status.ToString() } } + + if p.FromID != 0 { + query += " id:>" + strconv.FormatInt(int64(p.FromID), 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 @@ -143,7 +159,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() @@ -198,6 +214,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, @@ -208,6 +225,7 @@ func (p *TorrentParam) Clone() TorrentParam { Offset: p.Offset, UserID: p.UserID, TorrentID: p.TorrentID, + FromID: p.FromID, NotNull: p.NotNull, Null: p.Null, NameLike: p.NameLike, diff --git a/public/js/main.js b/public/js/main.js index ce3c552b..863ec6e2 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -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) }; diff --git a/public/js/query.js b/public/js/query.js new file mode 100644 index 00000000..b71e7448 --- /dev/null +++ b/public/js/query.js @@ -0,0 +1,15 @@ +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) { + renderer(this.response); + callback(this.response) + } + }; + xhr.send(); + } +} \ No newline at end of file diff --git a/public/js/template.js b/public/js/template.js new file mode 100644 index 00000000..f55701e5 --- /dev/null +++ b/public/js/template.js @@ -0,0 +1,36 @@ +// Templates variable +var Templates = { + tmpl: [], + Add: function(templateName, template) { + this.tmpl[templateName] = template + }, + Render: function(templateName, model) { + return this.tmpl[templateName](model) + }, + ApplyItemListRenderer: function(params) { + return function(models) { + for (var i=models.length-1; i >= 0; 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 + } + } + }; + }, + EncodeEntities: function(value) { + return value. + replace(/&/g, '&'). + replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(value) { + var hi = value.charCodeAt(0); + var low = value.charCodeAt(1); + return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; + }). + replace(/([^\#-~| |!])/g, function(value) { + return '&#' + value.charCodeAt(0) + ';'; + }). + replace(//g, '>'); + } +}; diff --git a/public/js/torrents.js b/public/js/torrents.js new file mode 100644 index 00000000..25118ea4 --- /dev/null +++ b/public/js/torrents.js @@ -0,0 +1,58 @@ +var Torrents = { + CanRefresh: false, + timeout: undefined, + Seconds: 300, // Every five minutes, can be overridden directly in home.html (not here is better) + SearchURL: "/api/search", + Method: "prepend", + LastID: 0, + StopRefresh: function() { + clearTimeout(this.timeout) + this.timeout = undefined + this.CanRefresh = false + }, + Refresh: function() { + if (this.CanRefresh) { + console.log("Start Refresh...") + this.timeout = setTimeout(function() { + var searchArgs = (window.location.search != "") ? window.location.search.substr(1) : "" + searchArgs = (Torrents.LastID > 0) ? "?fromID="+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.Refresh() + }); + }, this.Seconds*1000); + } + }, + StartRefresh: function() { + this.CanRefresh = true; + this.Refresh() + } +} + +document.addEventListener("DOMContentLoaded", function() { // if Torrents.CanRefresh is enabled, refresh is automatically done (no need to start it anually) + 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]; +} \ No newline at end of file diff --git a/router/template_functions.go b/router/template_functions.go index 5bea0549..6182a676 100644 --- a/router/template_functions.go +++ b/router/template_functions.go @@ -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 }, } diff --git a/templates/home.html b/templates/home.html index 91b2f83b..390e7287 100644 --- a/templates/home.html +++ b/templates/home.html @@ -37,6 +37,7 @@ Your browser does not support the audio element. {{call $.T "date"}}{{ genSortArrows .URL "2" }} + {{ range .Models}} {{.Date}} {{end}} + {{end}} +{{ define "footer_js"}} + + + + + +{{end}} \ No newline at end of file diff --git a/util/search/search.go b/util/search/search.go index a050b993..c370fca7 100644 --- a/util/search/search.go +++ b/util/search/search.go @@ -6,6 +6,7 @@ import ( "strings" "unicode" "unicode/utf8" + elastic "gopkg.in/olivere/elastic.v5" "github.com/NyaaPantsu/nyaa/cache" @@ -83,23 +84,24 @@ 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), + FromID: uint(torrentParam.FromID), + 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 +119,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) + fromID, _ := strconv.Atoi(r.URL.Query().Get("fromID")) + search.FromID = uint(fromID) switch s := r.URL.Query().Get("s"); s { case "1": @@ -216,6 +220,10 @@ func searchByQueryPostgres(r *http.Request, pagenum int, countAll bool, withUser conditions = append(conditions, "uploader = ?") parameters.Params = append(parameters.Params, search.UserID) } + if search.FromID != 0 { + conditions = append(conditions, "torrent_id > ?") + parameters.Params = append(parameters.Params, search.FromID) + } if search.Category.Sub != 0 { conditions = append(conditions, "sub_category = ?") parameters.Params = append(parameters.Params, search.Category.Sub)