This commit is contained in:
davy 2019-06-29 17:02:05 -05:00
parent 0979457d75
commit a7a5eee66f
6 changed files with 36 additions and 0 deletions

View file

@ -17,6 +17,7 @@ import (
"github.com/wybiral/tube/pkg/media" "github.com/wybiral/tube/pkg/media"
) )
// App represents main application.
type App struct { type App struct {
Config *Config Config *Config
Library *media.Library Library *media.Library
@ -26,6 +27,7 @@ type App struct {
Router *mux.Router Router *mux.Router
} }
// NewApp returns a new instance of App from Config.
func NewApp(cfg *Config) (*App, error) { func NewApp(cfg *Config) (*App, error) {
if cfg == nil { if cfg == nil {
cfg = DefaultConfig() cfg = DefaultConfig()
@ -33,24 +35,30 @@ func NewApp(cfg *Config) (*App, error) {
a := &App{ a := &App{
Config: cfg, Config: cfg,
} }
// Setup Library
a.Library = media.NewLibrary() a.Library = media.NewLibrary()
// Setup Watcher
w, err := fsnotify.NewWatcher() w, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
return nil, err return nil, err
} }
a.Watcher = w a.Watcher = w
// Setup Listener
ln, err := newListener(cfg.Server) ln, err := newListener(cfg.Server)
if err != nil { if err != nil {
return nil, err return nil, err
} }
a.Listener = ln a.Listener = ln
// Setup Templates
a.Templates = template.Must(template.ParseGlob("templates/*")) a.Templates = template.Must(template.ParseGlob("templates/*"))
// Setup Router
r := mux.NewRouter().StrictSlash(true) r := mux.NewRouter().StrictSlash(true)
r.HandleFunc("/", a.indexHandler).Methods("GET") r.HandleFunc("/", a.indexHandler).Methods("GET")
r.HandleFunc("/v/{id}.mp4", a.videoHandler).Methods("GET") r.HandleFunc("/v/{id}.mp4", a.videoHandler).Methods("GET")
r.HandleFunc("/t/{id}", a.thumbHandler).Methods("GET") r.HandleFunc("/t/{id}", a.thumbHandler).Methods("GET")
r.HandleFunc("/v/{id}", a.pageHandler).Methods("GET") r.HandleFunc("/v/{id}", a.pageHandler).Methods("GET")
r.HandleFunc("/feed.xml", a.rssHandler).Methods("GET") r.HandleFunc("/feed.xml", a.rssHandler).Methods("GET")
// Static file handler
fsHandler := http.StripPrefix( fsHandler := http.StripPrefix(
"/static/", "/static/",
http.FileServer(http.Dir("./static/")), http.FileServer(http.Dir("./static/")),
@ -60,6 +68,7 @@ func NewApp(cfg *Config) (*App, error) {
return a, nil return a, nil
} }
// Run imports the library and starts server.
func (a *App) Run() error { func (a *App) Run() error {
path := a.Config.LibraryPath path := a.Config.LibraryPath
err := a.Library.Import(path) err := a.Library.Import(path)
@ -71,6 +80,7 @@ func (a *App) Run() error {
return http.Serve(a.Listener, a.Router) return http.Serve(a.Listener, a.Router)
} }
// Watch the library path and update Library with changes.
func (a *App) watch() { func (a *App) watch() {
for { for {
e, ok := <-a.Watcher.Events e, ok := <-a.Watcher.Events
@ -92,6 +102,7 @@ func (a *App) watch() {
} }
} }
// HTTP handler for /
func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) { func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("/") log.Printf("/")
pl := a.Library.Playlist() pl := a.Library.Playlist()
@ -108,6 +119,7 @@ func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
// HTTP handler for /v/id
func (a *App) pageHandler(w http.ResponseWriter, r *http.Request) { func (a *App) pageHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id := vars["id"] id := vars["id"]
@ -133,6 +145,7 @@ func (a *App) pageHandler(w http.ResponseWriter, r *http.Request) {
}) })
} }
// HTTP handler for /v/id.mp4
func (a *App) videoHandler(w http.ResponseWriter, r *http.Request) { func (a *App) videoHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id := vars["id"] id := vars["id"]
@ -149,6 +162,7 @@ func (a *App) videoHandler(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, path) http.ServeFile(w, r, path)
} }
// HTTP handler for /t/id
func (a *App) thumbHandler(w http.ResponseWriter, r *http.Request) { func (a *App) thumbHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id := vars["id"] id := vars["id"]
@ -167,6 +181,7 @@ func (a *App) thumbHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
// HTTP handler for /feed.xml
func (a *App) rssHandler(w http.ResponseWriter, r *http.Request) { func (a *App) rssHandler(w http.ResponseWriter, r *http.Request) {
cfg := a.Config.Feed cfg := a.Config.Feed
now := time.Now() now := time.Now()

View file

@ -5,17 +5,20 @@ import (
"os" "os"
) )
// Config settings for main App.
type Config struct { type Config struct {
LibraryPath string `json:"library"` LibraryPath string `json:"library"`
Server *ServerConfig `json:"server"` Server *ServerConfig `json:"server"`
Feed *FeedConfig `json:"feed"` Feed *FeedConfig `json:"feed"`
} }
// ServerConfig settings for App Server.
type ServerConfig struct { type ServerConfig struct {
Host string `json:"host"` Host string `json:"host"`
Port int `json:"port"` Port int `json:"port"`
} }
// FeedConfig settings for App Feed.
type FeedConfig struct { type FeedConfig struct {
ExternalURL string `json:"external_url"` ExternalURL string `json:"external_url"`
Title string `json:"title"` Title string `json:"title"`
@ -28,6 +31,7 @@ type FeedConfig struct {
Copyright string `json:"copyright"` Copyright string `json:"copyright"`
} }
// DefaultConfig returns Config initialized with default values.
func DefaultConfig() *Config { func DefaultConfig() *Config {
return &Config{ return &Config{
LibraryPath: "videos", LibraryPath: "videos",
@ -41,6 +45,7 @@ func DefaultConfig() *Config {
} }
} }
// ReadFile reads a JSON file into Config.
func (c *Config) ReadFile(path string) error { func (c *Config) ReadFile(path string) error {
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {

View file

@ -1,3 +1,7 @@
// Instead of using the default new.Listener this file will construct a custom
// one. The main purpose for this is to have more control over the settings
// (like keep-alive) and to retrieve the assigned port when using port 0.
package app package app
import ( import (

View file

@ -9,11 +9,13 @@ import (
"sync" "sync"
) )
// Library manages importing and retrieving video data.
type Library struct { type Library struct {
mu sync.RWMutex mu sync.RWMutex
Videos map[string]*Video Videos map[string]*Video
} }
// NewLibrary returns new instance of Library.
func NewLibrary() *Library { func NewLibrary() *Library {
lib := &Library{ lib := &Library{
Videos: make(map[string]*Video), Videos: make(map[string]*Video),
@ -21,6 +23,7 @@ func NewLibrary() *Library {
return lib return lib
} }
// Import adds all valid videos from a given path.
func (lib *Library) Import(path string) error { func (lib *Library) Import(path string) error {
files, err := ioutil.ReadDir(path) files, err := ioutil.ReadDir(path)
if err != nil { if err != nil {
@ -36,6 +39,7 @@ func (lib *Library) Import(path string) error {
return nil return nil
} }
// Add adds a single video from a given file path.
func (lib *Library) Add(path string) error { func (lib *Library) Add(path string) error {
v, err := ParseVideo(path) v, err := ParseVideo(path)
if err != nil { if err != nil {
@ -48,6 +52,7 @@ func (lib *Library) Add(path string) error {
return nil return nil
} }
// Remove removes a single video from a given file path.
func (lib *Library) Remove(path string) { func (lib *Library) Remove(path string) {
name := filepath.Base(path) name := filepath.Base(path)
// ID is name without extension // ID is name without extension
@ -65,6 +70,7 @@ func (lib *Library) Remove(path string) {
} }
} }
// Playlist returns a sorted Playlist of all videos.
func (lib *Library) Playlist() Playlist { func (lib *Library) Playlist() Playlist {
lib.mu.RLock() lib.mu.RLock()
defer lib.mu.RUnlock() defer lib.mu.RUnlock()

View file

@ -1,15 +1,19 @@
package media package media
// Playlist holds an array of videos capable of sorting by Timestamp.
type Playlist []*Video type Playlist []*Video
// Len returns length of array (for sorting).
func (p Playlist) Len() int { func (p Playlist) Len() int {
return len(p) return len(p)
} }
// Swap swaps two values in array by index (for sorting).
func (p Playlist) Swap(i, j int) { func (p Playlist) Swap(i, j int) {
p[i], p[j] = p[j], p[i] p[i], p[j] = p[j], p[i]
} }
// Less returns true if p[i] Timestamp is after p[j] (for sorting).
func (p Playlist) Less(i, j int) bool { func (p Playlist) Less(i, j int) bool {
return p[i].Timestamp.After(p[j].Timestamp) return p[i].Timestamp.After(p[j].Timestamp)
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/dhowden/tag" "github.com/dhowden/tag"
) )
// Video represents metadata for a single video.
type Video struct { type Video struct {
ID string ID string
Title string Title string
@ -20,6 +21,7 @@ type Video struct {
Timestamp time.Time Timestamp time.Time
} }
// ParseVideo parses a video file's metadata and returns a Video.
func ParseVideo(path string) (*Video, error) { func ParseVideo(path string) (*Video, error) {
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {