Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] On-demand filelist fetching #1719

Open
wants to merge 52 commits into
base: dev
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
22e1c7e
Update stats.go
Kiloutre Nov 7, 2017
f226f1a
Update stats.go
Kiloutre Nov 7, 2017
89fd690
Update stats.go
Kiloutre Nov 7, 2017
58c6d09
add loading_file_list
Kiloutre Nov 7, 2017
a740a23
Update CHANGELOG.md
Kiloutre Nov 7, 2017
68da680
Update view.jet.html
Kiloutre Nov 7, 2017
1b1fd28
prevent filelist from being loaded
Kiloutre Nov 7, 2017
8e3d53c
Update view.jet.html
Kiloutre Nov 7, 2017
c3cb627
Update stats.go
Kiloutre Nov 7, 2017
557047c
Update view.jet.html
Kiloutre Nov 7, 2017
11567bf
Update view.jet.html
Kiloutre Nov 8, 2017
ef30870
Update torrent.go
Kiloutre Nov 8, 2017
1219e69
Update torrent.go
Kiloutre Nov 8, 2017
2087c27
Merge branch 'dev' into filelist-fetching
Kiloutre Nov 8, 2017
ceb02d1
Update file.go
Kiloutre Nov 8, 2017
ed8cf8c
Update stats.go
Kiloutre Nov 8, 2017
b6b1414
Update torrent.go
Kiloutre Nov 8, 2017
1d8a324
constantly preload filelists
Kiloutre Nov 8, 2017
d3480ad
Update treeview.jet.html
Kiloutre Nov 8, 2017
479eb02
Update treeview.jet.html
Kiloutre Nov 8, 2017
49ead03
Update stats.go
Kiloutre Nov 8, 2017
77e602b
Update router.go
Kiloutre Nov 8, 2017
8f28efc
Add files via upload
Kiloutre Nov 8, 2017
c1658bc
Update template.go
Kiloutre Nov 8, 2017
e703dea
Update template_test.go
Kiloutre Nov 8, 2017
ef49289
Add files via upload
Kiloutre Nov 8, 2017
18000fb
Update CHANGELOG.md
Kiloutre Nov 8, 2017
bd8a8b7
Update en-us.all.json
Kiloutre Nov 8, 2017
9f7ee65
Update filelist.jet.html
Kiloutre Nov 8, 2017
ba503c8
Update en-us.all.json
Kiloutre Nov 8, 2017
1d90dcf
Update CHANGELOG.md
Kiloutre Nov 8, 2017
9ffb5f1
Update view.jet.html
Kiloutre Nov 8, 2017
cb8604d
failsafe for empty names
Kiloutre Nov 8, 2017
08b6984
Merge branch 'dev' into filelist-fetching
Kiloutre Nov 8, 2017
c943839
remove "
Kiloutre Nov 8, 2017
1c82814
Update en-us.all.json
Kiloutre Nov 8, 2017
95daf79
Update en-us.all.json
Kiloutre Nov 8, 2017
4bd364e
Merge branch 'dev' into filelist-fetching
Kiloutre Nov 9, 2017
95cc7fd
ignore http trackers
Kiloutre Nov 10, 2017
43b16ed
Merge branch 'dev' into filelist-fetching
Kiloutre Nov 16, 2017
88a4966
fix travis
Kiloutre Nov 16, 2017
52050b2
fix travis
Kiloutre Nov 16, 2017
0cf75e3
Merge branch 'dev' into filelist-fetching
Kiloutre Nov 24, 2017
01865e8
fix missing <script>
Kiloutre Nov 24, 2017
239b953
Use client port defined in config file
Kiloutre Nov 24, 2017
c7ae1cf
Add FilesFetchingClientPort
Kiloutre Nov 24, 2017
b05f3b8
Add FilesFetchingClientPort
Kiloutre Nov 24, 2017
eb29745
Update filesize even if filelist is empty
Kiloutre Nov 24, 2017
81ef4b9
Update files.go
Kiloutre Nov 24, 2017
ec3858e
Update stats.go
Kiloutre Nov 24, 2017
1f2e85c
Merge branch 'dev' into filelist-fetching
ewhal Jan 3, 2018
050d00d
Merge branch 'dev' into filelist-fetching
Kiloutre Jan 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use client port defined in config file
  • Loading branch information
Kiloutre authored Nov 24, 2017
commit 239b953feeeb22c4b088af37dc0ba3f20c35d9c5
195 changes: 152 additions & 43 deletions controllers/torrent/files.go
Original file line number Diff line number Diff line change
@@ -1,78 +1,187 @@
package torrentController

import (
"html/template"
"encoding/hex"
"net/http"
"strings"
"path/filepath"
"strconv"
"strings"
"net/url"
"time"

"github.com/NyaaPantsu/nyaa/models/torrents"
"github.com/NyaaPantsu/nyaa/models"
"github.com/NyaaPantsu/nyaa/templates"
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/utils/log"
"github.com/NyaaPantsu/nyaa/utils/format"
"github.com/NyaaPantsu/nyaa/utils/filelist"
"github.com/Stephen304/goscrape"
"github.com/gin-gonic/gin"

"github.com/anacrolix/dht"
"github.com/anacrolix/torrent"
"github.com/bradfitz/slice"
)

func GetFilesHandler(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 32)
torrent, err := torrents.FindByID(uint(id))
var client *torrent.Client

func initClient() error {
clientConfig := torrent.Config{
DHTConfig: dht.ServerConfig{
StartingNodes: dht.GlobalBootstrapAddrs,
},
ListenAddr: ":" + strconv.Itoa(config.Get().Torrents.FilesFetchingClientPort),
}
cl, err := torrent.NewClient(&clientConfig)
if err != nil {
log.Errorf("error creating client: %s", err)
return err
}
client = cl
return nil
}

// ViewHeadHandler : Controller for getting torrent stats
func GetStatsHandler(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 32)
if err != nil {
c.Status(http.StatusNotFound)
return
}

updateTorrent, err := torrents.FindByID(uint(id))

if len(torrent.FileList) == 0 {
var blankScrape models.Scrape
ScrapeFiles(format.InfoHashToMagnet(strings.TrimSpace(torrent.Hash), torrent.Name, GetTorrentTrackers(torrent)...), torrent, blankScrape, true)
if err != nil {
return
}

folder := filelist.FileListToFolder(torrent.FileList, "root")
templates.TorrentFileList(c, torrent.ToJSON(), folder)
}
var CurrentData models.Scrape
statsExists := !(models.ORM.Where("torrent_id = ?", id).Find(&CurrentData).RecordNotFound())

// ScrapeFiles : Scrape torrent files
func ScrapeFiles(magnet string, torrent *models.Torrent, currentStats models.Scrape, statsExists bool) (error, []FileJSON) {
if client == nil {
err := initClient()
if statsExists && c.Request.URL.Query()["files"] == nil {
//Stats already exist, we check if the torrent stats have been scraped already very recently and if so, we stop there to avoid abuse of the /stats/:id route
if isEmptyScrape(CurrentData) && time.Since(CurrentData.LastScrape).Minutes() <= config.Get().Scrape.MaxStatScrapingFrequencyUnknown {
//Unknown stats but has been scraped less than X minutes ago (X being the limit set in the config file)
return
}
if !isEmptyScrape(CurrentData) && time.Since(CurrentData.LastScrape).Minutes() <= config.Get().Scrape.MaxStatScrapingFrequency {
//Known stats but has been scraped less than X minutes ago (X being the limit set in the config file)
return
}
}

Trackers := GetTorrentTrackers(updateTorrent)

var stats goscrape.Result
var torrentFiles []FileJSON

if c.Request.URL.Query()["files"] != nil {
if len(updateTorrent.FileList) > 0 {
return
}
err, torrentFiles = ScrapeFiles(format.InfoHashToMagnet(strings.TrimSpace(updateTorrent.Hash), updateTorrent.Name, Trackers...), updateTorrent, CurrentData, statsExists)
if err != nil {
return err, []FileJSON{}
return
}
} else {
//Single() returns an array which contain results for each torrent Hash it is fed, since we only feed him one we want to directly access the results
stats = goscrape.Single(Trackers, []string{
updateTorrent.Hash,
})[0]
UpdateTorrentStats(updateTorrent, stats, CurrentData, []torrent.File{}, statsExists)
}

t, _ := client.AddMagnet(magnet)
<-t.GotInfo()

infoHash := t.InfoHash()
dst := make([]byte, hex.EncodedLen(len(t.InfoHash())))
hex.Encode(dst, infoHash[:])
//If we put seeders on -1, the script instantly knows the fetching did not give any result, avoiding having to check all three stats below and in view.jet.html's javascript
if isEmptyResult(stats) {
stats.Seeders = -1
}

var UDP []string
c.JSON(200, gin.H{
"seeders": stats.Seeders,
"leechers": stats.Leechers,
"downloads": stats.Completed,
"filelist": torrentFiles,
"totalsize": fileSize(updateTorrent.Filesize),
})

for _, tracker := range t.Metainfo().AnnounceList[0] {
if strings.HasPrefix(tracker, "udp") {
UDP = append(UDP, tracker)
return
}

// UpdateTorrentStats : Update stats & filelist if files are specified, otherwise just stats
func UpdateTorrentStats(torrent *models.Torrent, stats goscrape.Result, currentStats models.Scrape, Files []torrent.File, statsExists bool) (JSONFilelist []FileJSON) {
if stats.Seeders == -1 {
stats.Seeders = 0
}

if !statsExists {
torrent.Scrape = torrent.Scrape.Create(torrent.ID, uint32(stats.Seeders), uint32(stats.Leechers), uint32(stats.Completed), time.Now())
//Create a stat entry in the DB because none exist
} else {
//Entry in the DB already exists, simply update it
if isEmptyScrape(currentStats) || !isEmptyResult(stats) {
torrent.Scrape = &models.Scrape{torrent.ID, uint32(stats.Seeders), uint32(stats.Leechers), uint32(stats.Completed), time.Now()}
} else {
torrent.Scrape = &models.Scrape{torrent.ID, uint32(currentStats.Seeders), uint32(currentStats.Leechers), uint32(currentStats.Completed), time.Now()}
}
//Only overwrite stats if the old one are Unknown OR if the new ones are not unknown, preventing good stats from being turned into unknown but allowing good stats to be updated to more reliable ones
torrent.Scrape.Update(false)
}
var results goscrape.Result
if len(UDP) != 0 {
udpscrape := goscrape.NewBulk(UDP)
results = udpscrape.ScrapeBulk([]string{torrent.Hash})[0]

if len(Files) > 0 {
files, err := torrent.CreateFileList(Files)

if err != nil {
return
}

JSONFilelist = make([]FileJSON, 0, len(files))
for _, f := range files {
JSONFilelist = append(JSONFilelist, FileJSON{
Path: filepath.Join(f.Path()...),
Filesize: fileSize(f.Filesize),
})
}

// Sort file list by lowercase filename
slice.Sort(JSONFilelist, func(i, j int) bool {
return strings.ToLower(JSONFilelist[i].Path) < strings.ToLower(JSONFilelist[j].Path)
})
}
t.Drop()
return nil, UpdateTorrentStats(torrent, results, currentStats, t.Files(), statsExists)

return
}

// FileJSON for file model in json,
type FileJSON struct {
Path string `json:"path"`
Filesize template.HTML `json:"filesize"`
// GetTorrentTrackers : Get the torrent trackers and add the default ones if they are missing
func GetTorrentTrackers(torrent *models.Torrent) []string {
var Trackers []string
if len(torrent.Trackers) > 3 {
for _, line := range strings.Split(torrent.Trackers[3:], "&tr=") {
tracker, error := url.QueryUnescape(line)
if error == nil && strings.HasPrefix(tracker, "udp") {
Trackers = append(Trackers, tracker)
}
//Cannot scrape from http trackers only keep UDP ones
}
}

for _, tracker := range config.Get().Torrents.Trackers.Default {
if !contains(Trackers, tracker) && strings.HasPrefix(tracker, "udp") {
Trackers = append(Trackers, tracker)
}
}
return Trackers
}

func fileSize(filesize int64) template.HTML {
return template.HTML(format.FileSize(filesize))
}
func isEmptyResult(stats goscrape.Result) bool {
return stats.Seeders == 0 && stats.Leechers == 0 && stats.Completed == 0
}

func isEmptyScrape(stats models.Scrape) bool {
return stats.Seeders == 0 && stats.Leechers == 0 && stats.Completed == 0
}

func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}