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.
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.
@ -77,7 +77,7 @@ Enjoy!
## 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).
@ -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.
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
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()
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),
length,
path,
time.Since(startTime),
)
}
@ -147,10 +146,9 @@ func (cache *fileCache) Import(path string) error {
}
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),
length,
path,
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 {
registerHandler(mux, Prefix+"/clear_cache", serveCacheClear(args, cache, formats, errorChannel))
register(mux, Prefix+"/clear_cache", serveCacheClear(args, cache, formats, errorChannel))
return nil
}

View File

@ -40,6 +40,20 @@ type scanStatsChannels struct {
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 {
const unit = 1000
@ -71,45 +85,65 @@ func kill(path string, cache *fileCache) error {
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) {
path, err := pickFile(list)
if err != nil {
return "", err
}
if sortOrder == "asc" || sortOrder == "desc" {
splitPath, length, err := split(path, regexes)
splitPath, err := split(path, regexes)
if err != nil {
return "", err
}
splitPath.number = 1
switch {
case sortOrder == "asc":
path, err = tryExtensions(splitPath, formats)
if err != nil {
return "", err
}
switch {
case sortOrder == "asc":
splitPath.number = fmt.Sprintf("%0*d", length, 1)
case sortOrder == "desc":
for {
splitPath.increment()
path, err = tryExtensions(splitPath, formats)
if err != nil {
return "", err
}
case sortOrder == "desc":
for {
splitPath.increment()
if path == "" {
splitPath.decrement()
path, err = tryExtensions(splitPath, formats)
if err != nil {
return "", err
}
if path == "" {
splitPath.decrement()
path, err = tryExtensions(splitPath, formats)
if err != nil {
return "", err
}
break
}
break
}
}
}
@ -117,8 +151,8 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats *types.T
return path, nil
}
func nextFile(filePath, sortOrder string, regexes *regexes, formats *types.Types) (string, error) {
splitPath, _, err := split(filePath, regexes)
func nextFile(path, sortOrder string, regexes *regexes, formats *types.Types) (string, error) {
splitPath, err := split(path, regexes)
if err != nil {
return "", err
}
@ -132,27 +166,27 @@ func nextFile(filePath, sortOrder string, regexes *regexes, formats *types.Types
return "", nil
}
path, err := tryExtensions(splitPath, formats)
fileName, err := tryExtensions(splitPath, formats)
if err != nil {
return "", err
}
return path, err
return fileName, err
}
func tryExtensions(splitPath *splitPath, formats *types.Types) (string, error) {
var path string
var fileName string
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 {
return "", err
}
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)
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:
for {
select {
case path := <-fileChannel:
list = append(list, path)
case stat := <-statsChannels.filesMatched:
stats.filesMatched = stats.filesMatched + stat
case stat := <-statsChannels.filesSkipped:
stats.filesSkipped = stats.filesSkipped + stat
case stat := <-statsChannels.directoriesMatched:
stats.directoriesMatched = stats.directoriesMatched + stat
case stat := <-statsChannels.directoriesSkipped:
stats.directoriesSkipped = stats.directoriesSkipped + stat
case err := <-errorChannel:
return []string{}, err
case p := <-fileChannel:
list = append(list, p)
case s := <-statsChannels.filesMatched:
stats.filesMatched = stats.filesMatched + s
case s := <-statsChannels.filesSkipped:
stats.filesSkipped = stats.filesSkipped + s
case s := <-statsChannels.directoriesMatched:
stats.directoriesMatched = stats.directoriesMatched + s
case s := <-statsChannels.directoriesSkipped:
stats.directoriesSkipped = stats.directoriesSkipped + s
case e := <-errorChannel:
return []string{}, e
case <-done:
break Poll
}
@ -398,7 +432,7 @@ Poll:
}
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),
stats.filesMatched,
stats.filesMatched+stats.filesSkipped,
@ -509,12 +543,12 @@ func validatePaths(args []string, formats *types.Types) ([]string, error) {
pathMatches := (args[i] == path)
hasSupportedFiles, err := hasSupportedFiles(path, formats)
hasSupportedFiles, err := pathHasSupportedFiles(path, formats)
if err != nil {
return nil, err
}
var addPath = false
var addPath bool = false
switch {
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) {
if Cache {
registerHandler(mux, Prefix+"/html", serveIndexHtml(args, cache, false))
register(mux, Prefix+"/html/", serveIndexHtml(args, cache, false))
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 {
registerHandler(mux, Prefix+"/json/:page", serveIndexJson(args, cache, errorChannel))
register(mux, Prefix+"/json/:page", serveIndexJson(args, cache, errorChannel))
}
}
registerHandler(mux, Prefix+"/available_extensions", serveAvailableExtensions())
registerHandler(mux, Prefix+"/enabled_extensions", serveEnabledExtensions(formats))
registerHandler(mux, Prefix+"/available_mime_types", serveAvailableMimeTypes())
registerHandler(mux, Prefix+"/enabled_mime_types", serveEnabledMimeTypes(formats))
register(mux, Prefix+"/available_extensions", serveAvailableExtensions())
register(mux, Prefix+"/enabled_extensions", serveEnabledExtensions(formats))
register(mux, Prefix+"/available_mime_types", serveAvailableMimeTypes())
register(mux, Prefix+"/enabled_mime_types", serveEnabledMimeTypes(formats))
}

View File

@ -5,29 +5,15 @@ Copyright © 2023 Seednode <seednode@seedno.de>
package cmd
import (
"fmt"
"net/http"
"net/http/pprof"
"time"
"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) {
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/", pprof.Index)
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/cmdline", pprof.Cmdline)
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/profile", pprof.Profile)
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/symbol", pprof.Symbol)
registerProfileHandler(mux, "GET", Prefix+"/debug/pprof/trace", pprof.Trace)
mux.HandlerFunc("GET", Prefix+"/debug/pprof/", pprof.Index)
mux.HandlerFunc("GET", Prefix+"/debug/pprof/cmdline", pprof.Cmdline)
mux.HandlerFunc("GET", Prefix+"/debug/pprof/profile", pprof.Profile)
mux.HandlerFunc("GET", Prefix+"/debug/pprof/symbol", pprof.Symbol)
mux.HandlerFunc("GET", Prefix+"/debug/pprof/trace", pprof.Trace)
}

View File

@ -12,7 +12,7 @@ import (
)
const (
ReleaseVersion string = "1.1.0"
ReleaseVersion string = "0.96.4"
)
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, ",")
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

View File

@ -37,7 +37,7 @@ const (
logDate string = `2006-01-02T15:04:05.000-07:00`
sourcePrefix string = `/source`
mediaPrefix string = `/view`
redirectStatusCode int = http.StatusSeeOther
RedirectStatusCode int = http.StatusSeeOther
timeout time.Duration = 10 * time.Second
)
@ -231,7 +231,7 @@ func serveRoot(paths []string, regexes *regexes, cache *fileCache, formats *type
preparePath(path),
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)
if Handlers {
fmt.Printf("%s | SERVE: Registered handler for %s\n",
time.Now().Format(logDate),
path,
)
fmt.Printf("Registered handler for path %s\n", path)
}
}
@ -380,7 +377,7 @@ func redirectRoot() httprouter.Handle {
Prefix,
)
http.Redirect(w, r, newUrl, redirectStatusCode)
http.Redirect(w, r, newUrl, RedirectStatusCode)
}
}
@ -443,22 +440,30 @@ func ServePage(args []string) error {
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))
if Verbose {
fmt.Printf("%s | SERVE: Listening on %s...\n",
time.Now().Format(logDate),
listenHost,
)
}
cache := &fileCache{
mutex: sync.RWMutex{},
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()
srv := &http.Server{
@ -471,25 +476,29 @@ func ServePage(args []string) error {
mux.PanicHandler = serverErrorHandler()
if !strings.HasSuffix(Prefix, "/") {
Prefix = Prefix + "/"
}
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, "/")
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 {
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")
}
err = importCache(paths, cache, formats)
if err != nil {
return err
}
go func() {
for err := range errorChannel {
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()
if !errors.Is(err, http.ErrServerClosed) {
return err