Compare commits

...

7 Commits

16 changed files with 340 additions and 189 deletions

View File

@ -112,33 +112,34 @@ Usage:
roulette <path> [path]... [flags]
Flags:
-a, --all enable all supported file types
--audio enable support for audio files
-b, --bind string address to bind to (default "0.0.0.0")
-c, --cache generate directory cache at startup
--cache-file string path to optional persistent cache file
--code enable support for source code files
--code-theme string theme for source code syntax highlighting (default "solarized-dark256")
-f, --filter enable filtering
--flash enable support for shockwave flash files (via ruffle.rs)
--handlers display registered handlers (for debugging)
-h, --help help for roulette
--images enable support for image files
-i, --info expose informational endpoints
--maximum-files uint32 skip directories with file counts above this value (default 4294967295)
--minimum-files uint32 skip directories with file counts below this value (default 1)
--page-length uint32 pagination length for info pages
-p, --port uint16 port to listen on (default 8080)
--prefix string root path for http handlers (for reverse proxying) (default "/")
--profile register net/http/pprof handlers
-r, --recursive recurse into subdirectories
--refresh enable automatic page refresh via query parameter
--russian remove selected images after serving
-s, --sort enable sorting
--text enable support for text files
-v, --verbose log accessed files and other information to stdout
-V, --version display version and exit
--video enable support for video files
-a, --all enable all supported file types
--audio enable support for audio files
-b, --bind string address to bind to (default "0.0.0.0")
-c, --cache generate directory cache at startup
--cache-file string path to optional persistent cache file
--code enable support for source code files
--code-theme string theme for source code syntax highlighting (default "solarized-dark256")
--exit-on-error shut down webserver on error, instead of just printing the error
-f, --filter enable filtering
--flash enable support for shockwave flash files (via ruffle.rs)
--handlers display registered handlers (for debugging)
-h, --help help for roulette
--images enable support for image files
-i, --info expose informational endpoints
--maximum-files uint skip directories with file counts above this value (default 4294967295)
--minimum-files uint skip directories with file counts below this value (default 1)
--page-length uint32 pagination length for info pages
-p, --port uint16 port to listen on (default 8080)
--prefix string root path for http handlers (for reverse proxying) (default "/")
--profile register net/http/pprof handlers
-r, --recursive recurse into subdirectories
--refresh enable automatic page refresh via query parameter
--russian remove selected images after serving
-s, --sort enable sorting
--text enable support for text files
-v, --verbose log accessed files and other information to stdout
-V, --version display version and exit
--video enable support for video files
```
## Building the Docker container

View File

@ -64,14 +64,6 @@ func (cache *fileCache) set(val []string) {
cache.list = make([]string, length)
copy(cache.list, val)
cache.mutex.Unlock()
}
func (cache *fileCache) generate(args []string, formats *types.Types) {
cache.mutex.Lock()
cache.list = []string{}
cache.mutex.Unlock()
fileList(args, &filters{}, "", cache, formats)
if Cache && CacheFile != "" {
cache.Export(CacheFile)
@ -136,9 +128,16 @@ func (cache *fileCache) Import(path string) error {
return nil
}
func serveCacheClear(args []string, cache *fileCache, formats *types.Types) httprouter.Handle {
func serveCacheClear(args []string, cache *fileCache, formats *types.Types, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
cache.generate(args, formats)
list, err := fileList(args, &filters{}, "", &fileCache{}, formats)
if err != nil {
errorChannel <- err
return
}
cache.set(list)
w.Header().Set("Content-Type", "text/plain")
@ -146,7 +145,7 @@ func serveCacheClear(args []string, cache *fileCache, formats *types.Types) http
}
}
func registerCacheHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types) {
func registerCacheHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types, errorChannel chan<- error) error {
skipIndex := false
if CacheFile != "" {
@ -157,8 +156,15 @@ func registerCacheHandlers(mux *httprouter.Router, args []string, cache *fileCac
}
if !skipIndex {
cache.generate(args, formats)
list, err := fileList(args, &filters{}, "", &fileCache{}, formats)
if err != nil {
return err
}
cache.set(list)
}
register(mux, Prefix+"/clear_cache", serveCacheClear(args, cache, formats))
register(mux, Prefix+"/clear_cache", serveCacheClear(args, cache, formats, errorChannel))
return nil
}

View File

@ -36,7 +36,7 @@ func notFound(w http.ResponseWriter, r *http.Request, path string) error {
startTime := time.Now()
if Verbose {
fmt.Printf("%s | Unavailable file %s requested by %s\n",
fmt.Printf("%s | Error: Unavailable file %s requested by %s\n",
startTime.Format(logDate),
path,
r.RemoteAddr,
@ -58,7 +58,7 @@ func serverError(w http.ResponseWriter, r *http.Request, i interface{}) {
startTime := time.Now()
if Verbose {
fmt.Printf("%s | Invalid request for %s from %s\n",
fmt.Printf("%s | Error: Invalid request for %s from %s\n",
startTime.Format(logDate),
r.URL.Path,
r.RemoteAddr,

View File

@ -27,12 +27,14 @@ const (
<meta name="theme-color" content="#ffffff">`
)
func serveFavicons() httprouter.Handle {
func serveFavicons(errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fname := strings.TrimPrefix(r.URL.Path, "/")
data, err := favicons.ReadFile(fname)
if err != nil {
errorChannel <- err
return
}

View File

@ -17,7 +17,6 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"seedno.de/seednode/roulette/types"
@ -41,22 +40,11 @@ type concurrency struct {
fileScans chan int
}
type files struct {
mutex sync.RWMutex
list []string
}
func (f *files) append(path string) {
f.mutex.Lock()
f.list = append(f.list, path)
f.mutex.Unlock()
}
type scanStats struct {
filesMatched atomic.Uint32
filesSkipped atomic.Uint32
directoriesMatched atomic.Uint32
directoriesSkipped atomic.Uint32
filesMatched int
filesSkipped int
directoriesMatched int
directoriesSkipped int
}
type splitPath struct {
@ -91,6 +79,19 @@ func humanReadableSize(bytes int) string {
float64(bytes)/float64(div), "KMGTPE"[exp])
}
func kill(path string, cache *fileCache) error {
err := os.Remove(path)
if err != nil {
return err
}
if Cache {
cache.remove(path)
}
return nil
}
func preparePath(path string) string {
if runtime.GOOS == "windows" {
return fmt.Sprintf("%s/%s", mediaPrefix, filepath.ToSlash(path))
@ -102,7 +103,7 @@ func preparePath(path string) string {
func newFile(paths []string, filters *filters, sortOrder string, regexes *regexes, cache *fileCache, formats *types.Types) (string, error) {
path, err := pickFile(paths, filters, sortOrder, cache, formats)
if err != nil {
return "", nil
return "", err
}
splitPath, err := split(path, regexes)
@ -230,7 +231,7 @@ func pathIsValid(path string, paths []string) bool {
switch {
case Verbose && !matchesPrefix:
fmt.Printf("%s | Error: Failed to serve file outside specified path(s): %s\n",
fmt.Printf("%s | Error: File outside specified path(s): %s\n",
time.Now().Format(logDate),
path,
)
@ -274,9 +275,9 @@ func pathHasSupportedFiles(path string, formats *types.Types) (bool, error) {
}
}
func pathCount(path string) (uint32, uint32, error) {
var directories uint32 = 0
var files uint32 = 0
func pathCount(path string) (int, int, error) {
var directories = 0
var files = 0
nodes, err := os.ReadDir(path)
if err != nil {
@ -294,9 +295,16 @@ func pathCount(path string) (uint32, uint32, error) {
return files, directories, nil
}
func scanPath(path string, files *files, stats *scanStats, concurrency *concurrency, formats *types.Types) error {
func scanPath(path string, fileChannel chan<- string, statChannel chan<- *scanStats, errorChannel chan<- error, concurrency *concurrency, formats *types.Types) {
var wg sync.WaitGroup
stats := &scanStats{
filesMatched: 0,
filesSkipped: 0,
directoriesMatched: 0,
directoriesSkipped: 0,
}
err := filepath.WalkDir(path, func(p string, info os.DirEntry, err error) error {
if err != nil {
return err
@ -318,34 +326,34 @@ func scanPath(path string, files *files, stats *scanStats, concurrency *concurre
path, err := normalizePath(p)
if err != nil {
fmt.Println(err)
errorChannel <- err
}
if !formats.Validate(path) {
stats.filesSkipped.Add(1)
stats.filesSkipped = stats.filesSkipped + 1
return
}
files.append(path)
fileChannel <- path
stats.filesMatched.Add(1)
stats.filesMatched = stats.filesMatched + 1
}()
case info.IsDir():
files, directories, err := pathCount(p)
if err != nil {
fmt.Println(err)
errorChannel <- err
}
if files > 0 && (files < MinimumFileCount) || (files > MaximumFileCount) {
if files > 0 && (files < int(MinimumFileCount)) || (files > int(MaximumFileCount)) {
// This count will not otherwise include the parent directory itself, so increment by one
stats.directoriesSkipped.Add(directories + 1)
stats.filesSkipped.Add(files)
stats.directoriesSkipped = stats.directoriesSkipped + directories + 1
stats.filesSkipped = stats.filesSkipped + files
return filepath.SkipDir
}
stats.directoriesMatched.Add(1)
stats.directoriesMatched = stats.directoriesMatched + 1
}
return err
@ -353,24 +361,26 @@ func scanPath(path string, files *files, stats *scanStats, concurrency *concurre
wg.Wait()
if err != nil {
return err
}
statChannel <- stats
return nil
if err != nil {
errorChannel <- err
}
}
func scanPaths(paths []string, sort string, cache *fileCache, formats *types.Types) []string {
files := &files{
mutex: sync.RWMutex{},
list: []string{},
}
func scanPaths(paths []string, sort string, cache *fileCache, formats *types.Types) ([]string, error) {
var list []string
fileChannel := make(chan string)
statChannel := make(chan *scanStats)
errorChannel := make(chan error)
done := make(chan bool, 1)
stats := &scanStats{
filesMatched: atomic.Uint32{},
filesSkipped: atomic.Uint32{},
directoriesMatched: atomic.Uint32{},
directoriesSkipped: atomic.Uint32{},
filesMatched: 0,
filesSkipped: 0,
directoriesMatched: 0,
directoriesSkipped: 0,
}
concurrency := &concurrency{
@ -393,55 +403,95 @@ func scanPaths(paths []string, sort string, cache *fileCache, formats *types.Typ
wg.Done()
}()
err := scanPath(paths[i], files, stats, concurrency, formats)
if err != nil {
fmt.Println(err)
}
scanPath(paths[i], fileChannel, statChannel, errorChannel, concurrency, formats)
}(i)
}
wg.Wait()
go func() {
wg.Wait()
done <- true
}()
if stats.filesMatched.Load() < 1 {
Poll:
for {
select {
case p := <-fileChannel:
list = append(list, p)
case s := <-statChannel:
stats.filesMatched = stats.filesMatched + s.filesMatched
stats.filesSkipped = stats.filesSkipped + s.filesSkipped
stats.directoriesMatched = stats.directoriesMatched + s.directoriesMatched
stats.directoriesSkipped = stats.directoriesSkipped + s.directoriesSkipped
case e := <-errorChannel:
return []string{}, e
case <-done:
break Poll
}
}
if stats.filesMatched < 1 {
fmt.Println("No files matched")
return []string{}
return []string{}, nil
}
if Verbose {
fmt.Printf("%s | Indexed %d/%d files across %d/%d directories in %s\n",
fmt.Printf("%s | Index: %d/%d files across %d/%d directories in %s\n",
time.Now().Format(logDate),
stats.filesMatched.Load(),
stats.filesMatched.Load()+stats.filesSkipped.Load(),
stats.directoriesMatched.Load(),
stats.directoriesMatched.Load()+stats.directoriesSkipped.Load(),
stats.filesMatched,
stats.filesMatched+stats.filesSkipped,
stats.directoriesMatched,
stats.directoriesMatched+stats.directoriesSkipped,
time.Since(startTime),
)
}
return files.list
return list, nil
}
func fileList(paths []string, filters *filters, sort string, cache *fileCache, formats *types.Types) []string {
func fileList(paths []string, filters *filters, sort string, cache *fileCache, formats *types.Types) ([]string, error) {
switch {
case Cache && !cache.isEmpty() && filters.isEmpty():
return cache.List()
return cache.List(), nil
case Cache && !cache.isEmpty() && !filters.isEmpty():
return filters.apply(cache.List())
return filters.apply(cache.List()), nil
case Cache && cache.isEmpty() && !filters.isEmpty():
cache.set(scanPaths(paths, sort, cache, formats))
return filters.apply(cache.List())
list, err := scanPaths(paths, sort, cache, formats)
if err != nil {
return []string{}, err
}
cache.set(list)
return filters.apply(cache.List()), nil
case Cache && cache.isEmpty() && filters.isEmpty():
cache.set(scanPaths(paths, sort, cache, formats))
return cache.List()
list, err := scanPaths(paths, sort, cache, formats)
if err != nil {
return []string{}, err
}
cache.set(list)
return cache.List(), nil
case !Cache && !filters.isEmpty():
return filters.apply(scanPaths(paths, sort, cache, formats))
list, err := scanPaths(paths, sort, cache, formats)
if err != nil {
return []string{}, err
}
return filters.apply(list), nil
default:
return scanPaths(paths, sort, cache, formats)
list, err := scanPaths(paths, sort, cache, formats)
if err != nil {
return []string{}, err
}
return list, nil
}
}
func pickFile(args []string, filters *filters, sort string, cache *fileCache, formats *types.Types) (string, error) {
list := fileList(args, filters, sort, cache, formats)
list, err := fileList(args, filters, sort, cache, formats)
if err != nil {
return "", err
}
fileCount := len(list)

View File

@ -123,7 +123,7 @@ func serveIndexHtml(args []string, cache *fileCache, paginate bool) httprouter.H
}
if Verbose {
fmt.Printf("%s | Served HTML index page (%s) to %s in %s\n",
fmt.Printf("%s | Serve: HTML index page (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(b),
realIP(r),
@ -133,7 +133,7 @@ func serveIndexHtml(args []string, cache *fileCache, paginate bool) httprouter.H
}
}
func serveIndexJson(args []string, index *fileCache) httprouter.Handle {
func serveIndexJson(args []string, index *fileCache, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
w.Header().Set("Content-Type", "application/json")
@ -168,7 +168,7 @@ func serveIndexJson(args []string, index *fileCache) httprouter.Handle {
response, err := json.MarshalIndent(cachedFiles[startIndex:stopIndex], "", " ")
if err != nil {
fmt.Println(err)
errorChannel <- err
serverError(w, r, nil)
@ -178,7 +178,7 @@ func serveIndexJson(args []string, index *fileCache) httprouter.Handle {
w.Write(response)
if Verbose {
fmt.Printf("%s | Served JSON index page (%s) to %s in %s\n",
fmt.Printf("%s | Serve: JSON index page (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(len(response)),
realIP(r),
@ -199,7 +199,7 @@ func serveAvailableExtensions() httprouter.Handle {
w.Write(response)
if Verbose {
fmt.Printf("%s | Served available extensions list (%s) to %s in %s\n",
fmt.Printf("%s | Serve: Available extension list (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(len(response)),
realIP(r),
@ -220,7 +220,7 @@ func serveEnabledExtensions(formats *types.Types) httprouter.Handle {
w.Write(response)
if Verbose {
fmt.Printf("%s | Served registered extensions list (%s) to %s in %s\n",
fmt.Printf("%s | Serve: Registered extension list (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(len(response)),
realIP(r),
@ -241,7 +241,7 @@ func serveAvailableMimeTypes() httprouter.Handle {
w.Write(response)
if Verbose {
fmt.Printf("%s | Served available MIME types list (%s) to %s in %s\n",
fmt.Printf("%s | Served available MIME type list (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(len(response)),
realIP(r),
@ -262,7 +262,7 @@ func serveEnabledMimeTypes(formats *types.Types) httprouter.Handle {
w.Write(response)
if Verbose {
fmt.Printf("%s | Served registered MIME types list (%s) to %s in %s\n",
fmt.Printf("%s | Served registered MIME type list (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(len(response)),
realIP(r),
@ -272,16 +272,16 @@ func serveEnabledMimeTypes(formats *types.Types) httprouter.Handle {
}
}
func registerInfoHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types) {
func registerInfoHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types, errorChannel chan<- error) {
if Cache {
register(mux, Prefix+"/html/", serveIndexHtml(args, cache, false))
if PageLength != 0 {
register(mux, Prefix+"/html/:page", serveIndexHtml(args, cache, true))
}
register(mux, Prefix+"/json", serveIndexJson(args, cache))
register(mux, Prefix+"/json", serveIndexJson(args, cache, errorChannel))
if PageLength != 0 {
register(mux, Prefix+"/json/:page", serveIndexJson(args, cache))
register(mux, Prefix+"/json/:page", serveIndexJson(args, cache, errorChannel))
}
}

View File

@ -11,7 +11,7 @@ import (
)
const (
ReleaseVersion string = "0.87.0"
ReleaseVersion string = "0.90.3"
)
var (
@ -22,13 +22,14 @@ var (
CacheFile string
Code bool
CodeTheme string
ExitOnError bool
Filtering bool
Flash bool
Handlers bool
Images bool
Info bool
MaximumFileCount uint32
MinimumFileCount uint32
MaximumFileCount uint
MinimumFileCount uint
PageLength uint32
Port uint16
Prefix string
@ -72,13 +73,14 @@ func init() {
rootCmd.Flags().StringVar(&CacheFile, "cache-file", "", "path to optional persistent cache file")
rootCmd.Flags().BoolVar(&Code, "code", false, "enable support for source code files")
rootCmd.Flags().StringVar(&CodeTheme, "code-theme", "solarized-dark256", "theme for source code syntax highlighting")
rootCmd.Flags().BoolVar(&ExitOnError, "exit-on-error", false, "shut down webserver on error, instead of just printing the error")
rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")
rootCmd.Flags().BoolVar(&Flash, "flash", false, "enable support for shockwave flash files (via ruffle.rs)")
rootCmd.Flags().BoolVar(&Handlers, "handlers", false, "display registered handlers (for debugging)")
rootCmd.Flags().BoolVar(&Images, "images", false, "enable support for image files")
rootCmd.Flags().BoolVarP(&Info, "info", "i", false, "expose informational endpoints")
rootCmd.Flags().Uint32Var(&MaximumFileCount, "maximum-files", 1<<32-1, "skip directories with file counts above this value")
rootCmd.Flags().Uint32Var(&MinimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
rootCmd.Flags().UintVar(&MaximumFileCount, "maximum-files", 1<<32-1, "skip directories with file counts above this value")
rootCmd.Flags().UintVar(&MinimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
rootCmd.Flags().Uint32Var(&PageLength, "page-length", 0, "pagination length for info pages")
rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on")
rootCmd.Flags().StringVar(&Prefix, "prefix", "/", "root path for http handlers (for reverse proxying)")

View File

@ -6,6 +6,7 @@ package cmd
import (
"bytes"
"context"
"errors"
"fmt"
"io"
@ -39,7 +40,7 @@ const (
timeout time.Duration = 10 * time.Second
)
func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle {
func serveStaticFile(paths []string, cache *fileCache, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
prefix := Prefix + sourcePrefix
@ -47,7 +48,7 @@ func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle {
prefixedFilePath, err := stripQueryParams(path)
if err != nil {
fmt.Println(err)
errorChannel <- err
serverError(w, r, nil)
@ -56,7 +57,7 @@ func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle {
filePath, err := filepath.EvalSymlinks(strings.TrimPrefix(prefixedFilePath, prefix))
if err != nil {
fmt.Println(err)
errorChannel <- err
serverError(w, r, nil)
@ -71,6 +72,8 @@ func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle {
exists, err := fileExists(filePath)
if err != nil {
errorChannel <- err
serverError(w, r, nil)
return
@ -86,33 +89,40 @@ func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle {
buf, err := os.ReadFile(filePath)
if err != nil {
errorChannel <- err
serverError(w, r, nil)
return
}
w.Write(buf)
written, _ := w.Write(buf)
fileSize := humanReadableSize(len(buf))
refererUri, err := stripQueryParams(refererToUri(r.Referer()))
if err != nil {
errorChannel <- err
if Russian {
err = os.Remove(filePath)
serverError(w, r, nil)
return
}
if Russian && refererUri != "" {
err = kill(filePath, cache)
if err != nil {
errorChannel <- err
serverError(w, r, nil)
return
}
if Cache {
cache.remove(filePath)
}
}
if Verbose {
fmt.Printf("%s | Served %s (%s) to %s in %s\n",
fmt.Printf("%s | Serve: %s (%s) to %s in %s\n",
startTime.Format(logDate),
filePath,
fileSize,
humanReadableSize(written),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
@ -120,11 +130,11 @@ func serveStaticFile(paths []string, cache *fileCache) httprouter.Handle {
}
}
func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *types.Types) httprouter.Handle {
func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *types.Types, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
refererUri, err := stripQueryParams(refererToUri(r.Referer()))
if err != nil {
fmt.Println(err)
errorChannel <- err
serverError(w, r, nil)
@ -147,7 +157,7 @@ func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *type
if refererUri != "" {
filePath, err = nextFile(strippedRefererUri, sortOrder, regexes, formats)
if err != nil {
fmt.Println(err)
errorChannel <- err
serverError(w, r, nil)
@ -174,7 +184,7 @@ func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *type
return
case err != nil:
fmt.Println(err)
errorChannel <- err
serverError(w, r, nil)
@ -194,7 +204,7 @@ func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *type
}
}
func serveMedia(paths []string, regexes *regexes, formats *types.Types) httprouter.Handle {
func serveMedia(paths []string, regexes *regexes, cache *fileCache, formats *types.Types, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
filters := &filters{
included: splitQueryParams(r.URL.Query().Get("include"), regexes),
@ -211,7 +221,7 @@ func serveMedia(paths []string, regexes *regexes, formats *types.Types) httprout
exists, err := fileExists(path)
if err != nil {
fmt.Println(err)
errorChannel <- err
serverError(w, r, nil)
@ -252,24 +262,62 @@ func serveMedia(paths []string, regexes *regexes, formats *types.Types) httprout
htmlBody.WriteString(`<!DOCTYPE html><html class="bg" lang="en"><head>`)
htmlBody.WriteString(faviconHtml)
htmlBody.WriteString(fmt.Sprintf(`<style>%s</style>`, format.Css()))
htmlBody.WriteString((format.Title(rootUrl, fileUri, path, fileName, Prefix, mimeType)))
title, err := format.Title(rootUrl, fileUri, path, fileName, Prefix, mimeType)
if err != nil {
errorChannel <- err
serverError(w, r, nil)
return
}
htmlBody.WriteString(title)
htmlBody.WriteString(`</head><body>`)
if refreshInterval != "0ms" {
htmlBody.WriteString(fmt.Sprintf("<script>window.onload = function(){setInterval(function(){window.location.href = '%s';}, %d);};</script>",
rootUrl,
refreshTimer))
}
htmlBody.WriteString((format.Body(rootUrl, fileUri, path, fileName, Prefix, mimeType)))
htmlBody.WriteString(`</body></html>`)
_, err = io.WriteString(w, gohtml.Format(htmlBody.String()))
body, err := format.Body(rootUrl, fileUri, path, fileName, Prefix, mimeType)
if err != nil {
fmt.Println(err)
errorChannel <- err
serverError(w, r, nil)
return
}
htmlBody.WriteString(body)
htmlBody.WriteString(`</body></html>`)
startTime := time.Now()
formattedPage := gohtml.Format(htmlBody.String())
written, err := io.WriteString(w, formattedPage)
if err != nil {
errorChannel <- err
serverError(w, r, nil)
return
}
if format.Type() != "embed" {
if Verbose {
fmt.Printf("%s | Serve: %s (%s) to %s in %s\n",
startTime.Format(logDate),
path,
humanReadableSize(written),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
}
if Russian {
kill(path, cache)
}
}
}
}
@ -387,7 +435,9 @@ func ServePage(args []string) error {
Prefix = Prefix + "/"
}
register(mux, Prefix, serveRoot(paths, regexes, cache, formats))
errorChannel := make(chan error)
register(mux, Prefix, serveRoot(paths, regexes, cache, formats, errorChannel))
Prefix = strings.TrimSuffix(Prefix, "/")
@ -395,22 +445,25 @@ func ServePage(args []string) error {
register(mux, "/", redirectRoot())
}
register(mux, Prefix+"/favicons/*favicon", serveFavicons())
register(mux, Prefix+"/favicons/*favicon", serveFavicons(errorChannel))
register(mux, Prefix+"/favicon.ico", serveFavicons())
register(mux, Prefix+"/favicon.ico", serveFavicons(errorChannel))
register(mux, Prefix+mediaPrefix+"/*media", serveMedia(paths, regexes, formats))
register(mux, Prefix+mediaPrefix+"/*media", serveMedia(paths, regexes, cache, formats, errorChannel))
register(mux, Prefix+sourcePrefix+"/*static", serveStaticFile(paths, cache))
register(mux, Prefix+sourcePrefix+"/*static", serveStaticFile(paths, cache, errorChannel))
register(mux, Prefix+"/version", serveVersion())
if Cache {
registerCacheHandlers(mux, args, cache, formats)
err = registerCacheHandlers(mux, args, cache, formats, errorChannel)
if err != nil {
return err
}
}
if Info {
registerInfoHandlers(mux, args, cache, formats)
registerInfoHandlers(mux, args, cache, formats, errorChannel)
}
if Profile {
@ -421,6 +474,18 @@ func ServePage(args []string) error {
fmt.Printf("WARNING! Files *will* be deleted after serving!\n\n")
}
go func() {
for err := range errorChannel {
fmt.Printf("%s | Error: %v\n", time.Now().Format(logDate), err)
if ExitOnError {
fmt.Printf("%s | Error: Shutting down...\n", time.Now().Format(logDate))
srv.Shutdown(context.Background())
}
}
}()
err = srv.ListenAndServe()
if !errors.Is(err, http.ErrServerClosed) {
return err

Binary file not shown.

View File

@ -22,16 +22,16 @@ func (t Format) Css() string {
return css.String()
}
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName)
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
return fmt.Sprintf(`<title>%s</title>`, fileName), nil
}
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
return fmt.Sprintf(`<a href="%s"><audio controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the audio tag.</audio></a>`,
rootUrl,
fileUri,
mime,
fileName)
fileName), nil
}
func (t Format) Extensions() map[string]string {
@ -57,6 +57,10 @@ func (t Format) Validate(filePath string) bool {
return true
}
func (t Format) Type() string {
return "embed"
}
func New() Format {
return Format{}
}

View File

@ -60,14 +60,14 @@ func (t Format) Css() string {
return css.String()
}
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName)
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
return fmt.Sprintf(`<title>%s</title>`, fileName), nil
}
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
contents, err := os.ReadFile(filePath)
if err != nil {
return ""
return "", err
}
contentString := string(contents)
@ -97,24 +97,24 @@ func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string)
iterator, err := lexer.Tokenise(nil, contentString)
if err != nil {
return ""
return "", err
}
err = formatter.Format(w, style, iterator)
if err != nil {
return ""
return "", err
}
w.Flush()
b, err := io.ReadAll(r)
if err != nil {
return ""
return "", err
}
return fmt.Sprintf(`<a href="%s">%s</a>`,
rootUrl,
string(b))
string(b)), nil
}
func (t Format) Extensions() map[string]string {
@ -221,6 +221,10 @@ func (t Format) Validate(filePath string) bool {
return true
}
func (t Format) Type() string {
return "inline"
}
func New(theme string) Format {
return Format{
Theme: theme,

View File

@ -22,17 +22,17 @@ func (t Format) Css() string {
return css.String()
}
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName)
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
return fmt.Sprintf(`<title>%s</title>`, fileName), nil
}
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
var html strings.Builder
html.WriteString(fmt.Sprintf(`<script src="https://unpkg.com/@ruffle-rs/ruffle"></script><script>window.RufflePlayer.config = {autoplay:"on"};</script><embed src="%s"></embed>`, fileUri))
html.WriteString(fmt.Sprintf(`<br /><button onclick="window.location.href = '%s';">Next</button>`, rootUrl))
return html.String()
return html.String(), nil
}
func (t Format) Extensions() map[string]string {
@ -56,6 +56,10 @@ func (t Format) Validate(filePath string) bool {
return true
}
func (t Format) Type() string {
return "embed"
}
func New() Format {
return Format{}
}

View File

@ -37,22 +37,22 @@ func (t Format) Css() string {
return css.String()
}
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
dimensions, err := ImageDimensions(filePath)
if err != nil {
fmt.Println(err)
return "", err
}
return fmt.Sprintf(`<title>%s (%dx%d)</title>`,
fileName,
dimensions.width,
dimensions.height)
dimensions.height), nil
}
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
dimensions, err := ImageDimensions(filePath)
if err != nil {
fmt.Println(err)
return "", err
}
return fmt.Sprintf(`<a href="%s"><img src="%s" width="%d" height="%d" type="%s" alt="Roulette selected: %s"></a>`,
@ -61,7 +61,7 @@ func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string)
dimensions.width,
dimensions.height,
mime,
fileName)
fileName), nil
}
func (t Format) Extensions() map[string]string {
@ -120,6 +120,10 @@ func ImageDimensions(path string) (*dimensions, error) {
return &dimensions{width: decodedConfig.Width, height: decodedConfig.Height}, nil
}
func (t Format) Type() string {
return "embed"
}
func New() Format {
return Format{}
}

View File

@ -27,11 +27,11 @@ func (t Format) Css() string {
return css.String()
}
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName)
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
return fmt.Sprintf(`<title>%s</title>`, fileName), nil
}
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
body, err := os.ReadFile(filePath)
if err != nil {
body = []byte{}
@ -39,7 +39,7 @@ func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string)
return fmt.Sprintf(`<a href="%s"><textarea autofocus readonly>%s</textarea></a>`,
rootUrl,
body)
body), nil
}
func (t Format) Extensions() map[string]string {
@ -76,6 +76,10 @@ func (t Format) Validate(filePath string) bool {
return utf8.Valid(head)
}
func (t Format) Type() string {
return "inline"
}
func New() Format {
return Format{}
}

View File

@ -15,9 +15,10 @@ var SupportedFormats = &Types{
}
type Type interface {
Type() string
Css() string
Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string
Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string
Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error)
Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error)
Extensions() map[string]string
MimeType(string) string
Validate(filePath string) bool

View File

@ -25,16 +25,16 @@ func (t Format) Css() string {
return css.String()
}
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName)
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
return fmt.Sprintf(`<title>%s</title>`, fileName), nil
}
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) {
return fmt.Sprintf(`<a href="%s"><video controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the video tag.</video></a>`,
rootUrl,
fileUri,
mime,
fileName)
fileName), nil
}
func (t Format) Extensions() map[string]string {
@ -63,6 +63,10 @@ func (t Format) Validate(filePath string) bool {
return true
}
func (t Format) Type() string {
return "embed"
}
func New() Format {
return Format{}
}