func (cs *ContractSet) NewDownloader(host modules.HostDBEntry, id types.FileContractID, hdb hostDB, cancel <-chan struct{}) (_ *Downloader, err error) {
	sc, ok := cs.Acquire(id)
	if !ok {
		return nil, errors.New("invalid contract")
	}
	defer cs.Return(sc)
	contract := sc.header
	sectorPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize)
	if contract.RenterFunds().Cmp(sectorPrice) < 0 {
		return nil, errors.New("contract has insufficient funds to support download")
	}
	defer func() {
		if err != nil && !IsRevisionMismatch(err) {
			hdb.IncrementFailedInteractions(contract.HostPublicKey())
			err = errors.Extend(err, modules.ErrHostFault)
		} else if err == nil {
			hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
		}
	}()
	conn, closeChan, err := initiateRevisionLoop(host, contract, modules.RPCDownload, cancel, cs.rl)
	if IsRevisionMismatch(err) && len(sc.unappliedTxns) > 0 {
		conn, closeChan, err = initiateRevisionLoop(host, sc.unappliedHeader(), modules.RPCDownload, cancel, cs.rl)
		if err != nil {
			return nil, err
		}
		if err := sc.commitTxns(); err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, err
	}
	for _, txn := range sc.unappliedTxns {
		txn.SignalUpdatesApplied()
	}
	sc.unappliedTxns = nil
	return &Downloader{
		contractID:  id,
		contractSet: cs,
		host:        host,
		conn:        conn,
		closeChan:   closeChan,
		deps:        cs.deps,
		hdb:         hdb,
	}, nil
}
NewDownloader initiates the download request loop with a host, and returns a
Downloader.
package proto
import (
	"net"
	"sync"
	"time"
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
	"github.com/NebulousLabs/errors"
)
type Downloader struct {
	closeChan   chan struct{}
	conn        net.Conn
	contractID  types.FileContractID
	contractSet *ContractSet
	deps        modules.Dependencies
	hdb         hostDB
	host        modules.HostDBEntry
	once        sync.Once
}
func (hd *Downloader) Sector(root crypto.Hash) (_ modules.RenterContract, _ []byte, err error) {
	defer extendDeadline(hd.conn, time.Hour)  
	sc, haveContract := hd.contractSet.Acquire(hd.contractID)
	if !haveContract {
		return modules.RenterContract{}, nil, errors.New("contract not present in contract set")
	}
	defer hd.contractSet.Return(sc)
	contract := sc.header  
	sectorPrice := hd.host.DownloadBandwidthPrice.Mul64(modules.SectorSize)
	if contract.RenterFunds().Cmp(sectorPrice) < 0 {
		return modules.RenterContract{}, nil, errors.New("contract has insufficient funds to support download")
	}
	sectorPrice = sectorPrice.MulFloat(1 + hostPriceLeeway)
	rev := newDownloadRevision(contract.LastRevision(), sectorPrice)
	extendDeadline(hd.conn, modules.NegotiateSettingsTime)
	if err := startDownload(hd.conn, hd.host); err != nil {
		return modules.RenterContract{}, nil, err
	}
	walTxn, err := sc.recordDownloadIntent(rev, sectorPrice)
	if err != nil {
		return modules.RenterContract{}, nil, err
	}
	extendDeadline(hd.conn, 2*time.Minute)  
	err = encoding.WriteObject(hd.conn, []modules.DownloadAction{{
		MerkleRoot: root,
		Offset:     0,
		Length:     modules.SectorSize,
	}})
	if err != nil {
		return modules.RenterContract{}, nil, err
	}
	defer func() {
		if err != nil {
			hd.hdb.IncrementFailedInteractions(contract.HostPublicKey())
			err = errors.Extend(err, modules.ErrHostFault)
		} else if err == nil {
			hd.hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
		}
	}()
	if hd.deps.Disrupt("InterruptDownloadBeforeSendingRevision") {
		return modules.RenterContract{}, nil,
			errors.New("InterruptDownloadBeforeSendingRevision disrupt")
	}
	extendDeadline(hd.conn, connTimeout)
	signedTxn, err := negotiateRevision(hd.conn, rev, contract.SecretKey)
	if err == modules.ErrStopResponse {
		defer hd.conn.Close()
	} else if err != nil {
		return modules.RenterContract{}, nil, err
	}
	if hd.deps.Disrupt("InterruptDownloadAfterSendingRevision") {
		return modules.RenterContract{}, nil,
			errors.New("InterruptDownloadAfterSendingRevision disrupt")
	}
	extendDeadline(hd.conn, modules.NegotiateDownloadTime)
	var sectors [][]byte
	if err := encoding.ReadObject(hd.conn, &sectors, modules.SectorSize+16); err != nil {
		return modules.RenterContract{}, nil, err
	} else if len(sectors) != 1 {
		return modules.RenterContract{}, nil, errors.New("host did not send enough sectors")
	}
	sector := sectors[0]
	if uint64(len(sector)) != modules.SectorSize {
		return modules.RenterContract{}, nil, errors.New("host did not send enough sector data")
	} else if crypto.MerkleRoot(sector) != root {
		return modules.RenterContract{}, nil, errors.New("host sent bad sector data")
	}
	if err := sc.commitDownload(walTxn, signedTxn, sectorPrice); err != nil {
		return modules.RenterContract{}, nil, err
	}
	return sc.Metadata(), sector, nil
}
func (hd *Downloader) shutdown() {
	extendDeadline(hd.conn, modules.NegotiateSettingsTime)
	_, _ = verifySettings(hd.conn, hd.host)
	_ = modules.WriteNegotiationStop(hd.conn)
	close(hd.closeChan)
}
func (hd *Downloader) Close() error {
	hd.once.Do(hd.shutdown)
	return hd.conn.Close()
}