func (cl *Client) ID() []byte {
	return cl.peerID[:]
}
ID returns the Client's random ID
package torrent
import (
	"bytes"
	"errors"
	"expvar"
	"fmt"
	"log"
	"net"
	"os"
	"path"
	"sync"
	"time"
	"github.com/anacrolix/dht/v2"
	"github.com/lkslts64/charo-torrent/metainfo"
	"github.com/lkslts64/charo-torrent/peer_wire"
	"github.com/lkslts64/charo-torrent/torrent/storage"
	"github.com/lkslts64/charo-torrent/tracker"
)
const clientID = "CH"
const version = "0001"
const logFileName = "charo.log"
type Client struct {
	config *Config
	peerID [20]byte
	logger *log.Logger
	close  chan struct{}
	listener         listener
	trackerAnnouncer *trackerAnnouncer
	dhtServer        *dht.Server
	reserved               peer_wire.Reserved
	trackerAnnouncerCloseC chan chan struct{}
	port                   int
	counters               *expvar.Map
	mu                     sync.Mutex  
	blackList              []net.IP
	torrents               map[[20]byte]*Torrent
}
type Config struct {
	SelectorF func() PieceSelector
	MaxOnFlightReqs int
	MaxEstablishedConns int
	RejectIncomingConnections bool
	DisableTrackers           bool
	DisableDHT                bool
	BaseDir string
	OpenStorage storage.Open
	DialTimeout time.Duration
	HandshakeTiemout time.Duration
}
func NewClient(cfg *Config) (*Client, error) {
	var err error
	if cfg == nil {
		cfg, err = DefaultConfig()
		if err != nil {
			return nil, err
		}
	}
	cl := &Client{
		peerID:    newPeerID(),
		config:    cfg,
		close:     make(chan struct{}),
		torrents:  make(map[[20]byte]*Torrent),
		blackList: make([]net.IP, 0),
	}
	cl.counters = expvar.NewMap("counters" + string(cl.peerID[:]))
	logPrefix := fmt.Sprintf("client%x ", cl.peerID[14:])  
	logFile, err := os.Create(path.Join(os.TempDir(), logFileName+logPrefix))
	if err != nil {
		return nil, err
	}
	cl.logger = log.New(logFile, logPrefix, log.LstdFlags)
	if !cl.config.RejectIncomingConnections {
		if cl.listener, err = listen(cl); err != nil {
			cl.logger.Fatal(err)
		}
		go func() {
			log.Fatal(cl.acceptForEver())
		}()
	} else {
		cl.config.DisableDHT = true
	}
	if !cl.config.DisableTrackers {
		cl.trackerAnnouncer = &trackerAnnouncer{
			cl:                            cl,
			trackerAnnouncerSubmitEventCh: make(chan trackerAnnouncerEvent, 5),
			trackers:                      make(map[string]*trackerURL),
		}
		go cl.trackerAnnouncer.run()
	}
	if !cl.config.DisableDHT {
		cl.reserved.SetDHT()
		if cl.dhtServer, err = dht.NewServer(nil); err == nil {
			go func() {
				ts, err := cl.dhtServer.Bootstrap()
				if err != nil {
					cl.logger.Printf("error bootstrapping dht: %s", err)
				}
				cl.logger.Printf("dht bootstrap complete with stats %v", ts)
			}()
		} else {
			cl.logger.Fatalf("error creating dht server: %s", err)
		}
	}
	cl.reserved.SetExtended()
	return cl, nil
}
func (cl *Client) Close() {
	close(cl.close)
	if cl.dhtServer != nil {
		cl.dhtServer.Close()
	}
	wg := sync.WaitGroup{}
	wg.Add(len(cl.torrents))
	for _, t := range cl.Torrents() {
		go func(t *Torrent) {
			defer wg.Done()
			t.Close()
		}(t)
	}
	wg.Wait()
}
func DefaultConfig() (*Config, error) {
	dir, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	return &Config{
		SelectorF:           NewDefaultPieceSelector,
		MaxOnFlightReqs:     250,
		MaxEstablishedConns: 55,
		BaseDir:             dir,
		OpenStorage:         storage.OpenFileStorage,
		DialTimeout:         5 * time.Second,
		HandshakeTiemout:    4 * time.Second,
	}, nil
}
func (cl *Client) AddFromFile(filename string) (*Torrent, error) {
	return cl.AddFromParser(&metainfo.FileParser{
		Filename: filename,
	})
}
func (cl *Client) AddFromMagnet(uri string) (*Torrent, error) {
	return cl.AddFromParser(&metainfo.MagnetParser{
		URI: uri,
	})
}
func (cl *Client) AddFromInfoHash(infohash [20]byte) (*Torrent, error) {
	return cl.AddFromParser(&metainfo.InfoHashParser{
		InfoHash: infohash,
	})
}
func (cl *Client) AddFromParser(p metainfo.Parser) (*Torrent, error) {
	var err error
	t := newTorrent(cl)
	t.mi, err = p.Parse()
	if err != nil {
		return nil, err
	}
	t.gotInfoHash()
	if t.mi.InfoBytes != nil {
		t.gotInfo()
	}
	go t.mainLoop()
	ihash := t.mi.Info.Hash
	cl.mu.Lock()
	if _, ok := cl.torrents[ihash]; ok {
		return nil, errors.New("torrent already exists")
	}
	cl.torrents[ihash] = t
	cl.mu.Unlock()
	return t, nil
}
func (cl *Client) dropTorrent(infohash [20]byte) error {
	cl.mu.Lock()
	defer cl.mu.Unlock()
	if _, ok := cl.torrents[infohash]; !ok {
		return errors.New("torrent doesn't exist")
	}
	delete(cl.torrents, infohash)
	return nil
}
func (cl *Client) Torrents() []*Torrent {
	cl.mu.Lock()
	defer cl.mu.Unlock()
	ts := []*Torrent{}
	for _, t := range cl.torrents {
		ts = append(ts, t)
	}
	return ts
}
func (cl *Client) ListenPort() int {
	return cl.port
}