aboutsummaryrefslogtreecommitdiff
path: root/cmd/mybittorrent/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/mybittorrent/client.go')
-rw-r--r--cmd/mybittorrent/client.go136
1 files changed, 136 insertions, 0 deletions
diff --git a/cmd/mybittorrent/client.go b/cmd/mybittorrent/client.go
new file mode 100644
index 0000000..f3988b6
--- /dev/null
+++ b/cmd/mybittorrent/client.go
@@ -0,0 +1,136 @@
+package main
+
+import (
+ "encoding/binary"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
+
+ bencode "github.com/jackpal/bencode-go"
+)
+
+type Client struct {
+ PeerId string
+ Port int
+ Torrents map[string]ClientTorrent
+}
+
+type ClientTorrent struct {
+ Meta Meta
+ Uploaded int
+ Downloaded int
+ Left int
+}
+
+type PeerResponse struct {
+ Interval int
+ Peers []string
+}
+
+func NewClient(peerId string, port int) *Client {
+ return &Client{
+ PeerId: peerId,
+ Port: port,
+ Torrents: make(map[string]ClientTorrent),
+ }
+}
+
+func (c *Client) AddTorrentFile(filename string) (ClientTorrent, error) {
+ f, err := os.Open(filename)
+ defer func(f *os.File) {
+ err := f.Close()
+ if err != nil {
+ slog.Error("Failed to close file", "filename", filename)
+ }
+ }(f)
+
+ if err != nil {
+ fmt.Println("Failed to open file: ", os.Args[2])
+ }
+
+ var meta Meta
+
+ if err = bencode.Unmarshal(f, &meta); err != nil {
+ return ClientTorrent{}, err
+ }
+
+ t := ClientTorrent{
+ Meta: meta,
+ Uploaded: 0,
+ Downloaded: 0,
+ Left: meta.Info.Length,
+ }
+
+ c.Torrents[filename] = t
+
+ return t, nil
+}
+
+func (ct ClientTorrent) getUrl(c Client) (string, error) {
+ u, err := url.Parse(ct.Meta.Announce)
+ if err != nil {
+ return "", err
+ }
+
+ q := u.Query()
+
+ infoHash, err := ct.Meta.InfoHash()
+ if err != nil {
+ return "", err
+ }
+
+ q.Set("info_hash", string(infoHash))
+ q.Set("peer_id", c.PeerId)
+ q.Set("port", strconv.Itoa(c.Port))
+ q.Set("uploaded", strconv.Itoa(ct.Uploaded))
+ q.Set("downloaded", strconv.Itoa(ct.Downloaded))
+ q.Set("left", strconv.Itoa(ct.Left))
+ q.Set("compact", "1")
+ u.RawQuery = q.Encode()
+
+ return u.String(), nil
+}
+
+func DecodePeers(b []byte) []string {
+ val := make([]string, 0, len(b)/6)
+ for i := 0; i < len(b); i += 6 {
+ val = append(val, fmt.Sprintf("%d.%d.%d.%d:%d", b[i], b[i+1], b[i+2], b[i+3], binary.BigEndian.Uint16(b[i+4:i+6])))
+ }
+
+ return val
+}
+
+func (c *Client) GetPeers(filename string) (PeerResponse, error) {
+ ct, ok := c.Torrents[filename]
+ if !ok {
+ return PeerResponse{}, fmt.Errorf("missing file from client: %s", filename)
+ }
+
+ u, err := ct.getUrl(*c)
+ if err != nil {
+ return PeerResponse{}, err
+ }
+
+ getResp, err := http.Get(u)
+ if err != nil {
+ return PeerResponse{}, err
+ }
+
+ var resp struct {
+ Interval int
+ Peers string
+ }
+
+ err = bencode.Unmarshal(getResp.Body, &resp)
+ if err != nil {
+ return PeerResponse{}, err
+ }
+
+ return PeerResponse{
+ Interval: resp.Interval,
+ Peers: DecodePeers([]byte(resp.Peers)),
+ }, err
+}