diff --git a/config.json b/config.json new file mode 100644 index 0000000..0240b07 --- /dev/null +++ b/config.json @@ -0,0 +1,7 @@ +{ + "library": "videos", + "server": { + "host": "127.0.0.1", + "port": 0 + } +} diff --git a/main.go b/main.go index df26d77..5719274 100644 --- a/main.go +++ b/main.go @@ -1,20 +1,26 @@ package main import ( + "fmt" "log" + "os" "github.com/wybiral/tube/pkg/app" ) -const addr = "127.0.0.1:40404" - func main() { - a, err := app.NewApp() + cfg := app.DefaultConfig() + err := cfg.ReadFile("config.json") + if err != nil && !os.IsNotExist(err) { + log.Fatal(err) + } + a, err := app.NewApp(cfg) if err != nil { log.Fatal(err) } + addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) log.Printf("Serving at http://%s", addr) - err = a.Run(addr) + err = a.Run() if err != nil { log.Fatal(err) } diff --git a/pkg/app/app.go b/pkg/app/app.go index befe1a1..7774a71 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -4,6 +4,7 @@ import ( "errors" "html/template" "log" + "net" "net/http" "github.com/gorilla/mux" @@ -11,16 +12,23 @@ import ( ) type App struct { + Config *Config Library *media.Library Playlist media.Playlist Templates *template.Template + Listener net.Listener Router *mux.Router } -func NewApp() (*App, error) { - a := &App{} +func NewApp(cfg *Config) (*App, error) { + if cfg == nil { + cfg = DefaultConfig() + } + a := &App{ + Config: cfg, + } lib := media.NewLibrary() - err := lib.Import("./videos") + err := lib.Import(cfg.LibraryPath) if err != nil { return nil, err } @@ -30,6 +38,11 @@ func NewApp() (*App, error) { return nil, errors.New("No valid videos found") } a.Playlist = pl + ln, err := newListener(cfg.Server) + if err != nil { + return nil, err + } + a.Listener = ln a.Templates = template.Must(template.ParseGlob("templates/*")) r := mux.NewRouter().StrictSlash(true) r.HandleFunc("/", a.indexHandler).Methods("GET") @@ -45,8 +58,8 @@ func NewApp() (*App, error) { return a, nil } -func (a *App) Run(addr string) error { - return http.ListenAndServe(addr, a.Router) +func (a *App) Run() error { + return http.Serve(a.Listener, a.Router) } func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) { @@ -84,7 +97,8 @@ func (a *App) videoHandler(w http.ResponseWriter, r *http.Request) { disposition := "attachment; filename=\"" + title + ".mp4\"" w.Header().Set("Content-Disposition", disposition) w.Header().Set("Content-Type", "video/mp4") - http.ServeFile(w, r, "./videos/"+id+".mp4") + path := a.Config.LibraryPath + "/" + id + ".mp4" + http.ServeFile(w, r, path) } func (a *App) thumbHandler(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/app/config.go b/pkg/app/config.go new file mode 100644 index 0000000..c4ea170 --- /dev/null +++ b/pkg/app/config.go @@ -0,0 +1,36 @@ +package app + +import ( + "encoding/json" + "os" +) + +type Config struct { + LibraryPath string `json:"library"` + Server *ServerConfig `json:"server"` +} + +type ServerConfig struct { + Host string `json:"host"` + Port int `json:"port"` +} + +func DefaultConfig() *Config { + return &Config{ + LibraryPath: "videos", + Server: &ServerConfig{ + Host: "127.0.0.1", + Port: 0, + }, + } +} + +func (c *Config) ReadFile(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + d := json.NewDecoder(f) + return d.Decode(c) +} diff --git a/pkg/app/listener.go b/pkg/app/listener.go new file mode 100644 index 0000000..7d42290 --- /dev/null +++ b/pkg/app/listener.go @@ -0,0 +1,33 @@ +package app + +import ( + "fmt" + "net" + "time" +) + +func newListener(cfg *ServerConfig) (net.Listener, error) { + addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + ln, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + // set actual port on config object (in case original port was 0) + cfg.Port = ln.Addr().(*net.TCPAddr).Port + return tcpListener{ln.(*net.TCPListener)}, nil +} + +// custom TCP listener with keep-alive timeout +type tcpListener struct { + *net.TCPListener +} + +func (ln tcpListener) Accept() (net.Conn, error) { + tc, err := ln.AcceptTCP() + if err != nil { + return nil, err + } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil +}