aboutsummaryrefslogtreecommitdiff
path: root/models/torrent.go
blob: 960a6de6b2ca778215be24bf0757b1af9e34d0af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package models

import (
	"bytes"
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/felix/dhtsearch/bencode"
	"github.com/felix/dhtsearch/dht"
	"github.com/felix/dhtsearch/krpc"
)

// Data for persistent storage
type Torrent struct {
	ID       int       `json:"-"`
	InfoHash string    `json:"infohash"`
	Name     string    `json:"name"`
	Files    []File    `json:"files" db:"-"`
	Size     int       `json:"size"`
	Seen     time.Time `json:"seen"`
	Tags     []string  `json:"tags" db:"-"`
}

type File struct {
	ID        int    `json:"-"`
	Path      string `json:"path"`
	Size      int    `json:"size"`
	TorrentID int    `json:"torrent_id" db:"torrent_id"`
}

type torrentStore interface {
	saveTorrent(*Torrent) error
	torrentsByHash(hashes dht.Infohash, offset, limit int) (*Torrent, error)
	torrentsByName(query string, offset, limit int) ([]*Torrent, error)
	torrentsByTags(tags []string, offset, limit int) ([]*Torrent, error)
}

func validMetadata(ih dht.Infohash, md []byte) bool {
	info := sha1.Sum(md)
	return bytes.Equal([]byte(ih), info[:])
}

func TorrentFromMetadata(ih dht.Infohash, md []byte) (*Torrent, error) {
	if !validMetadata(ih, md) {
		return nil, fmt.Errorf("infohash does not match metadata")
	}
	info, _, err := bencode.DecodeDict(md, 0)
	if err != nil {
		return nil, err
	}

	// Get the directory or advisory filename
	name, err := krpc.GetString(info, "name")
	if err != nil {
		return nil, err
	}

	bt := Torrent{
		InfoHash: hex.EncodeToString([]byte(ih)),
		Name:     name,
	}

	if files, err := krpc.GetList(info, "files"); err == nil {
		// Multiple file mode
		bt.Files = make([]File, len(files))

		// Files is a list of dicts
		for i, item := range files {
			file := item.(map[string]interface{})

			// Paths is a list of strings
			paths := file["path"].([]interface{})
			path := make([]string, len(paths))
			for j, p := range paths {
				path[j] = p.(string)
			}

			fSize := file["length"].(int)
			bt.Files[i] = File{
				// Assume Unix path sep?
				Path: strings.Join(path[:], string(os.PathSeparator)),
				Size: fSize,
			}
			// Ensure the torrent size totals all files'
			bt.Size = bt.Size + fSize
		}
	} else if length, err := krpc.GetInt(info, "length"); err == nil {
		// Single file mode
		bt.Size = length
	} else {
		return nil, fmt.Errorf("found neither length or files")
	}
	return &bt, nil
}