Skip to content

Commit

Permalink
metainfo
Browse files Browse the repository at this point in the history
  • Loading branch information
youthlin committed Aug 17, 2024
1 parent 517b563 commit 702c23d
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 54 deletions.
21 changes: 21 additions & 0 deletions model/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package model

import "code.gopub.tech/bencode"

type File bencode.Dict

// Length 文件大小(字节数)
func (f File) Length() int64 {
return bencode.AsInt(f["length"])
}

// Path 路径
func (f File) Path() (path []string) {
list := bencode.AsList(f["path"])
for _, item := range list {
if p, ok := item.(bencode.String); ok {
path = append(path, string(p))
}
}
return
}
88 changes: 88 additions & 0 deletions model/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package model

import (
"crypto/sha1"
"fmt"
"net/url"

"code.gopub.tech/bencode"
)

// Info
type Info bencode.Dict

// Name 建议保存为的文件(夹)名 实际保存时用户也可另行指定
func (i Info) Name() string {
return bencode.AsStr(i["name"])
}

// PieceLength 一个分片的长度(字节数) 通常是 2 的幂次方
func (i Info) PieceLength() int64 {
return bencode.AsInt(i["piece length"])
}

// Pieces 长度是 20 的整数倍; 每 20 位表示一个分片的 SHA1 散列值
func (i Info) Pieces() []byte {
return []byte(bencode.AsString(i["pieces"]))
}

// IsPrivate 是否是私有种子(只能通过追踪器获取对等方信息)
// see bep_0027
func (i Info) IsPrivate() bool {
return bencode.AsInt(i["private"]) == 1
}

// IsSingleFile 是否是单文件
func (i Info) IsSingleFile() bool {
_, ok := i["length"]
return ok
}

// Length 如果是单文件 表示文件大小(字节数)
func (i Info) Length() int64 {
return bencode.AsInt(i["length"])
}

// IsMultiFile 是否是多文件
func (i Info) IsMultiFile() bool {
_, ok := i["files"]
return ok
}

// Files 如果是多文件 表示目录下的文件
func (i Info) Files() (files []File) {
for _, file := range bencode.AsList(i["files"]) {
if f, ok := file.(bencode.Dict); ok {
files = append(files, File(f))
}
}
return
}

func (i Info) TotalSize() int64 {
if i.IsSingleFile() {
return i.Length()
}
var sum int64
for _, file := range i.Files() {
sum += file.Length()
}
return sum
}

// Hash 种子文件的 info_hash 用这个哈希值来识别一个种子
func (i Info) Hash() []byte {
info := bencode.Dict(i).Encode()
hash := sha1.Sum(info)
return hash[:]
}

// HashStr 种子文件的 info_hash 的十六进制表示
func (i Info) HashStr() string {
return fmt.Sprintf("%x", i.Hash())
}

// HashEscape 种子文件的 info_hash 用在 url 上的表示(使用百分号转义)
func (i Info) HashEscape() string {
return url.QueryEscape(string(i.Hash()))
}
117 changes: 73 additions & 44 deletions model/meta.go
Original file line number Diff line number Diff line change
@@ -1,76 +1,105 @@
package model

import (
"math/rand"
"time"

"code.gopub.tech/bencode"
)

type Meta bencode.Dict
// MetaInfo 种子文件
// see bep_0003
type MetaInfo bencode.Dict

func (m Meta) Announce() string {
// Announce 追踪器的地址 (一定有的字段)
func (m MetaInfo) Announce() string {
return bencode.AsStr(m["announce"])
}

func (m Meta) AnnounceList() (list []string) {
for _, item := range bencode.AsList(m["announce-list"]) {
if l := bencode.AsList(item); len(l) == 1 {
if s, ok := l[0].(bencode.String); ok {
list = append(list, string(s))
}
}
}
return
// Info 描述种子文件对应的文件(夹) (一定有的字段)
func (m MetaInfo) Info() Info {
return Info(bencode.AsDict(m["info"]))
}

func (m Meta) Comment() string {
// Comment 注释
func (m MetaInfo) Comment() string {
return bencode.AsStr(m["comment"])
}

func (m Meta) CreationDate() int64 {
// CreationDate 创建时间 秒级时间戳
func (m MetaInfo) CreationDate() int64 {
return bencode.AsInt(m["creation date"])
}

func (m Meta) CreationDateTime() time.Time {
// CreationDateTime 创建时间
func (m MetaInfo) CreationDateTime() time.Time {
sec := m.CreationDate()
return time.Unix(sec, 0)
}

func (m Meta) Info() Info {
return Info(bencode.AsDict(m["info"]))
}

type Info bencode.Dict

func (i Info) Name() string {
return bencode.AsStr(i["name"])
}

func (i Info) PieceLength() int64 {
return bencode.AsInt(i["piece length"])
}

func (i Info) Files() (files []File) {
for _, file := range bencode.AsList(i["files"]) {
if f, ok := file.(bencode.Dict); ok {
files = append(files, File(f))
// AnnounceList 扩展后的追踪器列表
// 如果客户端兼容该字段 应当优先使用这个字段, 并且忽略 announce 字段.
//
// 各层级的通告将按顺序处理;在客户端进入下一层级之前,必须检查每一层级中的所有 URL。
// 每一层级内的 URL 将以随机选择的顺序进行处理;换句话说,首次读取时列表将被打乱,然后按顺序进行解析。
// 此外,如果与跟踪器的连接成功,它将被移到该层级的前面。
//
// 示例
//
// d['announce-list'] = [ [tracker1], [backup1], [backup2] ]
// 每次通告时,首先尝试 追踪器 1, 如果无法联系到 追踪器 1, 则分别尝试 备份 1 和 备份 2.
// 在下一次通告时,按同样的顺序重复。这适用于标准跟踪器无法共享信息的情况。
//
// d['announce-list'] = [[ tracker1, tracker2, tracker3 ]]
// 首先,打乱列表。(为便于讨论,我们假设列表已经被打乱。)
// 然后,如果无法访问 tracker1, 则尝试 tracker2.
// 如果可以访问 tracker2, 那么列表现在是:tracker2, tracker1, tracker3.
// 从那时起,这将是客户端尝试的顺序。
// 如果稍后 tracker2 和 tracker1 都无法访问,但 tracker3 有响应,
// 那么列表将更改为:tracker3, tracker2, tracker1, 并在未来按照该顺序进行尝试。
// 这种形式适用于可以交换对等体信息的跟踪器,并会使客户端帮助平衡跟踪器之间的负载。
//
// d['announce-list'] = [ [ tracker1, tracker2 ], [backup1] ]
// 第一层由 tracker1 和 tracker2 组成,是随机排列的。
// 在客户端尝试连接 backup1 之前,每次通告都会尝试 tracker1 和 tracker2(尽管顺序可能不同)。
//
// see bep_0012
func (m MetaInfo) AnnounceList() (trackers [][]string) {
list := bencode.AsList(m["announce-list"])
trackers = make([][]string, 0, len(list))
for _, tiers := range list {
vals := bencode.AsList(tiers)
urls := make([]string, 0, len(vals))
for _, url := range vals {
urls = append(urls, bencode.AsStr(url))
}
trackers = append(trackers, urls)
}
return
}

type File bencode.Dict

func (f File) Length() int64 {
return bencode.AsInt(f["length"])
}

func (f File) Path() (path []string) {
list := bencode.AsList(f["path"])
for _, item := range list {
if p, ok := item.(bencode.String); ok {
path = append(path, string(p))
// Trackers 获取追踪器列表
func (m MetaInfo) Trackers() *Trackers {
list := m.AnnounceList()
announceList := make([][]string, 0, len(list))
for _, tiers := range list {
urls := make([]string, 0, len(tiers))
for _, url := range tiers {
if url != "" {
urls = append(urls, url)
}
}
if length := len(urls); length > 0 {
rand.Shuffle(int(int32(length)), func(i, j int) {
urls[i], urls[j] = urls[j], urls[i]
})
announceList = append(announceList, urls)
}
}
return
if len(announceList) == 0 {
announceList = append(announceList, []string{m.Announce()})
}
return &Trackers{
AnnounceList: announceList,
}
}
32 changes: 32 additions & 0 deletions model/trackers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package model

// Trackers 追踪器列表
type Trackers struct {
AnnounceList [][]string
// [ [a, b, c], [back1, back2] ]
// tiers 表示等级索引 当前使用第一等级 [a,b,c] 还是第二等级[back1, back2]
// index 表示当前等级追踪器索引
tiers, index int
}

func (t *Trackers) Reset() {
t.tiers, t.index = 0, 0
}

func (t *Trackers) Tiers() int {
return t.tiers
}

func (t *Trackers) Next() string {
return t.AnnounceList[t.tiers][t.index]
}

func (t *Trackers) MarkCurrentFail() {
tiers := t.AnnounceList[t.tiers]
t.index++
if t.index >= len(tiers) {
t.tiers++
t.tiers = t.tiers % len(t.AnnounceList)
t.index = 0
}
}
26 changes: 26 additions & 0 deletions model/trackers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package model_test

import (
"testing"

"code.gopub.tech/assert"
"code.gopub.tech/gbt/model"
)

func TestTrackers(t *testing.T) {
var trackers = &model.Trackers{
AnnounceList: [][]string{
{"tracker1", "tracker2"},
{"backup1", "backup2"},
},
}
assert.Equal(t, trackers.Next(), "tracker1")
trackers.MarkCurrentFail()
assert.Equal(t, trackers.Next(), "tracker2")
trackers.MarkCurrentFail()
assert.Equal(t, trackers.Next(), "backup1")
trackers.MarkCurrentFail()
assert.Equal(t, trackers.Next(), "backup2")
trackers.MarkCurrentFail()
assert.Equal(t, trackers.Next(), "tracker1")
}
4 changes: 2 additions & 2 deletions service/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"code.gopub.tech/gbt/model"
)

func ReadMeta(r io.Reader) (meta model.Meta, err error) {
func ReadMeta(r io.Reader) (meta model.MetaInfo, err error) {
data, err := io.ReadAll(r)
if err != nil {
return
Expand All @@ -16,6 +16,6 @@ func ReadMeta(r io.Reader) (meta model.Meta, err error) {
if err != nil {
return
}
meta = model.Meta(bencode.AsDict(val))
meta = model.MetaInfo(bencode.AsDict(val))
return
}
Loading

0 comments on commit 702c23d

Please sign in to comment.