Compare commits

..

No commits in common. "5e1a957541029629caeccf812e0c260fd7dc0d67" and "974a4c79fc8f32e56ca27eee03d33aa6140366b5" have entirely different histories.

6 changed files with 99 additions and 123 deletions

View File

@ -112,37 +112,35 @@ Usage:
roulette <path> [path]... [flags] roulette <path> [path]... [flags]
Flags: Flags:
-a, --all enable all supported file types -a, --all enable all supported file types
--audio enable support for audio files --audio enable support for audio files
-b, --bind string address to bind to (default "0.0.0.0") -b, --bind string address to bind to (default "0.0.0.0")
-c, --cache generate directory cache at startup -c, --cache generate directory cache at startup
--cache-file string path to optional persistent cache file --cache-file string path to optional persistent cache file
--case-sensitive use case-sensitive matching for filters --case-sensitive use case-sensitive matching for filters
--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 --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
--max-directory-scans int number of directories to scan at once (default 32) --maximum-files uint skip directories with file counts above this value (default 4294967295)
--max-file-count int skip directories with file counts above this value (default 2147483647) --minimum-files uint skip directories with file counts below this value (default 1)
--max-file-scans int number of files to scan at once (default 256) --page-length uint32 pagination length for info pages
--min-file-count int skip directories with file counts below this value (default 1) -p, --port uint16 port to listen on (default 8080)
--page-length int pagination length for info pages --prefix string root path for http handlers (for reverse proxying) (default "/")
-p, --port int port to listen on (default 8080) --profile register net/http/pprof handlers
--prefix string root path for http handlers (for reverse proxying) (default "/") -r, --recursive recurse into subdirectories
--profile register net/http/pprof handlers --refresh enable automatic page refresh via query parameter
-r, --recursive recurse into subdirectories --russian remove selected images after serving
--refresh enable automatic page refresh via query parameter -s, --sort enable sorting
--russian remove selected images after serving --text enable support for text files
-s, --sort enable sorting -v, --verbose log accessed files and other information to stdout
--text enable support for text files -V, --version display version and exit
-v, --verbose log accessed files and other information to stdout --video enable support for video files
-V, --version display version and exit
--video enable support for video files
``` ```
## Building the Docker container ## Building the Docker container

View File

@ -16,11 +16,8 @@ import (
) )
var ( var (
ErrInvalidFileCountRange = errors.New("maximum file count limit must be greater than or equal to minimum file count limit") ErrIncorrectRefreshInterval = errors.New("refresh interval must be a duration string >= 500ms")
ErrInvalidFileCountValue = errors.New("file count limits must be positive integers no greater than 2147483647") ErrNoMediaFound = errors.New("no supported media formats found which match all criteria")
ErrInvalidPort = errors.New("listen port must be an integer between 1 and 65535 inclusive")
ErrInvalidScanCount = errors.New("maximum scan count must be a positive integer no greater than 2147483647")
ErrNoMediaFound = errors.New("no supported media formats found which match all criteria")
) )
func newErrorPage(title, body string) string { func newErrorPage(title, body string) string {
@ -39,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 | Error: Unavailable file %s requested by %s\n",
startTime.Format(logDate), startTime.Format(logDate),
path, path,
r.RemoteAddr, r.RemoteAddr,
@ -61,16 +58,17 @@ 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 | Error: Invalid request for %s from %s\n",
startTime.Format(logDate), startTime.Format(logDate),
r.URL.Path, r.URL.Path,
r.RemoteAddr, r.RemoteAddr,
) )
} }
w.WriteHeader(http.StatusInternalServerError)
w.Header().Add("Content-Type", "text/html") w.Header().Add("Content-Type", "text/html")
io.WriteString(w, gohtml.Format(newErrorPage("Server Error", "An error has occurred. Please try again."))) io.WriteString(w, gohtml.Format(newErrorPage("Server Error", "500 Internal Server Error")))
} }
func serverErrorHandler() func(http.ResponseWriter, *http.Request, interface{}) { func serverErrorHandler() func(http.ResponseWriter, *http.Request, interface{}) {

View File

@ -21,6 +21,14 @@ import (
"seedno.de/seednode/roulette/types" "seedno.de/seednode/roulette/types"
) )
type maxConcurrency int
const (
// avoid hitting default open file descriptor limits (1024)
maxDirectoryScans maxConcurrency = 32
maxFileScans maxConcurrency = 256
)
type regexes struct { type regexes struct {
alphanumeric *regexp.Regexp alphanumeric *regexp.Regexp
filename *regexp.Regexp filename *regexp.Regexp
@ -216,7 +224,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: File outside specified path(s): %s\n",
time.Now().Format(logDate), time.Now().Format(logDate),
path, path,
) )
@ -323,7 +331,7 @@ func walkPath(path string, fileChannel chan<- string, fileScans chan int, stats
errorChannel <- err errorChannel <- err
} }
if files > 0 && (files < MinFileCount) || (files > MaxFileCount) { if files > 0 && (files < int(MinimumFileCount)) || (files > int(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 <- directories + 1 stats.directoriesSkipped <- directories + 1
stats.filesSkipped <- files stats.filesSkipped <- files
@ -360,8 +368,8 @@ func scanPaths(paths []string, sort string, cache *fileCache, formats *types.Typ
fileChannel := make(chan string) fileChannel := make(chan string)
errorChannel := make(chan error) errorChannel := make(chan error)
directoryScans := make(chan int, MaxDirScans) directoryScans := make(chan int, maxDirectoryScans)
fileScans := make(chan int, MaxFileScans) fileScans := make(chan int, maxFileScans)
done := make(chan bool, 1) done := make(chan bool, 1)
stats := &scanStats{ stats := &scanStats{
@ -432,7 +440,7 @@ Poll:
} }
if Verbose { if Verbose {
fmt.Printf("%s | INDEX: %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), time.Now().Format(logDate),
stats.filesMatched, stats.filesMatched,
stats.filesMatched+stats.filesSkipped, stats.filesMatched+stats.filesSkipped,

View File

@ -36,8 +36,8 @@ func serveIndexHtml(args []string, cache *fileCache, paginate bool) httprouter.H
startIndex = 0 startIndex = 0
stopIndex = fileCount stopIndex = fileCount
} else { } else {
startIndex = ((page - 1) * PageLength) startIndex = ((page - 1) * int(PageLength))
stopIndex = (startIndex + PageLength) stopIndex = (startIndex + int(PageLength))
} }
if startIndex > (fileCount - 1) { if startIndex > (fileCount - 1) {
@ -72,10 +72,10 @@ func serveIndexHtml(args []string, cache *fileCache, paginate bool) httprouter.H
var firstPage int = 1 var firstPage int = 1
var lastPage int var lastPage int
if fileCount%PageLength == 0 { if fileCount%int(PageLength) == 0 {
lastPage = fileCount / PageLength lastPage = fileCount / int(PageLength)
} else { } else {
lastPage = (fileCount / PageLength) + 1 lastPage = (fileCount / int(PageLength)) + 1
} }
if paginate { if paginate {
@ -96,7 +96,7 @@ func serveIndexHtml(args []string, cache *fileCache, paginate bool) httprouter.H
nextPage := page + 1 nextPage := page + 1
if nextPage > lastPage { if nextPage > lastPage {
nextPage = fileCount / PageLength nextPage = fileCount / int(PageLength)
} }
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">First</button>", htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">First</button>",
@ -154,8 +154,8 @@ func serveIndexJson(args []string, index *fileCache, errorChannel chan<- error)
startIndex = 0 startIndex = 0
stopIndex = fileCount stopIndex = fileCount
} else { } else {
startIndex = ((page - 1) * PageLength) startIndex = ((page - 1) * int(PageLength))
stopIndex = (startIndex + PageLength) stopIndex = (startIndex + int(PageLength))
} }
if startIndex > (fileCount - 1) { if startIndex > (fileCount - 1) {
@ -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 | Serve: 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 | Serve: Available extension 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 | Serve: Registered extension 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 | SERVE: Available MIME type list (%s) to %s in %s\n", fmt.Printf("%s | Served available MIME type 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 | SERVE: Registered MIME type list (%s) to %s in %s\n", fmt.Printf("%s | Served registered MIME type list (%s) to %s in %s\n",
startTime.Format(logDate), startTime.Format(logDate),
humanReadableSize(len(response)), humanReadableSize(len(response)),
realIP(r), realIP(r),

View File

@ -6,65 +6,48 @@ package cmd
import ( import (
"log" "log"
"math"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const ( const (
ReleaseVersion string = "0.94.0" ReleaseVersion string = "0.92.2"
) )
var ( var (
All bool All bool
Audio bool Audio bool
Bind string Bind string
Cache bool Cache bool
CacheFile string CacheFile string
CaseSensitive bool CaseSensitive bool
Code bool Code bool
CodeTheme string CodeTheme string
ExitOnError bool ExitOnError bool
Filtering bool Filtering bool
Flash bool Flash bool
Handlers bool Handlers bool
Images bool Images bool
Info bool Info bool
MaxDirScans int MaximumFileCount uint
MaxFileScans int MinimumFileCount uint
MaxFileCount int PageLength uint32
MinFileCount int Port uint16
PageLength int Prefix string
Port int Profile bool
Prefix string Recursive bool
Profile bool Refresh bool
Recursive bool Russian bool
Refresh bool Sorting bool
Russian bool Text bool
Sorting bool Verbose bool
Text bool Version bool
Verbose bool Videos bool
Version bool
Videos bool
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "roulette <path> [path]...", Use: "roulette <path> [path]...",
Short: "Serves random media from the specified directories.", Short: "Serves random media from the specified directories.",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
switch {
case MaxDirScans < 1 || MaxFileScans < 1 || MaxDirScans > math.MaxInt32 || MaxFileScans > math.MaxInt32:
return ErrInvalidScanCount
case MaxFileCount < 1 || MinFileCount < 1 || MaxFileCount > math.MaxInt32 || MinFileCount > math.MaxInt32:
return ErrInvalidFileCountValue
case MinFileCount > MaxFileCount:
return ErrInvalidFileCountRange
case Port < 1 || Port > 65535:
return ErrInvalidPort
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := ServePage(args) err := ServePage(args)
if err != nil { if err != nil {
@ -98,12 +81,10 @@ func init() {
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().IntVar(&MaxDirScans, "max-directory-scans", 32, "number of directories to scan at once") rootCmd.Flags().UintVar(&MaximumFileCount, "maximum-files", 1<<32-1, "skip directories with file counts above this value")
rootCmd.Flags().IntVar(&MaxFileScans, "max-file-scans", 256, "number of files to scan at once") rootCmd.Flags().UintVar(&MinimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
rootCmd.Flags().IntVar(&MaxFileCount, "max-file-count", math.MaxInt32, "skip directories with file counts above this value") rootCmd.Flags().Uint32Var(&PageLength, "page-length", 0, "pagination length for info pages")
rootCmd.Flags().IntVar(&MinFileCount, "min-file-count", 1, "skip directories with file counts below this value") rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on")
rootCmd.Flags().IntVar(&PageLength, "page-length", 0, "pagination length for info pages")
rootCmd.Flags().IntVarP(&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)")
rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers") rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers")
rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories") rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories")

View File

@ -140,7 +140,7 @@ func serveStaticFile(paths []string, cache *fileCache, errorChannel chan<- error
} }
if Verbose { if Verbose {
fmt.Printf("%s | SERVE: %s (%s) to %s in %s%s\n", fmt.Printf("%s | Serve: %s (%s) to %s in %s%s\n",
startTime.Format(logDate), startTime.Format(logDate),
filePath, filePath,
humanReadableSize(written), humanReadableSize(written),
@ -336,7 +336,7 @@ func serveMedia(paths []string, regexes *regexes, cache *fileCache, formats *typ
if format.Type() != "embed" { if format.Type() != "embed" {
if Verbose { if Verbose {
fmt.Printf("%s | SERVE: %s (%s) to %s in %s\n", fmt.Printf("%s | Serve: %s (%s) to %s in %s\n",
startTime.Format(logDate), startTime.Format(logDate),
path, path,
humanReadableSize(written), humanReadableSize(written),
@ -452,17 +452,8 @@ func ServePage(args []string) error {
mux := httprouter.New() mux := httprouter.New()
listenHost := net.JoinHostPort(Bind, strconv.Itoa(Port))
if Verbose {
fmt.Printf("%s | SERVE: Listening on %s...\n",
time.Now().Format(logDate),
listenHost,
)
}
srv := &http.Server{ srv := &http.Server{
Addr: listenHost, Addr: net.JoinHostPort(Bind, strconv.Itoa(int(Port))),
Handler: mux, Handler: mux,
IdleTimeout: 10 * time.Minute, IdleTimeout: 10 * time.Minute,
ReadTimeout: 5 * time.Second, ReadTimeout: 5 * time.Second,
@ -516,10 +507,10 @@ func ServePage(args []string) error {
go func() { go func() {
for err := range errorChannel { for err := range errorChannel {
fmt.Printf("%s | ERROR: %v\n", time.Now().Format(logDate), err) fmt.Printf("%s | Error: %v\n", time.Now().Format(logDate), err)
if ExitOnError { if ExitOnError {
fmt.Printf("%s | ERROR: Shutting down...\n", time.Now().Format(logDate)) fmt.Printf("%s | Error: Shutting down...\n", time.Now().Format(logDate))
srv.Shutdown(context.Background()) srv.Shutdown(context.Background())
} }