]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Remove metainfo.Builder, and issue #35 test for it
authorMatt Joiner <anacrolix@gmail.com>
Sat, 30 Apr 2016 02:00:12 +0000 (12:00 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Sat, 30 Apr 2016 02:00:12 +0000 (12:00 +1000)
Builder is poorly designed, and issue #35 is poorly written. I don't want to support either of them.

issue35_test.go [deleted file]
metainfo/builder.go [deleted file]

diff --git a/issue35_test.go b/issue35_test.go
deleted file mode 100644 (file)
index ca3d6f9..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-package torrent
-
-import (
-       "errors"
-       "fmt"
-       "io"
-       "os"
-       "path/filepath"
-       "runtime"
-       "testing"
-
-       "github.com/anacrolix/torrent/dht"
-       "github.com/anacrolix/torrent/metainfo"
-)
-
-var numclients int = 0
-
-func cfdir() string {
-       numclients++
-       return filepath.Join(os.TempDir(), "wtp-test/", fmt.Sprintf("%d/", numclients))
-}
-
-func addirs(cf *Config) *Config {
-       d := cfdir()
-       os.MkdirAll(d, 0700)
-       cf.DataDir = filepath.Join(d, "/data")
-       os.MkdirAll(cf.DataDir, 0700)
-       cf.ConfigDir = filepath.Join(d, "/config")
-       os.MkdirAll(cf.ConfigDir, 0700)
-       return cf
-}
-
-func issue35TestingConfig() *Config {
-       return &Config{
-               ListenAddr:           "localhost:0",
-               NoDHT:                false,
-               DisableTrackers:      true,
-               DisableUTP:           false,
-               DisableMetainfoCache: true,
-               DisableIPv6:          true,
-               NoUpload:             false,
-               Seed:                 true,
-               DataDir:              filepath.Join(os.TempDir(), "torrent-test/data"),
-               ConfigDir:            filepath.Join(os.TempDir(), "torrent-test/config"),
-               DHTConfig: dht.ServerConfig{
-                       Passive:            false,
-                       BootstrapNodes:     []string{},
-                       NoSecurity:         false,
-                       NoDefaultBootstrap: true,
-               },
-               Debug: true,
-       }
-}
-
-func writeranddata(path string) error {
-       var w int64
-       var to_write int64 = 1024 * 1024 //1MB
-       f, err := os.Create(path)
-       defer f.Close()
-       if err != nil {
-               return err
-       }
-       rnd, err := os.Open("/dev/urandom")
-       defer rnd.Close()
-       if err != nil {
-               return err
-       }
-       w, err = io.CopyN(f, rnd, to_write)
-       if err != nil {
-               return err
-       }
-       if w != to_write {
-               return errors.New("Short read on /dev/random")
-       }
-       return nil
-}
-
-func TestInfohash(t *testing.T) {
-       os.RemoveAll(filepath.Join(os.TempDir(), "torrent-test"))
-       os.MkdirAll(filepath.Join(os.TempDir(), "torrent-test"), 0700)
-       var cl_one *Client
-       var cf_one *Config
-       var err error
-       if err != nil {
-               t.Fatal(err)
-       }
-       cf_one = issue35TestingConfig()
-       cf_one.ListenAddr = "localhost:43433"
-       cf_one = addirs(cf_one)
-       cl_one, err = NewClient(cf_one)
-       if err != nil {
-               t.Fatal(err)
-       }
-       tfp := filepath.Join(cf_one.DataDir, "testdata")
-       writeranddata(tfp)
-       b := metainfo.Builder{}
-       b.AddFile(tfp)
-       b.AddDhtNodes([]string{"1.2.3.4:5555"})
-       ba, err := b.Submit()
-       if err != nil {
-               t.Fatal(err)
-       }
-       ttfp := filepath.Join(cf_one.ConfigDir, "/../torrent")
-       ttf, err := os.Create(ttfp)
-       if err != nil {
-               t.Fatal(err)
-       }
-       ec, _ := ba.Start(ttf, runtime.NumCPU())
-       err = <-ec
-       if err != nil {
-               t.Fatal(err)
-       }
-       ttf.Close()
-
-       tor, err := cl_one.AddTorrentFromFile(ttfp)
-       if err != nil {
-               t.Fatal(err)
-       }
-       <-tor.GotInfo()
-       tor.DownloadAll()
-       if cl_one.WaitAll() == false {
-               t.Fatal(errors.New("One did not download torrent"))
-       }
-}
diff --git a/metainfo/builder.go b/metainfo/builder.go
deleted file mode 100644 (file)
index af95bf0..0000000
+++ /dev/null
@@ -1,610 +0,0 @@
-package metainfo
-
-import (
-       "crypto/sha1"
-       "errors"
-       "hash"
-       "io"
-       "os"
-       "path/filepath"
-       "sort"
-       "time"
-
-       "github.com/anacrolix/missinggo"
-
-       "github.com/anacrolix/torrent/bencode"
-)
-
-//----------------------------------------------------------------------------
-// Build
-//----------------------------------------------------------------------------
-
-// The Builder type is responsible for .torrent files construction. Just
-// instantiate it, call necessary methods and then call the .Build method. While
-// waiting for completion you can use 'status' channel to get status reports.
-type Builder struct {
-       batch_state
-       filesmap map[string]bool
-}
-
-// Adds a file to the builder queue. You may add one or more files.
-func (b *Builder) AddFile(filename string) {
-       if b.filesmap == nil {
-               b.filesmap = make(map[string]bool)
-       }
-
-       filename, err := filepath.Abs(filename)
-       if err != nil {
-               panic(err)
-       }
-       b.filesmap[filename] = true
-}
-
-// Defines a name of the future torrent file. For single file torrents it's the
-// recommended name of the contained file. For multiple files torrents it's the
-// recommended name of the directory in which all of them will be
-// stored. Calling this function is not required. In case if no name was
-// specified, the builder will try to automatically assign it. It will use the
-// name of the file if there is only one file in the queue or it will try to
-// find the rightmost common directory of all the queued files and use its name as
-// a torrent name. In case if name cannot be assigned automatically, it will use
-// "unknown" as a torrent name.
-func (b *Builder) SetName(name string) {
-       b.name = name
-}
-
-// Sets the length of a piece in the torrent file in bytes. The default is
-// 256kb.
-func (b *Builder) SetPieceLength(length int64) {
-       b.piece_length = length
-}
-
-// Sets the "private" flag. The default is false.
-func (b *Builder) SetPrivate(v bool) {
-       b.private = v
-}
-
-// Add announce URL group. TODO: better explanation.
-func (b *Builder) AddAnnounceGroup(group []string) {
-       b.announce_list = append(b.announce_list, group)
-}
-
-// Add DHT nodes URLs for trackerless mode
-func (b *Builder) AddDhtNodes(group []string) {
-       b.node_list = append(b.node_list, group...)
-}
-
-// Sets creation date. The default is time.Now() when the .Build method was
-// called.
-func (b *Builder) SetCreationDate(date time.Time) {
-       b.creation_date = date
-}
-
-// Sets the comment. The default is no comment.
-func (b *Builder) SetComment(comment string) {
-       b.comment = comment
-}
-
-// Sets the "created by" parameter. The default is "libtorgo".
-func (b *Builder) SetCreatedBy(createdby string) {
-       b.created_by = createdby
-}
-
-// Sets the "encoding" parameter. The default is "UTF-8".
-func (b *Builder) SetEncoding(encoding string) {
-       b.encoding = encoding
-}
-
-// Add WebSeed URL to the list.
-func (b *Builder) AddWebSeedURL(url string) {
-       b.urls = append(b.urls, url)
-}
-
-// Finalizes the Builder state and makes a Batch out of it. After calling that
-// method, Builder becomes empty and you can use it to create another Batch if
-// you will.
-func (b *Builder) Submit() (*Batch, error) {
-       err := b.check_parameters()
-       if err != nil {
-               return nil, err
-       }
-       b.set_defaults()
-
-       batch := &Batch{
-               batch_state: b.batch_state,
-       }
-
-       const non_regular = os.ModeDir | os.ModeSymlink |
-               os.ModeDevice | os.ModeNamedPipe | os.ModeSocket
-
-       // convert a map to a slice, calculate sizes and split paths
-       batch.total_size = 0
-       batch.files = make([]file, 0, 10)
-       for f, _ := range b.filesmap {
-               var file file
-               fi, err := os.Stat(f)
-               if err != nil {
-                       return nil, err
-               }
-
-               if fi.Mode()&non_regular != 0 {
-                       return nil, errors.New(f + " is not a regular file")
-               }
-
-               file.abspath = f
-               file.splitpath = split_path(f)
-               file.size = fi.Size()
-               batch.files = append(batch.files, file)
-               batch.total_size += file.size
-       }
-
-       // find the rightmost common directory
-       if len(batch.files) == 1 {
-               sp := batch.files[0].splitpath
-               batch.default_name = sp[len(sp)-1]
-       } else {
-               common := batch.files[0].splitpath
-               for _, f := range batch.files {
-                       if len(common) > len(f.splitpath) {
-                               common = common[:len(f.splitpath)]
-                       }
-
-                       for i, n := 0, len(common); i < n; i++ {
-                               if common[i] != f.splitpath[i] {
-                                       common = common[:i]
-                                       break
-                               }
-                       }
-
-                       if len(common) == 0 {
-                               break
-                       }
-               }
-
-               if len(common) == 0 {
-                       return nil, errors.New("no common rightmost folder was found for a set of queued files")
-               }
-
-               // found the common folder, let's strip that part from splitpath
-               // and setup the default name
-               batch.default_name = common[len(common)-1]
-
-               lcommon := len(common)
-               for i := range batch.files {
-                       f := &batch.files[i]
-                       f.splitpath = f.splitpath[lcommon:]
-               }
-
-               // and finally sort the files
-               sort.Sort(file_slice(batch.files))
-       }
-
-       // reset the builder state
-       b.batch_state = batch_state{}
-       b.filesmap = nil
-
-       return batch, nil
-}
-
-func (b *Builder) set_defaults() {
-       if b.piece_length == 0 {
-               b.piece_length = 256 * 1024
-       }
-
-       if b.creation_date.IsZero() {
-               b.creation_date = time.Now()
-       }
-
-       if b.created_by == "" {
-               b.created_by = "libtorgo"
-       }
-
-       if b.encoding == "" {
-               b.encoding = "UTF-8"
-       }
-}
-
-func emptyStringsFiltered(ss []string) (ret []string) {
-       for _, s := range ss {
-               if s != "" {
-                       ret = append(ret, s)
-               }
-       }
-       return
-}
-
-func (b *Builder) check_parameters() error {
-       // should be at least one file
-       if len(b.filesmap) == 0 {
-               return errors.New("no files were queued")
-       }
-
-       // let's clean up the announce_list and node_list
-       b.announce_list = cleanUpLists(b.announce_list)
-       b.node_list = emptyStringsFiltered(b.node_list)
-
-       if len(b.announce_list) == 0 && len(b.node_list) == 0 {
-               return errors.New("no announce group or DHT nodes specified")
-       }
-
-       // Either the node_list or announce_list can be present
-       // Never the both!
-       if len(b.announce_list) > 0 && len(b.node_list) > 0 {
-               return errors.New("announce group and nodes are mutually exclusive")
-       }
-
-       // and clean up the urls
-       b.urls = remove_empty_strings(b.urls)
-
-       return nil
-}
-
-func cleanUpLists(list [][]string) [][]string {
-       newList := make([][]string, 0, len(list))
-       for _, l := range list {
-               l = remove_empty_strings(l)
-
-               // discard empty announce groups
-               if len(l) == 0 {
-                       continue
-               }
-               newList = append(newList, l)
-       }
-       return newList
-}
-
-//----------------------------------------------------------------------------
-// Batch
-//----------------------------------------------------------------------------
-
-// Batch represents a snapshot of a builder state, ready for transforming it
-// into a torrent file. Note that Batch contains two accessor methods you might
-// be interested in. The TotalSize is the total size of all the files queued for
-// hashing, you will use it for status reporting. The DefaultName is an
-// automatically determined name of the torrent metainfo, you might want to use
-// it for naming the .torrent file itself.
-type Batch struct {
-       batch_state
-       files        []file
-       total_size   int64
-       default_name string
-}
-
-// Get a total size of all the files queued for hashing. Useful in conjunction
-// with status reports.
-func (b *Batch) TotalSize() int64 {
-       return b.total_size
-}
-
-// Get an automatically determined name of the future torrent metainfo. You can
-// use it for a .torrent file in case user hasn't provided it specifically.
-func (b *Batch) DefaultName() string {
-       return b.default_name
-}
-
-// Starts a process of building the torrent file. This function does everything
-// in a separate goroutine and uses up to 'nworkers' of goroutines to perform
-// SHA1 hashing. Therefore it will return almost immedately. It returns two
-// channels, the first one is for completion awaiting, the second one is for
-// getting status reports. Status report is a number of bytes hashed, you can
-// get the total amount of bytes by inspecting the Batch.TotalSize method return
-// value.
-func (b *Batch) Start(w io.Writer, nworkers int) (<-chan error, <-chan int64) {
-       if nworkers <= 0 {
-               nworkers = 1
-       }
-
-       completion := make(chan error)
-       status := make(chan int64)
-
-       go func() {
-               // prepare workers
-               workers := make([]*worker, nworkers)
-               free_workers := make(chan *worker, nworkers)
-               for i := 0; i < nworkers; i++ {
-                       workers[i] = new_worker(free_workers)
-               }
-               stop_workers := func() {
-                       for _, w := range workers {
-                               w.stop()
-                       }
-                       for _, w := range workers {
-                               w.wait_for_stop()
-                       }
-               }
-
-               // prepare files for reading
-               fr := files_reader{files: b.files}
-               npieces := (b.total_size + b.piece_length - 1) / b.piece_length
-               b.pieces = make([]byte, 20*npieces)
-               hashed := int64(0)
-
-               // read all the pieces passing them to workers for hashing
-               var data []byte
-               for i := int64(0); i < npieces; i++ {
-                       if data == nil {
-                               data = make([]byte, b.piece_length)
-                       }
-
-                       nr, err := fr.Read(data)
-                       if err != nil {
-                               // EOF is not an eror if it was the last piece
-                               if err == io.EOF {
-                                       if i != npieces-1 {
-                                               stop_workers()
-                                               completion <- err
-                                               return
-                                       }
-                               } else {
-                                       stop_workers()
-                                       completion <- err
-                                       return
-                               }
-                       }
-
-                       // cut the data slice to the amount of actual data read
-                       data = data[:nr]
-                       w := <-free_workers
-                       data = w.queue(data, b.pieces[20*i:20*i+20])
-
-                       // update and try to send the status report
-                       if data != nil {
-                               hashed += int64(len(data))
-                               data = data[:cap(data)]
-
-                               select {
-                               case status <- hashed:
-                               default:
-                               }
-                       }
-               }
-               stop_workers()
-
-               // at this point the hash was calculated and we're ready to
-               // write the torrent file
-               err := b.write_torrent(w)
-               if err != nil {
-                       completion <- err
-                       return
-               }
-               completion <- nil
-       }()
-       return completion, status
-}
-
-func (b *Batch) write_torrent(w io.Writer) error {
-       var td MetaInfo
-
-       // Either announce or node lists are allowed - not both
-       if len(b.announce_list) != 0 {
-               td.Announce = b.announce_list[0][0]
-               if len(b.announce_list) != 1 || len(b.announce_list[0]) != 1 {
-                       td.AnnounceList = b.announce_list
-               }
-       }
-
-       missinggo.CastSlice(&td.Nodes, b.node_list)
-       td.CreationDate = b.creation_date.Unix()
-       td.Comment = b.comment
-       td.CreatedBy = b.created_by
-       td.Encoding = b.encoding
-       switch {
-       case len(b.urls) == 0:
-       case len(b.urls) == 1:
-               td.URLList = b.urls[0]
-       default:
-               td.URLList = b.urls
-       }
-
-       td.Info.PieceLength = b.piece_length
-       td.Info.Pieces = b.pieces
-       if b.name == "" {
-               td.Info.Name = b.default_name
-       } else {
-               td.Info.Name = b.name
-       }
-       if len(b.files) == 1 {
-               td.Info.Length = b.files[0].size
-       } else {
-               td.Info.Files = make([]FileInfo, len(b.files))
-               for i, f := range b.files {
-                       td.Info.Files[i] = FileInfo{
-                               Path:   f.splitpath,
-                               Length: f.size,
-                       }
-               }
-       }
-       td.Info.Private = b.private
-
-       e := bencode.NewEncoder(w)
-       return e.Encode(&td)
-}
-
-//----------------------------------------------------------------------------
-// misc stuff
-//----------------------------------------------------------------------------
-
-// splits path into components (dirs and files), works only on absolute paths
-func split_path(path string) []string {
-       var dir, file string
-       s := make([]string, 0, 5)
-
-       dir = path
-       for {
-               dir, file = filepath.Split(filepath.Clean(dir))
-               if file == "" {
-                       break
-               }
-               s = append(s, file)
-       }
-
-       // reverse the slice
-       for i, n := 0, len(s)/2; i < n; i++ {
-               i2 := len(s) - i - 1
-               s[i], s[i2] = s[i2], s[i]
-       }
-
-       return s
-}
-
-// just a common data between the Builder and the Batch
-type batch_state struct {
-       name          string
-       piece_length  int64
-       pieces        []byte
-       private       bool
-       announce_list [][]string
-       node_list     []string
-       creation_date time.Time
-       comment       string
-       created_by    string
-       encoding      string
-       urls          []string
-}
-
-type file struct {
-       abspath   string
-       splitpath []string
-       size      int64
-}
-
-type file_slice []file
-
-func (s file_slice) Len() int           { return len(s) }
-func (s file_slice) Less(i, j int) bool { return s[i].abspath < s[j].abspath }
-func (s file_slice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
-
-func remove_empty_strings(slice []string) []string {
-       j := 0
-       for i, n := 0, len(slice); i < n; i++ {
-               if slice[i] == "" {
-                       continue
-               }
-               slice[j] = slice[i]
-               j++
-       }
-       return slice[:j]
-}
-
-//----------------------------------------------------------------------------
-// worker
-//----------------------------------------------------------------------------
-
-type worker struct {
-       msgbox chan bool
-       hash   hash.Hash
-
-       // request
-       sha1 []byte
-       data []byte
-}
-
-// returns existing 'data'
-func (w *worker) queue(data, sha1 []byte) []byte {
-       d := w.data
-       w.data = data
-       w.sha1 = sha1
-       w.msgbox <- false
-       return d
-}
-
-func (w *worker) stop() {
-       w.msgbox <- true
-}
-
-func (w *worker) wait_for_stop() {
-       <-w.msgbox
-}
-
-func new_worker(out chan<- *worker) *worker {
-       w := &worker{
-               msgbox: make(chan bool),
-               hash:   sha1.New(),
-       }
-       go func() {
-               var sha1 [20]byte
-               for {
-                       if <-w.msgbox {
-                               w.msgbox <- true
-                               return
-                       }
-                       w.hash.Reset()
-                       w.hash.Write(w.data)
-                       w.hash.Sum(sha1[:0])
-                       copy(w.sha1, sha1[:])
-                       out <- w
-               }
-       }()
-       out <- w
-       return w
-}
-
-//----------------------------------------------------------------------------
-// files_reader
-//----------------------------------------------------------------------------
-
-type files_reader struct {
-       files   []file
-       cur     int
-       curfile *os.File
-       off     int64
-}
-
-func (f *files_reader) Read(data []byte) (int, error) {
-       if f.cur >= len(f.files) {
-               return 0, io.EOF
-       }
-
-       if len(data) == 0 {
-               return 0, nil
-       }
-
-       read := 0
-       for len(data) > 0 {
-               file := &f.files[f.cur]
-               if f.curfile == nil {
-                       var err error
-                       f.curfile, err = os.Open(file.abspath)
-                       if err != nil {
-                               return read, err
-                       }
-               }
-
-               // we need to read up to 'len(data)' bytes from current file
-               n := int64(len(data))
-
-               // unless there is not enough data in this file
-               if file.size-f.off < n {
-                       n = file.size - f.off
-               }
-
-               // if there is no data in this file, try next one
-               if n == 0 {
-                       err := f.curfile.Close()
-                       if err != nil {
-                               return read, err
-                       }
-
-                       f.curfile = nil
-                       f.off = 0
-                       f.cur++
-                       if f.cur >= len(f.files) {
-                               return read, io.EOF
-                       }
-                       continue
-               }
-
-               // read, handle errors
-               nr, err := f.curfile.Read(data[:n])
-               read += nr
-               f.off += int64(nr)
-               if err != nil {
-                       return read, err
-               }
-
-               // ok, we've read nr bytes out of len(data), cut the data slice
-               data = data[nr:]
-       }
-
-       return read, nil
-}