diff options
| author | jet2tlf <jet2tlf@gmail.com> | 2024-06-03 17:54:41 +0000 |
|---|---|---|
| committer | jet2tlf <jet2tlf@gmail.com> | 2024-06-03 17:54:41 +0000 |
| commit | 944cfe99f03034c37b2d963f7048c41c56f21b9f (patch) | |
| tree | b35fe96dedc9efe821cd2ef0f8f902e28c5b42ef /cmd/mybittorrent/client.go | |
| parent | b01c839940e6ea22ddc28aa0c975d3cd3da69e72 (diff) | |
| download | bittorrent-go-944cfe99f03034c37b2d963f7048c41c56f21b9f.tar.gz bittorrent-go-944cfe99f03034c37b2d963f7048c41c56f21b9f.zip | |
codecrafters submit [skip ci]
Diffstat (limited to 'cmd/mybittorrent/client.go')
| -rw-r--r-- | cmd/mybittorrent/client.go | 136 |
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 +} |