func GenerateMetadataInfo(fqfn string) (*MetadataInfo, error) {
	info, err := os.Stat(fqfn)
	if err != nil {
		return nil, err
	}
	if info.Size() == 0 {
		return nil, nil
	}
	file, err := os.Open(fqfn)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	hashCount := int(math.Ceil(float64(info.Size()) / float64(PIECE_LENGTH)))
	var hashes [][]byte
	use_cache := false
	cache_bytes := []byte{}
	cache_fqfn := fqfn + ".mdcache"
	cache_info, err := os.Stat(cache_fqfn)
	if err == nil && cache_info != nil {
		if info.ModTime().After(cache_info.ModTime()) {
			LogDebug("Cache invalid: %s updated more recently than %s", fqfn, cache_fqfn)
		} else {
			cache_bytes, err = ioutil.ReadFile(cache_fqfn)
			if err == nil {
				LogDebug("Loaded %d cached bytes from %s.", len(cache_bytes), cache_fqfn)
				if len(cache_bytes) != hashCount*20 {
					LogError("Cache invalid: length does not match expected size!")
				} else {
					use_cache = true
				}
			}
		}
	}
	if use_cache {
		hashes = make([][]byte, 0, hashCount)
		for i := 0; i < hashCount; i++ {
			idx := i * 20
			hashes = append(hashes, cache_bytes[idx:idx+20])
		}
	} else {
		var bytesRead int64
		var err error
		hashes, bytesRead, err = makeHashes(file, info.Size())
		if err != nil {
			LogFatal("Failed to make hashes for file: %s", err)
			return nil, err
		}
		if int64(bytesRead) != info.Size() {
			LogFatal("Read %d, size %d... mismatch!", bytesRead, info.Size())
			return nil, errors.New("bytesRead invalid")
		}
		if err := ioutil.WriteFile(cache_fqfn, bytes.Join(hashes, []byte{}), 0644); err != nil {
			LogError("Failed to write cache file: %s", err)
			return nil, err
		}
	}
	LogDebug("Generated (or cached) metadata for %s:", fqfn)
	LogDebug(" * Pieces:     %d * %d bytes", hashCount, PIECE_LENGTH)
	LogDebug(" * Hashes:     %d", len(hashes))
	LogDebug(" * First hash: %s", hex.EncodeToString(hashes[0]))
	return &MetadataInfo{
		Name:        filepath.Base(fqfn),
		PieceLength: int(PIECE_LENGTH),
		Pieces:      string(bytes.Join(hashes, []byte{})),
		Length:      info.Size(),
	}, nil
}
GenerateMetadata takes a file and generates the metadata required to serve that file.
package torrent
import (
	"bytes"
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
)
const PIECE_LENGTH = int64(256 * 1024)
type Metadata struct {
	Announce string       `announce`  
	Info     MetadataInfo `info`
}
type MetadataInfo struct {
	Name        string `name`          
	PieceLength int    `piece length`  
	Pieces      string `pieces`        
	Length      int64  `length`
}
func makeHashes(data io.Reader, dataSize int64) ([][]byte, int64, error) {
	hashCount := int(math.Ceil(float64(dataSize) / float64(PIECE_LENGTH)))
	hashes := make([][]byte, 0, hashCount)
	buf := make([]byte, PIECE_LENGTH)
	bytesRead := int64(0)
	for {
		bytesToRead := dataSize - bytesRead
		if bytesToRead < 0 {
			return nil, 0, nil
		}
		if bytesToRead > PIECE_LENGTH {
			bytesToRead = PIECE_LENGTH
		}
		if bytesToRead == 0 {
			break
		}
		n, err := io.ReadAtLeast(data, buf, int(bytesToRead))
		if n == 0 && err == io.EOF {
			break
		} else if err != nil {
			return nil, 0, errors.New(fmt.Sprintf("Failed to read: %s", err))
		}
		hash := sha1.New()
		nw, err := hash.Write(buf[:n])
		if err != nil {
			return nil, 0, errors.New(fmt.Sprintf("Failed to hash chunk: %s", err))
		} else if nw != n {
			return nil, 0, errors.New(fmt.Sprintf("Failed to write to hash; %d != %d", n, nw))
		}
		hashes = append(hashes, hash.Sum(nil))
		bytesRead += int64(n)
	}
	return hashes, bytesRead, nil
}