Compare commits

..

No commits in common. "16d1428a52f9ed51d7058b24a0fc20a98641f8f4" and "ba827430bb6fa57ff33c5ed0db8264680ece4162" have entirely different histories.

16 changed files with 187 additions and 338 deletions

View File

@ -119,15 +119,14 @@ Flags:
--cache-file string path to optional persistent cache file --cache-file string path to optional persistent cache file
--code enable support for source code files --code enable support for source code files
--code-theme string theme for source code syntax highlighting (default "solarized-dark256") --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 -f, --filter enable filtering
--flash enable support for shockwave flash files (via ruffle.rs) --flash enable support for shockwave flash files (via ruffle.rs)
--handlers display registered handlers (for debugging) --handlers display registered handlers (for debugging)
-h, --help help for roulette -h, --help help for roulette
--images enable support for image files --images enable support for image files
-i, --info expose informational endpoints -i, --info expose informational endpoints
--maximum-files uint skip directories with file counts above this value (default 4294967295) --maximum-files uint32 skip directories with file counts above this value (default 4294967295)
--minimum-files uint skip directories with file counts below this value (default 1) --minimum-files uint32 skip directories with file counts below this value (default 1)
--page-length uint32 pagination length for info pages --page-length uint32 pagination length for info pages
-p, --port uint16 port to listen on (default 8080) -p, --port uint16 port to listen on (default 8080)
--prefix string root path for http handlers (for reverse proxying) (default "/") --prefix string root path for http handlers (for reverse proxying) (default "/")

View File

@ -64,6 +64,14 @@ func (cache *fileCache) set(val []string) {
cache.list = make([]string, length) cache.list = make([]string, length)
copy(cache.list, val) copy(cache.list, val)
cache.mutex.Unlock() 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 != "" { if Cache && CacheFile != "" {
cache.Export(CacheFile) cache.Export(CacheFile)
@ -128,16 +136,9 @@ func (cache *fileCache) Import(path string) error {
return nil return nil
} }
func serveCacheClear(args []string, cache *fileCache, formats *types.Types, errorChannel chan<- error) httprouter.Handle { func serveCacheClear(args []string, cache *fileCache, formats *types.Types) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
list, err := fileList(args, &filters{}, "", &fileCache{}, formats) cache.generate(args, formats)
if err != nil {
errorChannel <- err
return
}
cache.set(list)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
@ -145,7 +146,7 @@ func serveCacheClear(args []string, cache *fileCache, formats *types.Types, erro
} }
} }
func registerCacheHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types, errorChannel chan<- error) error { func registerCacheHandlers(mux *httprouter.Router, args []string, cache *fileCache, formats *types.Types) {
skipIndex := false skipIndex := false
if CacheFile != "" { if CacheFile != "" {
@ -156,15 +157,8 @@ func registerCacheHandlers(mux *httprouter.Router, args []string, cache *fileCac
} }
if !skipIndex { if !skipIndex {
list, err := fileList(args, &filters{}, "", &fileCache{}, formats) cache.generate(args, 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() startTime := time.Now()
if Verbose { if Verbose {
fmt.Printf("%s | Error: Unavailable file %s requested by %s\n", fmt.Printf("%s | Unavailable file %s requested by %s\n",
startTime.Format(logDate), startTime.Format(logDate),
path, path,
r.RemoteAddr, r.RemoteAddr,
@ -58,7 +58,7 @@ func serverError(w http.ResponseWriter, r *http.Request, i interface{}) {
startTime := time.Now() startTime := time.Now()
if Verbose { if Verbose {
fmt.Printf("%s | Error: Invalid request for %s from %s\n", fmt.Printf("%s | Invalid request for %s from %s\n",
startTime.Format(logDate), startTime.Format(logDate),
r.URL.Path, r.URL.Path,
r.RemoteAddr, r.RemoteAddr,

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import (
) )
const ( const (
ReleaseVersion string = "0.90.3" ReleaseVersion string = "0.87.0"
) )
var ( var (
@ -22,14 +22,13 @@ var (
CacheFile string CacheFile string
Code bool Code bool
CodeTheme string CodeTheme string
ExitOnError bool
Filtering bool Filtering bool
Flash bool Flash bool
Handlers bool Handlers bool
Images bool Images bool
Info bool Info bool
MaximumFileCount uint MaximumFileCount uint32
MinimumFileCount uint MinimumFileCount uint32
PageLength uint32 PageLength uint32
Port uint16 Port uint16
Prefix string Prefix string
@ -73,14 +72,13 @@ func init() {
rootCmd.Flags().StringVar(&CacheFile, "cache-file", "", "path to optional persistent cache file") 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().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().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().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(&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(&Handlers, "handlers", false, "display registered handlers (for debugging)")
rootCmd.Flags().BoolVar(&Images, "images", false, "enable support for image files") rootCmd.Flags().BoolVar(&Images, "images", false, "enable support for image files")
rootCmd.Flags().BoolVarP(&Info, "info", "i", false, "expose informational endpoints") rootCmd.Flags().BoolVarP(&Info, "info", "i", false, "expose informational endpoints")
rootCmd.Flags().UintVar(&MaximumFileCount, "maximum-files", 1<<32-1, "skip directories with file counts above this value") rootCmd.Flags().Uint32Var(&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(&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().Uint32Var(&PageLength, "page-length", 0, "pagination length for info pages")
rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on") rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on")
rootCmd.Flags().StringVar(&Prefix, "prefix", "/", "root path for http handlers (for reverse proxying)") rootCmd.Flags().StringVar(&Prefix, "prefix", "/", "root path for http handlers (for reverse proxying)")

View File

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

Binary file not shown.

View File

@ -22,16 +22,16 @@ func (t Format) Css() string {
return css.String() return css.String()
} }
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) { func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName), nil return fmt.Sprintf(`<title>%s</title>`, fileName)
} }
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) { func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
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>`, 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, rootUrl,
fileUri, fileUri,
mime, mime,
fileName), nil fileName)
} }
func (t Format) Extensions() map[string]string { func (t Format) Extensions() map[string]string {
@ -57,10 +57,6 @@ func (t Format) Validate(filePath string) bool {
return true return true
} }
func (t Format) Type() string {
return "embed"
}
func New() Format { func New() Format {
return Format{} return Format{}
} }

View File

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

View File

@ -22,17 +22,17 @@ func (t Format) Css() string {
return css.String() return css.String()
} }
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) { func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName), nil return fmt.Sprintf(`<title>%s</title>`, fileName)
} }
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) { func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
var html strings.Builder 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(`<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)) html.WriteString(fmt.Sprintf(`<br /><button onclick="window.location.href = '%s';">Next</button>`, rootUrl))
return html.String(), nil return html.String()
} }
func (t Format) Extensions() map[string]string { func (t Format) Extensions() map[string]string {
@ -56,10 +56,6 @@ func (t Format) Validate(filePath string) bool {
return true return true
} }
func (t Format) Type() string {
return "embed"
}
func New() Format { func New() Format {
return Format{} return Format{}
} }

View File

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

View File

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

View File

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

View File

@ -25,16 +25,16 @@ func (t Format) Css() string {
return css.String() return css.String()
} }
func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) { func (t Format) Title(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
return fmt.Sprintf(`<title>%s</title>`, fileName), nil return fmt.Sprintf(`<title>%s</title>`, fileName)
} }
func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) (string, error) { func (t Format) Body(rootUrl, fileUri, filePath, fileName, prefix, mime string) string {
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>`, 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, rootUrl,
fileUri, fileUri,
mime, mime,
fileName), nil fileName)
} }
func (t Format) Extensions() map[string]string { func (t Format) Extensions() map[string]string {
@ -63,10 +63,6 @@ func (t Format) Validate(filePath string) bool {
return true return true
} }
func (t Format) Type() string {
return "embed"
}
func New() Format { func New() Format {
return Format{} return Format{}
} }