Compare commits

..

No commits in common. "0e8ac8cdc55f80bbfab806c35aaa52004291e41c" and "c11ddae546be8b2a51a02d40cc71ecccd1ab2e3e" have entirely different histories.

9 changed files with 136 additions and 174 deletions

View File

@ -38,7 +38,7 @@ You can also provide a comma-delimited string of alphanumeric patterns to exclud
Filenames matching any of these patterns will not be served. Filenames matching any of these patterns will not be served.
You can combine these two parameters, with exclusions taking priority over inclusions. You can also combine these two parameters, with exclusions taking priority over inclusions.
Both filtering parameters ignore the file extension and full path; they only compare against the bare filename. Both filtering parameters ignore the file extension and full path; they only compare against the bare filename.
@ -77,7 +77,7 @@ Enjoy!
## Sorting ## Sorting
You can specify a sorting direction via the `sort=` query parameter, assuming the `-s|--sort` flag is enabled. You can specify a sorting pattern via the `sort=` query parameter, assuming the `-s|--sort` flag is enabled.
A value of `sort=asc` means files will be served in ascending order (lowest-numbered to highest). A value of `sort=asc` means files will be served in ascending order (lowest-numbered to highest).
@ -95,7 +95,7 @@ For `sort=desc`, the highest-numbered file will be served instead.
If any other (or no) value is provided, the selected file will be random. If any other (or no) value is provided, the selected file will be random.
Note: These options require sequentially-numbered files matching the following pattern: `filename[0-9]*.extension`. Note: These patterns require sequentially-numbered files matching the following pattern: `filename###.extension`.
## Themes ## Themes
The `--code` handler provides syntax highlighting via [alecthomas/chroma](https://github.com/alecthomas/chroma). The `--code` handler provides syntax highlighting via [alecthomas/chroma](https://github.com/alecthomas/chroma).

View File

@ -109,10 +109,9 @@ func (cache *fileCache) Export(path string) error {
cache.mutex.RUnlock() cache.mutex.RUnlock()
if Verbose { if Verbose {
fmt.Printf("%s | CACHE: Exported %d entries to %s in %s\n", fmt.Printf("%s | CACHE: Exported %d entries in %s\n",
time.Now().Format(logDate), time.Now().Format(logDate),
length, length,
path,
time.Since(startTime), time.Since(startTime),
) )
} }
@ -147,10 +146,9 @@ func (cache *fileCache) Import(path string) error {
} }
if Verbose { if Verbose {
fmt.Printf("%s | CACHE: Imported %d entries from %s in %s\n", fmt.Printf("%s | CACHE: Imported %d entries in %s\n",
time.Now().Format(logDate), time.Now().Format(logDate),
length, length,
path,
time.Since(startTime), time.Since(startTime),
) )
} }
@ -176,7 +174,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, errorChannel chan<- error) error {
registerHandler(mux, Prefix+"/clear_cache", serveCacheClear(args, cache, formats, errorChannel)) register(mux, Prefix+"/clear_cache", serveCacheClear(args, cache, formats, errorChannel))
return nil return nil
} }

View File

@ -40,6 +40,20 @@ type scanStatsChannels struct {
directoriesSkipped chan int directoriesSkipped chan int
} }
type splitPath struct {
base string
number int
extension string
}
func (splitPath *splitPath) increment() {
splitPath.number = splitPath.number + 1
}
func (splitPath *splitPath) decrement() {
splitPath.number = splitPath.number - 1
}
func humanReadableSize(bytes int) string { func humanReadableSize(bytes int) string {
const unit = 1000 const unit = 1000
@ -71,45 +85,65 @@ func kill(path string, cache *fileCache) error {
return nil return nil
} }
func split(path string, regexes *regexes) (*splitPath, error) {
p := splitPath{}
var err error
split := regexes.filename.FindAllStringSubmatch(path, -1)
if len(split) < 1 || len(split[0]) < 3 {
return &splitPath{}, nil
}
p.base = split[0][1]
p.number, err = strconv.Atoi(split[0][2])
if err != nil {
return &splitPath{}, err
}
p.extension = split[0][3]
return &p, nil
}
func newFile(list []string, sortOrder string, regexes *regexes, formats *types.Types) (string, error) { func newFile(list []string, sortOrder string, regexes *regexes, formats *types.Types) (string, error) {
path, err := pickFile(list) path, err := pickFile(list)
if err != nil { if err != nil {
return "", err return "", err
} }
if sortOrder == "asc" || sortOrder == "desc" { splitPath, err := split(path, regexes)
splitPath, length, err := split(path, regexes) if err != nil {
return "", err
}
splitPath.number = 1
switch {
case sortOrder == "asc":
path, err = tryExtensions(splitPath, formats)
if err != nil { if err != nil {
return "", err return "", err
} }
case sortOrder == "desc":
switch { for {
case sortOrder == "asc": splitPath.increment()
splitPath.number = fmt.Sprintf("%0*d", length, 1)
path, err = tryExtensions(splitPath, formats) path, err = tryExtensions(splitPath, formats)
if err != nil { if err != nil {
return "", err return "", err
} }
case sortOrder == "desc":
for { if path == "" {
splitPath.increment() splitPath.decrement()
path, err = tryExtensions(splitPath, formats) path, err = tryExtensions(splitPath, formats)
if err != nil { if err != nil {
return "", err return "", err
} }
if path == "" { break
splitPath.decrement()
path, err = tryExtensions(splitPath, formats)
if err != nil {
return "", err
}
break
}
} }
} }
} }
@ -117,8 +151,8 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats *types.T
return path, nil return path, nil
} }
func nextFile(filePath, sortOrder string, regexes *regexes, formats *types.Types) (string, error) { func nextFile(path, sortOrder string, regexes *regexes, formats *types.Types) (string, error) {
splitPath, _, err := split(filePath, regexes) splitPath, err := split(path, regexes)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -132,27 +166,27 @@ func nextFile(filePath, sortOrder string, regexes *regexes, formats *types.Types
return "", nil return "", nil
} }
path, err := tryExtensions(splitPath, formats) fileName, err := tryExtensions(splitPath, formats)
if err != nil { if err != nil {
return "", err return "", err
} }
return path, err return fileName, err
} }
func tryExtensions(splitPath *splitPath, formats *types.Types) (string, error) { func tryExtensions(splitPath *splitPath, formats *types.Types) (string, error) {
var path string var fileName string
for extension := range formats.Extensions { for extension := range formats.Extensions {
path = fmt.Sprintf("%s%s%s", splitPath.base, splitPath.number, extension) fileName = fmt.Sprintf("%s%.3d%s", splitPath.base, splitPath.number, extension)
exists, err := fileExists(path) exists, err := fileExists(fileName)
if err != nil { if err != nil {
return "", err return "", err
} }
if exists { if exists {
return path, nil return fileName, nil
} }
} }
@ -195,7 +229,7 @@ func pathIsValid(path string, paths []string) bool {
} }
} }
func hasSupportedFiles(path string, formats *types.Types) (bool, error) { func pathHasSupportedFiles(path string, formats *types.Types) (bool, error) {
hasRegisteredFiles := make(chan bool, 1) hasRegisteredFiles := make(chan bool, 1)
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 {
@ -375,18 +409,18 @@ func scanPaths(paths []string, sort string, cache *fileCache, formats *types.Typ
Poll: Poll:
for { for {
select { select {
case path := <-fileChannel: case p := <-fileChannel:
list = append(list, path) list = append(list, p)
case stat := <-statsChannels.filesMatched: case s := <-statsChannels.filesMatched:
stats.filesMatched = stats.filesMatched + stat stats.filesMatched = stats.filesMatched + s
case stat := <-statsChannels.filesSkipped: case s := <-statsChannels.filesSkipped:
stats.filesSkipped = stats.filesSkipped + stat stats.filesSkipped = stats.filesSkipped + s
case stat := <-statsChannels.directoriesMatched: case s := <-statsChannels.directoriesMatched:
stats.directoriesMatched = stats.directoriesMatched + stat stats.directoriesMatched = stats.directoriesMatched + s
case stat := <-statsChannels.directoriesSkipped: case s := <-statsChannels.directoriesSkipped:
stats.directoriesSkipped = stats.directoriesSkipped + stat stats.directoriesSkipped = stats.directoriesSkipped + s
case err := <-errorChannel: case e := <-errorChannel:
return []string{}, err return []string{}, e
case <-done: case <-done:
break Poll break Poll
} }
@ -398,7 +432,7 @@ Poll:
} }
if Verbose { if Verbose {
fmt.Printf("%s | INDEX: Selected %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,
@ -509,12 +543,12 @@ func validatePaths(args []string, formats *types.Types) ([]string, error) {
pathMatches := (args[i] == path) pathMatches := (args[i] == path)
hasSupportedFiles, err := hasSupportedFiles(path, formats) hasSupportedFiles, err := pathHasSupportedFiles(path, formats)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var addPath = false var addPath bool = false
switch { switch {
case pathMatches && hasSupportedFiles: case pathMatches && hasSupportedFiles:

View File

@ -274,19 +274,19 @@ 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, errorChannel chan<- error) {
if Cache { if Cache {
registerHandler(mux, Prefix+"/html", serveIndexHtml(args, cache, false)) register(mux, Prefix+"/html/", serveIndexHtml(args, cache, false))
if PageLength != 0 { if PageLength != 0 {
registerHandler(mux, Prefix+"/html/:page", serveIndexHtml(args, cache, true)) register(mux, Prefix+"/html/:page", serveIndexHtml(args, cache, true))
} }
registerHandler(mux, Prefix+"/json", serveIndexJson(args, cache, errorChannel)) register(mux, Prefix+"/json", serveIndexJson(args, cache, errorChannel))
if PageLength != 0 { if PageLength != 0 {
registerHandler(mux, Prefix+"/json/:page", serveIndexJson(args, cache, errorChannel)) register(mux, Prefix+"/json/:page", serveIndexJson(args, cache, errorChannel))
} }
} }
registerHandler(mux, Prefix+"/available_extensions", serveAvailableExtensions()) register(mux, Prefix+"/available_extensions", serveAvailableExtensions())
registerHandler(mux, Prefix+"/enabled_extensions", serveEnabledExtensions(formats)) register(mux, Prefix+"/enabled_extensions", serveEnabledExtensions(formats))
registerHandler(mux, Prefix+"/available_mime_types", serveAvailableMimeTypes()) register(mux, Prefix+"/available_mime_types", serveAvailableMimeTypes())
registerHandler(mux, Prefix+"/enabled_mime_types", serveEnabledMimeTypes(formats)) register(mux, Prefix+"/enabled_mime_types", serveEnabledMimeTypes(formats))
} }

View File

@ -5,29 +5,15 @@ Copyright © 2023 Seednode <seednode@seedno.de>
package cmd package cmd
import ( import (
"fmt"
"net/http"
"net/http/pprof" "net/http/pprof"
"time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
) )
func registerProfileHandler(mux *httprouter.Router, verb, path string, handler http.HandlerFunc) {
mux.HandlerFunc(verb, path, handler)
if Handlers {
fmt.Printf("%s | SERVE: Registered handler for %s\n",
time.Now().Format(logDate),
path,
)
}
}
func registerProfileHandlers(mux *httprouter.Router) { func registerProfileHandlers(mux *httprouter.Router) {
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/", pprof.Index) mux.HandlerFunc("GET", Prefix+"/debug/pprof/", pprof.Index)
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/cmdline", pprof.Cmdline) mux.HandlerFunc("GET", Prefix+"/debug/pprof/cmdline", pprof.Cmdline)
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/profile", pprof.Profile) mux.HandlerFunc("GET", Prefix+"/debug/pprof/profile", pprof.Profile)
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/symbol", pprof.Symbol) mux.HandlerFunc("GET", Prefix+"/debug/pprof/symbol", pprof.Symbol)
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/trace", pprof.Trace) mux.HandlerFunc("GET", Prefix+"/debug/pprof/trace", pprof.Trace)
} }

View File

@ -12,7 +12,7 @@ import (
) )
const ( const (
ReleaseVersion string = "1.1.0" ReleaseVersion string = "0.96.4"
) )
var ( var (

View File

@ -1,57 +0,0 @@
/*
Copyright © 2023 Seednode <seednode@seedno.de>
*/
package cmd
import (
"fmt"
"strconv"
)
type splitPath struct {
base string
number string
extension string
}
func (splitPath *splitPath) increment() {
length := len(splitPath.number)
asInt, err := strconv.Atoi(splitPath.number)
if err != nil {
return
}
splitPath.number = fmt.Sprintf("%0*d", length, asInt+1)
}
func (splitPath *splitPath) decrement() {
length := len(splitPath.number)
asInt, err := strconv.Atoi(splitPath.number)
if err != nil {
return
}
splitPath.number = fmt.Sprintf("%0*d", length, asInt-1)
}
func split(path string, regexes *regexes) (*splitPath, int, error) {
p := splitPath{}
split := regexes.filename.FindAllStringSubmatch(path, -1)
if len(split) < 1 || len(split[0]) < 3 {
return &splitPath{}, 0, nil
}
p.base = split[0][1]
p.number = split[0][2]
p.extension = split[0][3]
return &p, len(p.number), nil
}

View File

@ -47,7 +47,12 @@ func splitQueryParams(query string, regexes *regexes) []string {
params := strings.Split(query, ",") params := strings.Split(query, ",")
for i := 0; i < len(params); i++ { for i := 0; i < len(params); i++ {
results = append(results, params[i]) switch {
case regexes.alphanumeric.MatchString(params[i]) && CaseSensitive:
results = append(results, params[i])
case regexes.alphanumeric.MatchString(params[i]):
results = append(results, strings.ToLower(params[i]))
}
} }
return results return results

View File

@ -37,7 +37,7 @@ const (
logDate string = `2006-01-02T15:04:05.000-07:00` logDate string = `2006-01-02T15:04:05.000-07:00`
sourcePrefix string = `/source` sourcePrefix string = `/source`
mediaPrefix string = `/view` mediaPrefix string = `/view`
redirectStatusCode int = http.StatusSeeOther RedirectStatusCode int = http.StatusSeeOther
timeout time.Duration = 10 * time.Second timeout time.Duration = 10 * time.Second
) )
@ -231,7 +231,7 @@ func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *type
preparePath(path), preparePath(path),
queryParams, queryParams,
) )
http.Redirect(w, r, newUrl, redirectStatusCode) http.Redirect(w, r, newUrl, RedirectStatusCode)
} }
} }
@ -362,14 +362,11 @@ func serveVersion() httprouter.Handle {
} }
} }
func registerHandler(mux *httprouter.Router, path string, handle httprouter.Handle) { func register(mux *httprouter.Router, path string, handle httprouter.Handle) {
mux.GET(path, handle) mux.GET(path, handle)
if Handlers { if Handlers {
fmt.Printf("%s | SERVE: Registered handler for %s\n", fmt.Printf("Registered handler for path %s\n", path)
time.Now().Format(logDate),
path,
)
} }
} }
@ -380,7 +377,7 @@ func redirectRoot() httprouter.Handle {
Prefix, Prefix,
) )
http.Redirect(w, r, newUrl, redirectStatusCode) http.Redirect(w, r, newUrl, RedirectStatusCode)
} }
} }
@ -443,22 +440,30 @@ func ServePage(args []string) error {
return ErrNoMediaFound return ErrNoMediaFound
} }
regexes := &regexes{
filename: regexp.MustCompile(`(.+?)([0-9]*)(\..+)`),
alphanumeric: regexp.MustCompile(`^[A-z0-9]*$`),
}
if !strings.HasSuffix(Prefix, "/") {
Prefix = Prefix + "/"
}
listenHost := net.JoinHostPort(Bind, strconv.Itoa(Port)) listenHost := net.JoinHostPort(Bind, strconv.Itoa(Port))
if Verbose {
fmt.Printf("%s | SERVE: Listening on %s...\n",
time.Now().Format(logDate),
listenHost,
)
}
cache := &fileCache{ cache := &fileCache{
mutex: sync.RWMutex{}, mutex: sync.RWMutex{},
list: []string{}, list: []string{},
} }
err = importCache(paths, cache, formats)
if err != nil {
return err
}
regexes := &regexes{
filename: regexp.MustCompile(`(.+)([0-9]{3})(\..+)`),
alphanumeric: regexp.MustCompile(`^[A-z0-9]*$`),
}
mux := httprouter.New() mux := httprouter.New()
srv := &http.Server{ srv := &http.Server{
@ -471,25 +476,29 @@ func ServePage(args []string) error {
mux.PanicHandler = serverErrorHandler() mux.PanicHandler = serverErrorHandler()
if !strings.HasSuffix(Prefix, "/") {
Prefix = Prefix + "/"
}
errorChannel := make(chan error) errorChannel := make(chan error)
registerHandler(mux, Prefix, serveRoot(paths, regexes, cache, formats, errorChannel)) register(mux, Prefix, serveRoot(paths, regexes, cache, formats, errorChannel))
Prefix = strings.TrimSuffix(Prefix, "/") Prefix = strings.TrimSuffix(Prefix, "/")
if Prefix != "" { if Prefix != "" {
registerHandler(mux, "/", redirectRoot()) register(mux, "/", redirectRoot())
} }
registerHandler(mux, Prefix+"/favicons/*favicon", serveFavicons()) register(mux, Prefix+"/favicons/*favicon", serveFavicons())
registerHandler(mux, Prefix+"/favicon.ico", serveFavicons()) register(mux, Prefix+"/favicon.ico", serveFavicons())
registerHandler(mux, Prefix+mediaPrefix+"/*media", serveMedia(paths, regexes, cache, formats, errorChannel)) register(mux, Prefix+mediaPrefix+"/*media", serveMedia(paths, regexes, cache, formats, errorChannel))
registerHandler(mux, Prefix+sourcePrefix+"/*static", serveStaticFile(paths, cache, errorChannel)) register(mux, Prefix+sourcePrefix+"/*static", serveStaticFile(paths, cache, errorChannel))
registerHandler(mux, Prefix+"/version", serveVersion()) register(mux, Prefix+"/version", serveVersion())
if Cache { if Cache {
err = registerCacheHandlers(mux, args, cache, formats, errorChannel) err = registerCacheHandlers(mux, args, cache, formats, errorChannel)
@ -510,11 +519,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")
} }
err = importCache(paths, cache, formats)
if err != nil {
return err
}
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)
@ -527,14 +531,6 @@ func ServePage(args []string) error {
} }
}() }()
if Verbose {
fmt.Printf("%s | SERVE: Listening on http://%s%s/\n",
time.Now().Format(logDate),
listenHost,
Prefix,
)
}
err = srv.ListenAndServe() err = srv.ListenAndServe()
if !errors.Is(err, http.ErrServerClosed) { if !errors.Is(err, http.ErrServerClosed) {
return err return err