~kota/evids

4b99efde96aef5c6dca610641af7bec03da2faa2 — Dakota Walsh 11 months ago b2b4edf main
add video player
10 files changed, 218 insertions(+), 115 deletions(-)

D artist.go
M helpers.go
D home.go
M main.go
M routes.go
M templates.go
R html/base.tmpl => ui/base.tmpl
R html/pages/artist.tmpl => ui/pages/artist.tmpl
R html/pages/home.tmpl => ui/pages/home.tmpl
A ui/pages/video.tmpl
D artist.go => artist.go +0 -61
@@ 1,61 0,0 @@
package main

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

func (app *application) artist(w http.ResponseWriter, r *http.Request) {
	path := filepath.Join(app.dir, filepath.Clean(r.URL.Path))
	info, err := os.Stat(path)
	if err != nil {
		app.errLog.Println(err)
		http.NotFound(w, r)
		return
	}
	fmt.Println("STATED", path)
	if !info.IsDir() {
		f, err := os.Open(path)
		if err != nil {
			app.errLog.Println(err)
			http.NotFound(w, r)
			return
		}
		http.ServeContent(w, r, path, info.ModTime(), f)
		return
	}

	artist := strings.TrimPrefix(path, filepath.Clean(app.dir)+"/")
	entries, err := ListDir(path)
	if err != nil {
		app.errLog.Println(err)
		http.NotFound(w, r)
		return
	}

	tsName := "artist.tmpl"
	ts, ok := app.templateCache[tsName]
	if !ok {
		app.errLog.Println(fmt.Errorf(
			"the template %s is missing",
			tsName,
		))
		http.NotFound(w, r)
		return
	}
	err = ts.ExecuteTemplate(w, "base", ArtistPage{
		Artist:  artist,
		Entries: entries,
	})
	if err != nil {
		app.errLog.Println(err)
		http.Error(
			w,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError,
		)
	}
}

M helpers.go => helpers.go +5 -1
@@ 5,6 5,7 @@ import (
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"
)



@@ 15,7 16,7 @@ type DirEntry struct {
	Time string
}

func ListDir(path string) ([]DirEntry, error) {
func ListVideos(path string) ([]DirEntry, error) {
	dirEntries, err := os.ReadDir(path)
	if err != nil {
		return nil, err


@@ 24,6 25,9 @@ func ListDir(path string) ([]DirEntry, error) {
	var entries []DirEntry
	for _, e := range dirEntries {
		name := e.Name()
		if !strings.HasSuffix(name, ".mp4") {
			continue
		}
		if len(name) > 28 {
			name = name[:25] + "..."
		}

D home.go => home.go +0 -40
@@ 1,40 0,0 @@
package main

import (
	"fmt"
	"net/http"
	"path/filepath"
)

func (app *application) home(w http.ResponseWriter, r *http.Request) {
	released, err := Released(filepath.Join(app.dir, "released.txt"))
	if err != nil {
		app.errLog.Println(err)
		http.Error(
			w,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError,
		)
		return
	}

	tsName := "home.tmpl"
	ts, ok := app.templateCache[tsName]
	if !ok {
		app.errLog.Println(fmt.Errorf(
			"the template %s is missing",
			tsName,
		))
		http.NotFound(w, r)
		return
	}
	err = ts.ExecuteTemplate(w, "base", released)
	if err != nil {
		app.errLog.Println(err)
		http.Error(
			w,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError,
		)
	}
}

M main.go => main.go +0 -5
@@ 16,11 16,6 @@ type application struct {
	errLog  *log.Logger
}

type ArtistPage struct {
	Artist  string
	Entries []DirEntry
}

func main() {
	addr := flag.String("addr", ":4000", "HTTP network address")
	content := flag.String("path", "/var/www", "Path to serve")

M routes.go => routes.go +167 -2
@@ 1,7 1,11 @@
package main

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/julienschmidt/httprouter"
)


@@ 9,8 13,169 @@ import (
func (app *application) routes() http.Handler {
	router := httprouter.New()
	router.HandlerFunc(http.MethodGet, "/", app.home)
	router.HandlerFunc(http.MethodGet, "/:artist", app.artist)
	router.HandlerFunc(http.MethodGet, "/:artist/:file", app.artist)
	router.HandlerFunc(http.MethodGet, "/:path", app.path)
	router.HandlerFunc(http.MethodGet, "/:path/:subpath", app.path)

	return app.logRequest(router)
}

// home is an http.HandlerFunc which displays the home page.
// The home page is a list of all "released" videos, which are loaded on each
// request from "released.txt".
func (app *application) home(w http.ResponseWriter, r *http.Request) {
	released, err := Released(filepath.Join(app.dir, "released.txt"))
	if err != nil {
		app.errLog.Println(err)
		http.Error(
			w,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError,
		)
		return
	}

	tsName := "home.tmpl"
	ts, ok := app.templateCache[tsName]
	if !ok {
		app.errLog.Println(fmt.Errorf(
			"the template %s is missing",
			tsName,
		))
		http.NotFound(w, r)
		return
	}
	err = ts.ExecuteTemplate(w, "base", released)
	if err != nil {
		app.errLog.Println(err)
		http.Error(
			w,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError,
		)
	}
}

// path is an http.HandlerFunc which passes the request to either artist,
// video, or file depending on if the request is for a file, video file, or
// directory.
func (app *application) path(w http.ResponseWriter, r *http.Request) {
	path := filepath.Join(app.dir, filepath.Clean(r.URL.Path))
	info, err := os.Stat(path)
	if err != nil {
		app.errLog.Println(err)
		http.NotFound(w, r)
		return
	}
	if !info.IsDir() {
		q := r.URL.Query()
		_, direct := q["direct"]
		if strings.HasSuffix(path, ".mp4") && !direct {
			app.video(w, r)
			return
		}
		app.file(w, r)
		return
	}

	app.artist(w, r)
}

// ArtistPage is the datastructure used in the artist handler for the artist
// template.
type ArtistPage struct {
	Artist  string
	Entries []DirEntry
}

// artist is an http.HandlerFunc which displays a page for the requested artist.
// The artist's page is a listing of all their videos, including unreleased
// videos.
func (app *application) artist(w http.ResponseWriter, r *http.Request) {
	path := filepath.Join(app.dir, filepath.Clean(r.URL.Path))
	artist := strings.TrimPrefix(path, filepath.Clean(app.dir)+"/")
	entries, err := ListVideos(path)
	if err != nil {
		app.errLog.Println(err)
		http.NotFound(w, r)
		return
	}

	tsName := "artist.tmpl"
	ts, ok := app.templateCache[tsName]
	if !ok {
		app.errLog.Println(fmt.Errorf(
			"the template %s is missing",
			tsName,
		))
		http.NotFound(w, r)
		return
	}
	err = ts.ExecuteTemplate(w, "base", ArtistPage{
		Artist:  artist,
		Entries: entries,
	})
	if err != nil {
		app.errLog.Println(err)
		http.Error(
			w,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError,
		)
	}
}

// file is an http.HandlerFunc for files.
func (app *application) file(w http.ResponseWriter, r *http.Request) {
	path := filepath.Join(app.dir, filepath.Clean(r.URL.Path))
	info, err := os.Stat(path)
	if err != nil {
		app.errLog.Println(err)
		http.NotFound(w, r)
		return
	}
	f, err := os.Open(path)
	if err != nil {
		app.errLog.Println(err)
		http.NotFound(w, r)
		return
	}
	http.ServeContent(w, r, path, info.ModTime(), f)
	return
}

// VideoPage is the datastructure used in the video handler for the video
// template.
type VideoPage struct {
	Artist string
	Video  string
}

// video is an http.HandlerFunc for videos.
// The video is displayed in browser with a helpful player and download link.
func (app *application) video(w http.ResponseWriter, r *http.Request) {
	tsName := "video.tmpl"
	ts, ok := app.templateCache[tsName]
	if !ok {
		app.errLog.Println(fmt.Errorf(
			"the template %s is missing",
			tsName,
		))
		http.NotFound(w, r)
		return
	}
	err := ts.ExecuteTemplate(w, "base", VideoPage{
		Artist: strings.TrimPrefix(
			filepath.Dir(filepath.Clean(r.URL.Path)),
			"/",
		),
		Video: filepath.Clean(r.URL.Path),
	})
	if err != nil {
		app.errLog.Println(err)
		http.Error(
			w,
			http.StatusText(http.StatusInternalServerError),
			http.StatusInternalServerError,
		)
	}
}

M templates.go => templates.go +3 -3
@@ 7,13 7,13 @@ import (
	"text/template"
)

//go:embed "html"
//go:embed "ui"
var EmbededFiles embed.FS

func newTemplateCache() (map[string]*template.Template, error) {
	cache := map[string]*template.Template{}

	pages, err := fs.Glob(EmbededFiles, "html/pages/*.tmpl")
	pages, err := fs.Glob(EmbededFiles, "ui/pages/*.tmpl")
	if err != nil {
		return nil, err
	}


@@ 21,7 21,7 @@ func newTemplateCache() (map[string]*template.Template, error) {
	for _, page := range pages {
		name := filepath.Base(page)
		files := []string{
			"html/base.tmpl",
			"ui/base.tmpl",
			page,
		}


R html/base.tmpl => ui/base.tmpl +0 -3
@@ 6,9 6,6 @@
		<title>evids</title>
		<link rel='stylesheet' href='/main.css'>
		<link rel='shortcut icon' href='/favicon.png'>
	</head>
	<body>
		{{template "main" .}}
	</body>
</html>
{{end}}

R html/pages/artist.tmpl => ui/pages/artist.tmpl +3 -0
@@ 1,4 1,6 @@
{{define "main"}}
</head>
<body>
<header>
	<span><a href="/">
		<img


@@ 35,4 37,5 @@
	</a>
	{{end}}
</main>
</body>
{{end}}

R html/pages/home.tmpl => ui/pages/home.tmpl +3 -0
@@ 1,4 1,6 @@
{{define "main"}}
</head>
<body>
<header>
	<span><a href="/">
		<img


@@ 24,4 26,5 @@
	</a>
	{{end}}
</main>
</body>
{{end}}

A ui/pages/video.tmpl => ui/pages/video.tmpl +37 -0
@@ 0,0 1,37 @@
{{define "main"}}
</head>
<header>
	<span><a href="/">
		<img
			id="evids"
			src="/logo.png"
			alt="EVIDS"
			onmouseover="evids.src='/bold-logo.png'"
			onmouseout="evids.src='/logo.png'"
		></img>
	</a></span>
	{{ if .Artist }}
	<span class="artist">
		<img
		class="artist"
		onclick="history.back()"
		src="/{{.Artist}}/{{.Artist}}.png"
		alt="{{.Artist}}">
	</span>
	{{ end }}
</header>
<main>
	<video
		controls
		width="100%"
		height="auto"
		preload="auto"
	  >
		<source src="{{.Video}}?direct" type="video/mp4" />
	</video>
	<span class="download"><a download href="{{.Video}}?direct">
		DOWNLOAD
	</span></a>
</main>
</body>
{{end}}