Skip to content

Commit 48dbddc

Browse files
authored
feat: support to download magnet link from an url (#335)
* feat: support to download magnet link from an url * add unit tests --------- Co-authored-by: rick <[email protected]>
1 parent e1055a7 commit 48dbddc

File tree

9 files changed

+188
-19
lines changed

9 files changed

+188
-19
lines changed

cmd/get.go

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7-
"github.com/linuxsuren/http-downloader/pkg/exec"
7+
"io"
88
"net/http"
99
"net/url"
1010
sysos "os"
@@ -13,6 +13,11 @@ import (
1313
"strings"
1414
"sync"
1515

16+
"github.com/AlecAivazis/survey/v2"
17+
"github.com/linuxsuren/http-downloader/pkg/exec"
18+
"golang.org/x/net/html"
19+
"golang.org/x/net/html/charset"
20+
1621
"github.com/linuxsuren/http-downloader/pkg"
1722
"github.com/linuxsuren/http-downloader/pkg/installer"
1823
"github.com/linuxsuren/http-downloader/pkg/net"
@@ -62,6 +67,7 @@ func newGetCmd(ctx context.Context) (cmd *cobra.Command) {
6267
"Print the category list")
6368
flags.IntVarP(&opt.PrintVersionCount, "print-version-count", "", 20,
6469
"The number of the version list")
70+
flags.BoolVarP(&opt.Magnet, "magnet", "", false, "Fetch magnet list from a website")
6571

6672
_ = cmd.RegisterFlagCompletionFunc("proxy-github", ArrayCompletion("gh.api.99988866.xyz",
6773
"ghproxy.com", "mirror.ghproxy.com"))
@@ -74,6 +80,7 @@ func newDownloadOption(ctx context.Context) *downloadOption {
7480
RoundTripper: getRoundTripper(ctx),
7581
fetcher: &installer.DefaultFetcher{},
7682
wait: &sync.WaitGroup{},
83+
execer: exec.DefaultExecer{},
7784
}
7885
}
7986

@@ -91,6 +98,7 @@ type downloadOption struct {
9198
MaxAttempts int
9299
AcceptPreRelease bool
93100
RoundTripper http.RoundTripper
101+
Magnet bool
94102

95103
ContinueAt int64
96104

@@ -111,6 +119,7 @@ type downloadOption struct {
111119
org string
112120
repo string
113121
fetcher installer.Fetcher
122+
execer exec.Execer
114123
ExpectVersion string // should be like >v1.1.0
115124
}
116125

@@ -170,7 +179,7 @@ func (o *downloadOption) preRunE(cmd *cobra.Command, args []string) (err error)
170179

171180
targetURL := args[0]
172181
o.Package = &installer.HDConfig{}
173-
if strings.HasPrefix(targetURL, "magnet:?") {
182+
if o.Magnet || strings.HasPrefix(targetURL, "magnet:?") {
174183
// download via external tool
175184
o.URL = targetURL
176185
return
@@ -208,6 +217,21 @@ func (o *downloadOption) preRunE(cmd *cobra.Command, args []string) (err error)
208217
return
209218
}
210219

220+
func findAnchor(n *html.Node) (items []string) {
221+
if n.Type == html.ElementNode && n.Data == "a" {
222+
for _, a := range n.Attr {
223+
if a.Key == "href" && strings.Contains(a.Val, "magnet") {
224+
items = append(items, strings.TrimSpace(n.FirstChild.Data))
225+
break
226+
}
227+
}
228+
}
229+
for c := n.FirstChild; c != nil; c = c.NextSibling {
230+
items = append(items, findAnchor(c)...)
231+
}
232+
return
233+
}
234+
211235
func (o *downloadOption) runE(cmd *cobra.Command, args []string) (err error) {
212236
defer func() {
213237
if o.cancel != nil {
@@ -247,8 +271,8 @@ func (o *downloadOption) runE(cmd *cobra.Command, args []string) (err error) {
247271
return
248272
}
249273

250-
if strings.HasPrefix(o.URL, "magnet:?") {
251-
err = downloadMagnetFile(o.ProxyGitHub, o.URL)
274+
if o.Magnet || strings.HasPrefix(o.URL, "magnet:?") {
275+
err = downloadMagnetFile(o.ProxyGitHub, o.URL, o.execer)
252276
return
253277
}
254278

@@ -273,9 +297,8 @@ func (o *downloadOption) runE(cmd *cobra.Command, args []string) (err error) {
273297
return
274298
}
275299

276-
func downloadMagnetFile(proxyGitHub, target string) (err error) {
300+
func downloadMagnetFile(proxyGitHub, target string, execer exec.Execer) (err error) {
277301
targetCmd := "gotorrent"
278-
execer := exec.DefaultExecer{}
279302
is := installer.Installer{
280303
Provider: "github",
281304
Execer: execer,
@@ -287,6 +310,43 @@ func downloadMagnetFile(proxyGitHub, target string) (err error) {
287310
return
288311
}
289312

313+
if strings.HasPrefix(target, "http") {
314+
var resp *http.Response
315+
if resp, err = http.Get(target); err == nil && resp.StatusCode == http.StatusOK {
316+
var data []byte
317+
data, err = io.ReadAll(resp.Body)
318+
if err != nil {
319+
return
320+
}
321+
322+
var reader io.Reader
323+
if reader, err = charset.NewReader(strings.NewReader(string(data)), "UTF-8"); err != nil {
324+
return
325+
}
326+
327+
var docutf8 *html.Node
328+
if docutf8, err = html.Parse(reader); err != nil {
329+
return
330+
}
331+
items := findAnchor(docutf8)
332+
333+
if len(items) > 1 {
334+
selector := &survey.Select{
335+
Message: "Select item",
336+
Options: items,
337+
}
338+
err = survey.AskOne(selector, &target)
339+
} else if len(items) > 0 {
340+
target = items[0]
341+
}
342+
}
343+
}
344+
345+
fmt.Println(target)
346+
if target == "" || err != nil {
347+
return
348+
}
349+
290350
var targetBinary string
291351
if targetBinary, err = execer.LookPath(targetCmd); err == nil {
292352
sysCallArgs := []string{targetCmd}

cmd/get_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import (
1414
"time"
1515

1616
"github.com/golang/mock/gomock"
17+
"github.com/h2non/gock"
1718
"github.com/linuxsuren/http-downloader/mock/mhttp"
19+
"github.com/linuxsuren/http-downloader/pkg/exec"
1820
"github.com/linuxsuren/http-downloader/pkg/installer"
1921
"github.com/spf13/cobra"
2022
"github.com/stretchr/testify/assert"
@@ -238,3 +240,59 @@ func TestRunE(t *testing.T) {
238240
})
239241
}
240242
}
243+
244+
func TestDownloadMagnetFile(t *testing.T) {
245+
tests := []struct {
246+
name string
247+
proxyGitHub string
248+
target string
249+
prepare func()
250+
execer exec.Execer
251+
expectErr bool
252+
}{{
253+
name: "proxyGitHub and target is empty",
254+
proxyGitHub: "",
255+
target: "",
256+
execer: exec.FakeExecer{},
257+
}, {
258+
name: "failed to download",
259+
proxyGitHub: "",
260+
target: "fake",
261+
execer: exec.FakeExecer{ExpectError: errors.New("error")},
262+
expectErr: true,
263+
}, {
264+
name: "one target item",
265+
proxyGitHub: "",
266+
target: "http://fake.com",
267+
prepare: func() {
268+
gock.New("http://fake.com").
269+
Get("/").
270+
Reply(http.StatusOK).
271+
File("testdata/magnet.html")
272+
},
273+
execer: exec.FakeExecer{},
274+
expectErr: false,
275+
}, {
276+
name: "HTTP server error response",
277+
proxyGitHub: "",
278+
target: "http://fake.com",
279+
prepare: func() {
280+
gock.New("http://fake.com").
281+
Get("/").
282+
ReplyError(errors.New("error"))
283+
},
284+
execer: exec.FakeExecer{},
285+
expectErr: true,
286+
}}
287+
for _, tt := range tests {
288+
t.Run(tt.name, func(t *testing.T) {
289+
defer gock.Off()
290+
if tt.prepare == nil {
291+
tt.prepare = func() {}
292+
}
293+
tt.prepare()
294+
err := downloadMagnetFile(tt.proxyGitHub, tt.target, tt.execer)
295+
assert.Equal(t, tt.expectErr, err != nil, err)
296+
})
297+
}
298+
}

cmd/install_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,10 @@ func TestShouldInstall(t *testing.T) {
151151
assert.True(t, exist)
152152

153153
// not exist
154-
opt.execer = &exec.FakeExecer{ExpectError: errors.New("fake")}
154+
opt.execer = &exec.FakeExecer{
155+
ExpectError: errors.New("fake"),
156+
ExpectLookPathError: errors.New("error"),
157+
}
155158
should, exist = opt.shouldInstall()
156159
assert.True(t, should)
157160
assert.False(t, exist)

cmd/testdata/magnet.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<html>
2+
<head></head>
3+
<table>
4+
<tr>
5+
<td>
6+
<a href="magnet:?xxx">magnet:?xxx</a>
7+
</td>
8+
<td>
9+
<a href="https://github.com">github</a>
10+
</td>
11+
</tr>
12+
</table>
13+
</html>

pkg/exec/fake-command.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ import (
77

88
// FakeExecer is for the unit test purposes
99
type FakeExecer struct {
10-
ExpectError error
11-
ExpectOutput string
12-
ExpectOS string
13-
ExpectArch string
10+
ExpectError error
11+
ExpectLookPathError error
12+
ExpectOutput string
13+
ExpectOS string
14+
ExpectArch string
1415
}
1516

1617
// LookPath is a fake method
1718
func (f FakeExecer) LookPath(path string) (string, error) {
18-
return "", f.ExpectError
19+
return "", f.ExpectLookPathError
1920
}
2021

2122
// Command is a fake method

pkg/exec/fake-command_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import (
99

1010
func TestLookPath(t *testing.T) {
1111
fake := FakeExecer{
12-
ExpectError: errors.New("fake"),
13-
ExpectOutput: "output",
14-
ExpectOS: "os",
15-
ExpectArch: "arch",
12+
ExpectLookPathError: errors.New("fake"),
13+
ExpectOutput: "output",
14+
ExpectOS: "os",
15+
ExpectArch: "arch",
1616
}
1717
_, err := fake.LookPath("")
1818
assert.NotNil(t, err)
1919

20-
fake.ExpectError = nil
20+
fake.ExpectLookPathError = nil
2121
_, err = fake.LookPath("")
2222
assert.Nil(t, err)
2323

pkg/net/fake.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Package net provides net related functions
2+
package net
3+
4+
import "fmt"
5+
6+
// FakeReader is a fake reader for the test purpose
7+
type FakeReader struct {
8+
ExpectErr error
9+
}
10+
11+
// Read is a fake method
12+
func (e *FakeReader) Read(p []byte) (n int, err error) {
13+
err = e.ExpectErr
14+
fmt.Println(err, "fake")
15+
return
16+
}

pkg/net/fake_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package net_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/linuxsuren/http-downloader/pkg/net"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestFakeReader(t *testing.T) {
12+
reader := &net.FakeReader{
13+
ExpectErr: errors.New("error"),
14+
}
15+
_, err := reader.Read(nil)
16+
assert.NotNil(t, err)
17+
}

pkg/os/apt/init_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ func TestCommonCase(t *testing.T) {
3232

3333
errRegistry := &core.FakeRegistry{}
3434
SetInstallerRegistry(errRegistry, exec.FakeExecer{
35-
ExpectError: errors.New("error"),
36-
ExpectOS: "linux",
35+
ExpectLookPathError: errors.New("error"),
36+
ExpectError: errors.New("error"),
37+
ExpectOS: "linux",
3738
})
3839
errRegistry.Walk(func(s string, i core.Installer) {
3940
t.Run(s, func(t *testing.T) {

0 commit comments

Comments
 (0)