From 8f67c1018e551c735fae750d85bb6be5a841c26d Mon Sep 17 00:00:00 2001
From: Leo Balduf <balduf@hm.edu>
Date: Sat, 6 Aug 2016 22:41:33 -0400
Subject: [PATCH] restructure into backend and frontends

---
 tracker.go => backend/backend.go              | 56 ++++++++++-----
 hooks.go => backend/hooks.go                  |  2 +-
 storage.go => backend/storage.go              |  2 +-
 bittorrent/bittorrent.go                      | 26 -------
 cmd/trakr/main.go                             | 72 ++++++++++++++++---
 .../http/bencode/bencode.go                   |  0
 .../http/bencode/decoder.go                   |  0
 .../http/bencode/decoder_test.go              |  0
 .../http/bencode/encoder.go                   |  0
 .../http/bencode/encoder_test.go              |  0
 .../tracker.go => frontends/http/frontend.go  | 32 ++++-----
 {bittorrent => frontends}/http/parser.go      |  0
 .../http/query_params.go                      |  0
 .../http/query_params_test.go                 |  0
 {bittorrent => frontends}/http/writer.go      |  2 +-
 {bittorrent => frontends}/http/writer_test.go |  0
 .../udp/bytepool/bytepool.go                  |  0
 .../udp/connection_id.go                      |  0
 .../udp/connection_id_test.go                 |  0
 .../tracker.go => frontends/udp/frontend.go   | 23 +++---
 {bittorrent => frontends}/udp/parser.go       |  0
 {bittorrent => frontends}/udp/writer.go       |  0
 22 files changed, 132 insertions(+), 83 deletions(-)
 rename tracker.go => backend/backend.go (50%)
 rename hooks.go => backend/hooks.go (99%)
 rename storage.go => backend/storage.go (99%)
 rename {bittorrent => frontends}/http/bencode/bencode.go (100%)
 rename {bittorrent => frontends}/http/bencode/decoder.go (100%)
 rename {bittorrent => frontends}/http/bencode/decoder_test.go (100%)
 rename {bittorrent => frontends}/http/bencode/encoder.go (100%)
 rename {bittorrent => frontends}/http/bencode/encoder_test.go (100%)
 rename bittorrent/http/tracker.go => frontends/http/frontend.go (86%)
 rename {bittorrent => frontends}/http/parser.go (100%)
 rename {bittorrent => frontends}/http/query_params.go (100%)
 rename {bittorrent => frontends}/http/query_params_test.go (100%)
 rename {bittorrent => frontends}/http/writer.go (98%)
 rename {bittorrent => frontends}/http/writer_test.go (100%)
 rename {bittorrent => frontends}/udp/bytepool/bytepool.go (100%)
 rename {bittorrent => frontends}/udp/connection_id.go (100%)
 rename {bittorrent => frontends}/udp/connection_id_test.go (100%)
 rename bittorrent/udp/tracker.go => frontends/udp/frontend.go (91%)
 rename {bittorrent => frontends}/udp/parser.go (100%)
 rename {bittorrent => frontends}/udp/writer.go (100%)

diff --git a/tracker.go b/backend/backend.go
similarity index 50%
rename from tracker.go
rename to backend/backend.go
index a4dab7c..2e00989 100644
--- a/tracker.go
+++ b/backend/backend.go
@@ -15,13 +15,13 @@
 // Package trakr implements a BitTorrent Tracker that supports multiple
 // protocols and configurable Hooks that execute before and after a Response
 // has been delievered to a BitTorrent client.
-package trakr
+package backend
 
 import (
 	"time"
 
-	"github.com/jzelinskie/trakr/bittorrent/http"
-	"github.com/jzelinskie/trakr/bittorrent/udp"
+	"github.com/jzelinskie/trakr/bittorrent"
+	"golang.org/x/net/context"
 )
 
 // GenericConfig is a block of configuration who's structure is unknown.
@@ -30,38 +30,32 @@ type GenericConfig struct {
 	config interface{} `yaml:"config"`
 }
 
-// MultiTracker is a multi-protocol, customizable BitTorrent Tracker.
-type MultiTracker struct {
+// Backend is a multi-protocol, customizable BitTorrent Tracker.
+type Backend struct {
 	AnnounceInterval time.Duration   `yaml:"announce_interval"`
 	GCInterval       time.Duration   `yaml:"gc_interval"`
 	GCExpiration     time.Duration   `yaml:"gc_expiration"`
-	HTTPConfig       http.Config     `yaml:"http"`
-	UDPConfig        udp.Config      `yaml:"udp"`
 	PeerStoreConfig  []GenericConfig `yaml:"storage"`
 	PreHooks         []GenericConfig `yaml:"prehooks"`
 	PostHooks        []GenericConfig `yaml:"posthooks"`
 
-	peerStore   PeerStore
-	httpTracker http.Tracker
-	udpTracker  udp.Tracker
-	closing     chan struct{}
+	peerStore PeerStore
+	closing   chan struct{}
 }
 
 // Stop provides a thread-safe way to shutdown a currently running
-// MultiTracker.
-func (t *MultiTracker) Stop() {
+// Backend.
+func (t *Backend) Stop() {
 	close(t.closing)
 }
 
-// ListenAndServe listens on the protocols and addresses specified in the
-// HTTPConfig and UDPConfig then blocks serving BitTorrent requests until
-// t.Stop() is called or an error is returned.
-func (t *MultiTracker) ListenAndServe() error {
+// Start starts the Backend.
+// It blocks until t.Stop() is called or an error is returned.
+func (t *Backend) Start() error {
 	t.closing = make(chan struct{})
 	// Build an TrackerFuncs from the PreHooks and PostHooks.
 	// Create a PeerStore instance.
-	// Create a HTTP Tracker instance.
-	// Create a UDP Tracker instance.
+	// Make TrackerFuncs available to be used by frontends.
 	select {
 	case <-t.closing:
 		return nil
@@ -69,3 +63,27 @@ func (t *MultiTracker) ListenAndServe() error {
 
 	return nil
 }
+
+// TrackerFuncs is the collection of callback functions provided by the Backend
+// to (1) generate a response from a parsed request, and (2) observe anything
+// after the response has been delivered to the client.
+type TrackerFuncs struct {
+	HandleAnnounce AnnounceHandler
+	HandleScrape   ScrapeHandler
+	AfterAnnounce  AnnounceCallback
+	AfterScrape    ScrapeCallback
+}
+
+// AnnounceHandler is a function that generates a response for an Announce.
+type AnnounceHandler func(context.Context, *bittorrent.AnnounceRequest) (*bittorrent.AnnounceResponse, error)
+
+// AnnounceCallback is a function that does something with the results of an
+// Announce after it has been completed.
+type AnnounceCallback func(*bittorrent.AnnounceRequest, *bittorrent.AnnounceResponse)
+
+// ScrapeHandler is a function that generates a response for a Scrape.
+type ScrapeHandler func(context.Context, *bittorrent.ScrapeRequest) (*bittorrent.ScrapeResponse, error)
+
+// ScrapeCallback is a function that does something with the results of a
+// Scrape after it has been completed.
+type ScrapeCallback func(*bittorrent.ScrapeRequest, *bittorrent.ScrapeResponse)
diff --git a/hooks.go b/backend/hooks.go
similarity index 99%
rename from hooks.go
rename to backend/hooks.go
index 03c4430..484c4b9 100644
--- a/hooks.go
+++ b/backend/hooks.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package trakr
+package backend
 
 import (
 	"fmt"
diff --git a/storage.go b/backend/storage.go
similarity index 99%
rename from storage.go
rename to backend/storage.go
index 02c719a..541742f 100644
--- a/storage.go
+++ b/backend/storage.go
@@ -1,4 +1,4 @@
-package trakr
+package backend
 
 import (
 	"fmt"
diff --git a/bittorrent/bittorrent.go b/bittorrent/bittorrent.go
index 34294fe..20c9b66 100644
--- a/bittorrent/bittorrent.go
+++ b/bittorrent/bittorrent.go
@@ -20,8 +20,6 @@ package bittorrent
 import (
 	"net"
 	"time"
-
-	"golang.org/x/net/context"
 )
 
 // PeerID represents a peer ID.
@@ -108,13 +106,6 @@ type AnnounceResponse struct {
 	IPv6Peers   []Peer
 }
 
-// AnnounceHandler is a function that generates a response for an Announce.
-type AnnounceHandler func(context.Context, *AnnounceRequest) (*AnnounceResponse, error)
-
-// AnnounceCallback is a function that does something with the results of an
-// Announce after it has been completed.
-type AnnounceCallback func(*AnnounceRequest, *AnnounceResponse)
-
 // ScrapeRequest represents the parsed parameters from a scrape request.
 type ScrapeRequest struct {
 	InfoHashes []InfoHash
@@ -133,13 +124,6 @@ type Scrape struct {
 	Incomplete uint32
 }
 
-// ScrapeHandler is a function that generates a response for a Scrape.
-type ScrapeHandler func(context.Context, *ScrapeRequest) (*ScrapeResponse, error)
-
-// ScrapeCallback is a function that does something with the results of a
-// Scrape after it has been completed.
-type ScrapeCallback func(*ScrapeRequest, *ScrapeResponse)
-
 // Peer represents the connection details of a peer that is returned in an
 // announce response.
 type Peer struct {
@@ -171,13 +155,3 @@ type Tracker interface {
 	ListenAndServe() error
 	Stop()
 }
-
-// TrackerFuncs is the collection of callback functions provided to a Tracker
-// to (1) generate a response from a parsed request, and (2) observe anything
-// after the response has been delivered to the client.
-type TrackerFuncs struct {
-	HandleAnnounce AnnounceHandler
-	HandleScrape   ScrapeHandler
-	AfterAnnounce  AnnounceCallback
-	AfterScrape    ScrapeCallback
-}
diff --git a/cmd/trakr/main.go b/cmd/trakr/main.go
index f250c87..f44d764 100644
--- a/cmd/trakr/main.go
+++ b/cmd/trakr/main.go
@@ -14,13 +14,18 @@ import (
 	"github.com/spf13/cobra"
 	"gopkg.in/yaml.v2"
 
-	"github.com/jzelinskie/trakr"
+	"github.com/jzelinskie/trakr/backend"
+
+	httpfrontend "github.com/jzelinskie/trakr/frontends/http"
+	udpfrontend "github.com/jzelinskie/trakr/frontends/udp"
 )
 
 type ConfigFile struct {
 	Config struct {
 		PrometheusAddr string `yaml:"prometheus_addr"`
-		trakr.MultiTracker
+		backend.Backend
+		HTTPConfig httpfrontend.Config `yaml:"http"`
+		UDPConfig  udpfrontend.Config  `yaml:"udp"`
 	} `yaml:"trakr"`
 }
 
@@ -89,15 +94,66 @@ func main() {
 					}
 				}()
 
+				errChan := make(chan error)
+				closedChan := make(chan struct{})
+
 				go func() {
-					shutdown := make(chan os.Signal)
-					signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM)
-					<-shutdown
-					configFile.Config.MultiTracker.Stop()
+					if err := configFile.Config.Backend.Start(); err != nil {
+						errChan <- errors.New("failed to cleanly shutdown: " + err.Error())
+					}
 				}()
 
-				if err := configFile.Config.MultiTracker.ListenAndServe(); err != nil {
-					return errors.New("failed to cleanly shutdown: " + err.Error())
+				var hFrontend *httpfrontend.Frontend
+				var uFrontend *udpfrontend.Frontend
+
+				if configFile.Config.HTTPConfig.Addr != "" {
+					// TODO get the real TrackerFuncs
+					hFrontend = httpfrontend.NewFrontend(backend.TrackerFuncs{}, configFile.Config.HTTPConfig)
+
+					go func() {
+						log.Println("started serving HTTP on", configFile.Config.HTTPConfig.Addr)
+						if err := hFrontend.ListenAndServe(); err != nil {
+							errChan <- errors.New("failed to cleanly shutdown HTTP frontend: " + err.Error())
+						}
+					}()
+				}
+
+				if configFile.Config.UDPConfig.Addr != "" {
+					// TODO get the real TrackerFuncs
+					uFrontend = udpfrontend.NewFrontend(backend.TrackerFuncs{}, configFile.Config.UDPConfig)
+
+					go func() {
+						log.Println("started serving UDP on", configFile.Config.UDPConfig.Addr)
+						if err := uFrontend.ListenAndServe(); err != nil {
+							errChan <- errors.New("failed to cleanly shutdown UDP frontend: " + err.Error())
+						}
+					}()
+				}
+
+				shutdown := make(chan os.Signal)
+				signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM)
+				go func() {
+					<-shutdown
+
+					if uFrontend != nil {
+						uFrontend.Stop()
+					}
+
+					if hFrontend != nil {
+						hFrontend.Stop()
+					}
+
+					configFile.Config.Backend.Stop()
+
+					close(errChan)
+					close(closedChan)
+				}()
+
+				err = <-errChan
+				if err != nil {
+					close(shutdown)
+					<-closedChan
+					return err
 				}
 
 				return nil
diff --git a/bittorrent/http/bencode/bencode.go b/frontends/http/bencode/bencode.go
similarity index 100%
rename from bittorrent/http/bencode/bencode.go
rename to frontends/http/bencode/bencode.go
diff --git a/bittorrent/http/bencode/decoder.go b/frontends/http/bencode/decoder.go
similarity index 100%
rename from bittorrent/http/bencode/decoder.go
rename to frontends/http/bencode/decoder.go
diff --git a/bittorrent/http/bencode/decoder_test.go b/frontends/http/bencode/decoder_test.go
similarity index 100%
rename from bittorrent/http/bencode/decoder_test.go
rename to frontends/http/bencode/decoder_test.go
diff --git a/bittorrent/http/bencode/encoder.go b/frontends/http/bencode/encoder.go
similarity index 100%
rename from bittorrent/http/bencode/encoder.go
rename to frontends/http/bencode/encoder.go
diff --git a/bittorrent/http/bencode/encoder_test.go b/frontends/http/bencode/encoder_test.go
similarity index 100%
rename from bittorrent/http/bencode/encoder_test.go
rename to frontends/http/bencode/encoder_test.go
diff --git a/bittorrent/http/tracker.go b/frontends/http/frontend.go
similarity index 86%
rename from bittorrent/http/tracker.go
rename to frontends/http/frontend.go
index f029de2..716da66 100644
--- a/bittorrent/http/tracker.go
+++ b/frontends/http/frontend.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Package http implements a BitTorrent tracker via the HTTP protocol as
+// Package http implements a BitTorrent frontend via the HTTP protocol as
 // described in BEP 3 and BEP 23.
 package http
 
@@ -26,7 +26,7 @@ import (
 	"github.com/tylerb/graceful"
 	"golang.org/x/net/context"
 
-	"github.com/jzelinskie/trakr/bittorrent"
+	"github.com/jzelinskie/trakr/backend"
 )
 
 func init() {
@@ -43,8 +43,8 @@ var promResponseDurationMilliseconds = prometheus.NewHistogramVec(
 	[]string{"action", "error"},
 )
 
-// recordResponseDuration records the duration of time to respond to a UDP
-// Request in milliseconds .
+// recordResponseDuration records the duration of time to respond to a Request
+// in milliseconds .
 func recordResponseDuration(action string, err error, duration time.Duration) {
 	var errString string
 	if err != nil {
@@ -57,7 +57,7 @@ func recordResponseDuration(action string, err error, duration time.Duration) {
 }
 
 // Config represents all of the configurable options for an HTTP BitTorrent
-// Tracker.
+// Frontend.
 type Config struct {
 	Addr            string
 	ReadTimeout     time.Duration
@@ -67,29 +67,29 @@ type Config struct {
 	RealIPHeader    string
 }
 
-// Tracker holds the state of an HTTP BitTorrent Tracker.
-type Tracker struct {
+// Frontend holds the state of an HTTP BitTorrent Frontend.
+type Frontend struct {
 	grace *graceful.Server
 
-	bittorrent.TrackerFuncs
+	backend.TrackerFuncs
 	Config
 }
 
-// NewTracker allocates a new instance of a Tracker.
-func NewTracker(funcs bittorrent.TrackerFuncs, cfg Config) *Tracker {
-	return &Tracker{
+// NewFrontend allocates a new instance of a Frontend.
+func NewFrontend(funcs backend.TrackerFuncs, cfg Config) *Frontend {
+	return &Frontend{
 		TrackerFuncs: funcs,
 		Config:       cfg,
 	}
 }
 
 // Stop provides a thread-safe way to shutdown a currently running Tracker.
-func (t *Tracker) Stop() {
+func (t *Frontend) Stop() {
 	t.grace.Stop(t.grace.Timeout)
 	<-t.grace.StopChan()
 }
 
-func (t *Tracker) handler() http.Handler {
+func (t *Frontend) handler() http.Handler {
 	router := httprouter.New()
 	router.GET("/announce", t.announceRoute)
 	router.GET("/scrape", t.scrapeRoute)
@@ -98,7 +98,7 @@ func (t *Tracker) handler() http.Handler {
 
 // ListenAndServe listens on the TCP network address t.Addr and blocks serving
 // BitTorrent requests until t.Stop() is called or an error is returned.
-func (t *Tracker) ListenAndServe() error {
+func (t *Frontend) ListenAndServe() error {
 	t.grace = &graceful.Server{
 		Server: &http.Server{
 			Addr:         t.Addr,
@@ -139,7 +139,7 @@ func (t *Tracker) ListenAndServe() error {
 }
 
 // announceRoute parses and responds to an Announce by using t.TrackerFuncs.
-func (t *Tracker) announceRoute(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+func (t *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 	var err error
 	start := time.Now()
 	defer recordResponseDuration("announce", err, time.Since(start))
@@ -168,7 +168,7 @@ func (t *Tracker) announceRoute(w http.ResponseWriter, r *http.Request, _ httpro
 }
 
 // scrapeRoute parses and responds to a Scrape by using t.TrackerFuncs.
-func (t *Tracker) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+func (t *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 	var err error
 	start := time.Now()
 	defer recordResponseDuration("scrape", err, time.Since(start))
diff --git a/bittorrent/http/parser.go b/frontends/http/parser.go
similarity index 100%
rename from bittorrent/http/parser.go
rename to frontends/http/parser.go
diff --git a/bittorrent/http/query_params.go b/frontends/http/query_params.go
similarity index 100%
rename from bittorrent/http/query_params.go
rename to frontends/http/query_params.go
diff --git a/bittorrent/http/query_params_test.go b/frontends/http/query_params_test.go
similarity index 100%
rename from bittorrent/http/query_params_test.go
rename to frontends/http/query_params_test.go
diff --git a/bittorrent/http/writer.go b/frontends/http/writer.go
similarity index 98%
rename from bittorrent/http/writer.go
rename to frontends/http/writer.go
index c1e9266..0565fc3 100644
--- a/bittorrent/http/writer.go
+++ b/frontends/http/writer.go
@@ -18,7 +18,7 @@ import (
 	"net/http"
 
 	"github.com/jzelinskie/trakr/bittorrent"
-	"github.com/jzelinskie/trakr/bittorrent/http/bencode"
+	"github.com/jzelinskie/trakr/frontends/http/bencode"
 )
 
 // WriteError communicates an error to a BitTorrent client over HTTP.
diff --git a/bittorrent/http/writer_test.go b/frontends/http/writer_test.go
similarity index 100%
rename from bittorrent/http/writer_test.go
rename to frontends/http/writer_test.go
diff --git a/bittorrent/udp/bytepool/bytepool.go b/frontends/udp/bytepool/bytepool.go
similarity index 100%
rename from bittorrent/udp/bytepool/bytepool.go
rename to frontends/udp/bytepool/bytepool.go
diff --git a/bittorrent/udp/connection_id.go b/frontends/udp/connection_id.go
similarity index 100%
rename from bittorrent/udp/connection_id.go
rename to frontends/udp/connection_id.go
diff --git a/bittorrent/udp/connection_id_test.go b/frontends/udp/connection_id_test.go
similarity index 100%
rename from bittorrent/udp/connection_id_test.go
rename to frontends/udp/connection_id_test.go
diff --git a/bittorrent/udp/tracker.go b/frontends/udp/frontend.go
similarity index 91%
rename from bittorrent/udp/tracker.go
rename to frontends/udp/frontend.go
index 2ff6ac6..7da88f7 100644
--- a/bittorrent/udp/tracker.go
+++ b/frontends/udp/frontend.go
@@ -27,8 +27,9 @@ import (
 	"github.com/prometheus/client_golang/prometheus"
 	"golang.org/x/net/context"
 
+	"github.com/jzelinskie/trakr/backend"
 	"github.com/jzelinskie/trakr/bittorrent"
-	"github.com/jzelinskie/trakr/bittorrent/udp/bytepool"
+	"github.com/jzelinskie/trakr/frontends/udp/bytepool"
 )
 
 func init() {
@@ -67,27 +68,27 @@ type Config struct {
 	AllowIPSpoofing bool
 }
 
-// Tracker holds the state of a UDP BitTorrent Tracker.
-type Tracker struct {
+// Frontend holds the state of a UDP BitTorrent Frontend.
+type Frontend struct {
 	socket  *net.UDPConn
 	closing chan struct{}
 	wg      sync.WaitGroup
 
-	bittorrent.TrackerFuncs
+	backend.TrackerFuncs
 	Config
 }
 
-// NewTracker allocates a new instance of a Tracker.
-func NewTracker(funcs bittorrent.TrackerFuncs, cfg Config) *Tracker {
-	return &Tracker{
+// NewFrontend allocates a new instance of a Frontend.
+func NewFrontend(funcs backend.TrackerFuncs, cfg Config) *Frontend {
+	return &Frontend{
 		closing:      make(chan struct{}),
 		TrackerFuncs: funcs,
 		Config:       cfg,
 	}
 }
 
-// Stop provides a thread-safe way to shutdown a currently running Tracker.
-func (t *Tracker) Stop() {
+// Stop provides a thread-safe way to shutdown a currently running Frontend.
+func (t *Frontend) Stop() {
 	close(t.closing)
 	t.socket.SetReadDeadline(time.Now())
 	t.wg.Wait()
@@ -95,7 +96,7 @@ func (t *Tracker) Stop() {
 
 // ListenAndServe listens on the UDP network address t.Addr and blocks serving
 // BitTorrent requests until t.Stop() is called or an error is returned.
-func (t *Tracker) ListenAndServe() error {
+func (t *Frontend) ListenAndServe() error {
 	udpAddr, err := net.ResolveUDPAddr("udp", t.Addr)
 	if err != nil {
 		return err
@@ -175,7 +176,7 @@ func (w ResponseWriter) Write(b []byte) (int, error) {
 }
 
 // handleRequest parses and responds to a UDP Request.
-func (t *Tracker) handleRequest(r Request, w ResponseWriter) (response []byte, actionName string, err error) {
+func (t *Frontend) handleRequest(r Request, w ResponseWriter) (response []byte, actionName string, err error) {
 	if len(r.Packet) < 16 {
 		// Malformed, no client packets are less than 16 bytes.
 		// We explicitly return nothing in case this is a DoS attempt.
diff --git a/bittorrent/udp/parser.go b/frontends/udp/parser.go
similarity index 100%
rename from bittorrent/udp/parser.go
rename to frontends/udp/parser.go
diff --git a/bittorrent/udp/writer.go b/frontends/udp/writer.go
similarity index 100%
rename from bittorrent/udp/writer.go
rename to frontends/udp/writer.go