Skip to content

Commit 2bb0965

Browse files
committed
# This is a combination of 2 commits.
# [toru] big update. moved old download command to 'wget' and made the download command interactively select and download multiple episodes to a specified directory [toru] big update. moved old download command to 'wget' and made the download command interactively select and download multiple episodes to a specified directory # This is the commit message #2: [toru] big update. moved old download command to 'wget' and made the download command interactively select and download multiple episodes to a specified directory
1 parent 65040fb commit 2bb0965

File tree

6 files changed

+358
-37
lines changed

6 files changed

+358
-37
lines changed

cmd/toru/download.go

Lines changed: 166 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,191 @@ package main
33
import (
44
"context"
55
"fmt"
6+
"log"
7+
"os"
8+
"time"
69

10+
"github.com/anacrolix/torrent"
711
"github.com/pterm/pterm"
812
"github.com/sweetbbak/toru/pkg/libtorrent"
13+
"github.com/sweetbbak/toru/pkg/search"
914
)
1015

11-
func DownloadTorrent(cl *libtorrent.Client) error {
12-
var tfile string
16+
//
17+
//
18+
//
19+
//
1320

14-
if download.TorrentFile != "" {
15-
tfile = download.TorrentFile
16-
} else if download.Args.Query != "" {
17-
tfile = download.Args.Query
21+
func DownloadMain(cl *libtorrent.Client) error {
22+
var outputName string
23+
if download.Directory != "" {
24+
outputName = string(download.Directory)
1825
} else {
19-
return fmt.Errorf("download: missing argument (magnet|torrent|url) OR --torrent flag")
26+
outputName = "toru-media"
2027
}
2128

22-
success, _ := pterm.DefaultSpinner.Start("getting torrent info")
29+
// create download dir
30+
if err := CreateOutput(outputName); err != nil {
31+
return err
32+
}
33+
34+
// set download dir lol
35+
cl.SetDownloadDir(outputName)
36+
37+
// no need to serve torrents
38+
// cl.SetServerOFF(true)
39+
tmp := os.TempDir()
40+
opt := libtorrent.SetDataDir(tmp)
41+
42+
if err := cl.Init(opt); err != nil {
43+
return err
44+
}
2345

24-
t, err := cl.AddTorrent(tfile)
46+
torrents, err := DownloadMultiple(cl)
2547
if err != nil {
2648
return err
2749
}
2850

29-
success.Success("Success!")
51+
// Create a multi printer for managing multiple printers
52+
multi := pterm.DefaultMultiPrinter
53+
54+
var pbars []*pterm.ProgressbarPrinter
55+
56+
for _, t := range torrents {
57+
pb, _ := pterm.DefaultProgressbar.WithTotal(100).WithWriter(multi.NewWriter()).Start(TruncateString(t.Name(), 30))
58+
pbars = append(pbars, pb)
59+
}
60+
61+
_, err = multi.Start()
62+
if err != nil {
63+
log.Println(err)
64+
}
65+
3066
ctx, cancel := context.WithCancel(context.Background())
3167

3268
go func() {
33-
Progress(t, ctx)
69+
for {
70+
select {
71+
case <-ctx.Done():
72+
multi.Stop()
73+
return
74+
default:
75+
for i, t := range torrents {
76+
pb := pbars[i]
77+
pc := float64(t.BytesCompleted()) / float64(t.Length()) * 100
78+
numpeers := len(t.PeerConns())
79+
pb.Increment().Current = int(pc)
80+
pb.UpdateTitle(fmt.Sprintf("peers [%02d]", numpeers))
81+
time.Sleep(time.Millisecond * 5)
82+
}
83+
}
84+
}
3485
}()
3586

36-
t.DownloadAll()
37-
if cl.TorrentClient.WaitAll() {
38-
cancel()
39-
return nil
87+
for {
88+
if cl.TorrentClient.WaitAll() {
89+
cancel()
90+
println("done!")
91+
return nil
92+
}
93+
}
94+
95+
}
96+
97+
// TODO: create a function that just handles query building
98+
func SearchAnime(q *Search, term string) (*search.Results, error) {
99+
s := search.NewSearch()
100+
101+
// build the query
102+
if q.Category != "" {
103+
s.Category = q.Category
104+
}
105+
if q.Filter != "" {
106+
s.Filter = q.Filter
107+
}
108+
if q.SortBy != "" {
109+
s.SortBy = q.SortBy
110+
}
111+
if q.SortOrder != "" {
112+
s.SortOrder = q.SortOrder
113+
}
114+
if q.User != "" {
115+
s.User = q.User
116+
}
117+
if q.Args.Query != "" {
118+
s.Args.Query = q.Args.Query
119+
}
120+
if term != "" {
121+
s.Args.Query = q.Args.Query
122+
}
123+
if q.Page != 0 {
124+
s.Page = q.Page
125+
}
126+
127+
if q.Latest {
128+
s = &search.Search{
129+
SortBy: "id",
130+
SortOrder: "desc",
131+
Page: 1,
132+
Category: "subs",
133+
}
134+
}
135+
136+
if options.Proxy != "" {
137+
s.ProxyURL = options.Proxy
138+
}
139+
140+
// make the request for results to nyaa.si
141+
m, err := s.Query()
142+
if err != nil {
143+
return nil, err
144+
}
145+
146+
return m, nil
147+
}
148+
149+
func DownloadMultiple(cl *libtorrent.Client) ([]*torrent.Torrent, error) {
150+
q := &Search{
151+
SortBy: download.SortBy,
152+
SortOrder: download.SortOrder,
153+
User: download.User,
154+
Filter: download.Filter,
155+
Page: download.Page,
156+
Latest: download.Latest,
157+
Category: download.Category,
158+
}
159+
160+
q.Args.Query = download.Query
161+
162+
m, err := SearchAnime(q, download.Query)
163+
if err != nil {
164+
return nil, err
165+
}
166+
167+
choices, err := fzfMenuMulti(m.Media)
168+
if err != nil {
169+
return nil, err
170+
}
171+
172+
var torrents []*torrent.Torrent
173+
for _, item := range choices {
174+
t, err := cl.AddTorrent(item.Magnet)
175+
if err != nil {
176+
log.Println(err)
177+
}
178+
179+
t.DownloadAll()
180+
torrents = append(torrents, t)
181+
}
182+
183+
return torrents, nil
184+
}
185+
186+
func CreateOutput(dir string) error {
187+
_, err := os.Stat(dir)
188+
if err == nil {
189+
return err
40190
} else {
41-
cancel()
42-
return fmt.Errorf("Unable to completely download torrent: %s", t.Name())
191+
return os.MkdirAll(dir, 0o755)
43192
}
44193
}

cmd/toru/flags.go

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,55 @@ type Completions struct {
2727

2828
// Streaming options
2929
type Stream struct {
30-
Magnet string `short:"m" long:"magnet" description:"stream directly from the provided torrent magnet link"`
31-
TorrentFile string `short:"t" long:"torrent" description:"stream directly from the provided torrent file or torrent URL"`
32-
Remove bool ` long:"rm" description:"remove cached files after exiting"`
33-
Latest bool `short:"l" long:"latest" description:"view the latest anime and select an episode"`
34-
FromJson flags.Filename `short:"j" long:"from-json" description:"resume selection from prior search saved as json [see: toru search --help]"`
30+
Magnet string `short:"m" long:"magnet" description:"stream directly from the provided torrent magnet link"`
31+
TorrentFile string `short:"t" long:"torrent" description:"stream directly from the provided torrent file or torrent URL"`
32+
Remove bool `long:"rm" description:"remove cached files after exiting"`
33+
Latest bool `short:"l" long:"latest" description:"view the latest anime and select an episode"`
34+
FromJson flags.Filename `short:"j" long:"from-json" description:"resume selection from prior search saved as json [see: toru search --help]"`
3535

3636
// optional magnet link or torrent file as a trailing argument instead of explicitly defined
3737
Args struct {
3838
Query string
3939
} `positional-args:"yes" positional-arg-name:"TORRENT"`
4040
}
4141

42-
// Downloading options
42+
// Downloading options by selecting items from a query
4343
type Download struct {
44-
Directory string `short:"d" long:"dir" description:"parent directory to download torrents to"`
45-
TorrentFile string `short:"t" long:"torrent" description:"explicitly define torrent magnet|file|url to download"`
44+
Directory string `short:"o" long:"output" description:"parent directory to download torrents to"`
45+
Query string `short:"q" long:"query" description:"query to search for"`
46+
SortBy string `short:"b" long:"sort-by" description:"sort results by a category [size|date|seeders|leechers|downloads]"`
47+
SortOrder string `short:"O" long:"sort-order" description:"sort by ascending or descending: options [asc|desc]" choice:"asc"`
48+
User string `short:"u" long:"user" description:"search for content by a user"`
49+
Filter string `short:"f" long:"filter" description:"filter content. Options: [no-remakes|trusted]"`
50+
Page uint `short:"p" long:"page" description:"which results page to display [default 1]"`
51+
Stream bool `short:"s" long:"stream" description:"stream selected torrents after search"`
52+
Multi bool `short:"m" long:"multi" description:"choose multiple torrents to queue for downloading or streaming"`
53+
Latest bool `short:"n" long:"latest" description:"view the latest anime"`
54+
Category string `short:"c" long:"category" description:"search torrents by a category: run [toru search --list] to see categories"`
4655

4756
// magnet link, torrent or torrent file url
4857
Args struct {
4958
Query string
50-
} `positional-args:"yes" positional-arg-name:"TORRENT"`
59+
} `positional-args:"yes" positional-arg-name:"QUERY"`
60+
}
61+
62+
// Downloading options, moved to simple download section
63+
type WGET struct {
64+
Directory flags.Filename `short:"d" long:"dir" description:"parent directory to download torrents to"`
65+
TorrentFile flags.Filename `short:"t" long:"torrent" description:"explicitly define torrent magnet|file|url to download"`
66+
67+
// magnet link, torrent or torrent file url
68+
Args struct {
69+
Query string
70+
} `positional-args:"yes" positional-arg-name:"QUERY"`
5171
}
5272

5373
type Latest struct{}
5474

5575
// Non-interactive CLI search options
5676
type Search struct {
5777
SortBy string `short:"b" long:"sort-by" description:"sort results by a category [size|date|seeders|leechers|downloads]"`
58-
SortOrder string `short:"o" long:"sort-order" description:"sort by ascending or descending: options [asc|desc]" choice:"asc"`
78+
SortOrder string `short:"o" long:"sort-order" description:"sort by ascending or descending: options [asc|desc]" choice:"asc"`
5979
User string `short:"u" long:"user" description:"search for content by a user"`
6080
Filter string `short:"f" long:"filter" description:"filter content. Options: [no-remakes|trusted]"`
6181
Page uint `short:"p" long:"page" description:"which results page to display [default 1]"`
@@ -74,4 +94,3 @@ type Search struct {
7494
Query string
7595
} `positional-args:"yes" positional-arg-name:"QUERY"`
7696
}
77-

cmd/toru/main.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var (
2222
searchopts Search
2323
download Download
2424
completions Completions
25+
wget WGET
2526
latest Latest
2627
)
2728

@@ -41,11 +42,15 @@ func init() {
4142
if err != nil {
4243
log.Fatal(err)
4344
}
44-
d, err := parser.AddCommand("download", "download torrents", "download torrent from .torrent file, magnet or URL to a .torrent file", &download)
45+
d, err := parser.AddCommand("download", "select one or many torrents to download", "download torrent from .torrent file, magnet or URL to a .torrent file", &download)
4546
if err != nil {
4647
log.Fatal(err)
4748

4849
}
50+
_, err = parser.AddCommand("wget", "wget a torrent file", "wget a torrent file", &wget)
51+
if err != nil {
52+
log.Fatal(err)
53+
}
4954
_, err = parser.AddCommand("latest", "get the latest anime", "get the latest anime from nyaa.si", &latest)
5055
if err != nil {
5156
log.Fatal(err)
@@ -108,6 +113,13 @@ func main() {
108113
cl.TorrentPort = options.TorrentPort
109114
}
110115

116+
if parser.Active.Name == "download" {
117+
if err := DownloadMain(cl); err != nil {
118+
log.Fatal(err)
119+
}
120+
os.Exit(0)
121+
}
122+
111123
if err := cl.Init(); err != nil {
112124
log.Fatal(err)
113125
}
@@ -121,7 +133,7 @@ func main() {
121133
if err := runStream(cl); err != nil {
122134
log.Fatal(err)
123135
}
124-
case "dl", "download":
136+
case "wget":
125137
if err := DownloadTorrent(cl); err != nil {
126138
log.Fatal(err)
127139
}

cmd/toru/search.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,36 @@ func fzfMenu(m []nyaa.Media) (nyaa.Media, error) {
254254

255255
return m[idx], nil
256256
}
257+
258+
func fzfMenuMulti(m []nyaa.Media) ([]nyaa.Media, error) {
259+
idxs, err := fzf.FindMulti(
260+
m,
261+
func(i int) string {
262+
return m[i].Name
263+
},
264+
fzf.WithPreviewWindow(func(i, width, height int) string {
265+
if i == -1 {
266+
return "lol"
267+
}
268+
269+
return FormatMedia(m[i])
270+
271+
}),
272+
)
273+
274+
var matches []nyaa.Media
275+
for _, item := range idxs {
276+
matches = append(matches, m[item])
277+
}
278+
279+
// User has selected nothing
280+
if err != nil {
281+
if errors.Is(err, fzf.ErrAbort) {
282+
os.Exit(0)
283+
} else {
284+
return nil, err
285+
}
286+
}
287+
288+
return matches, nil
289+
}

0 commit comments

Comments
 (0)