Continue scanning instead of aborting on fs.ErrPermission or fs.ErrNotExist; add --debug flag to view output from those errors; initialize error logging earlier in setup

This commit is contained in:
Seednode 2024-01-06 09:41:30 -06:00
parent e9d75ac6f3
commit 6f29f89acf
5 changed files with 84 additions and 145 deletions

View File

@ -153,9 +153,10 @@ Flags:
--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")
--compression string compression format to use for index (flate, gzip, lz4, lzw, none, snappy, zlib, zstd) (default "zstd") --compression string compression format to use for index (flate, gzip, lz5, lzw, none, snappy, zlib, zstd) (default "zstd")
--compression-fast use fastest compression level (default is best) --compression-fast use fastest compression level (default is best)
--concurrency int maximum concurrency for scan threads (default 8192) --concurrency int maximum concurrency for scan threads (default 9223372036854775807)
-d, --debug display even more verbose logs
--disable-buttons disable first/prev/next/last buttons --disable-buttons disable first/prev/next/last buttons
--exit-on-error shut down webserver on error, instead of just printing error --exit-on-error shut down webserver on error, instead of just printing error
--fallback serve files as application/octet-stream if no matching format is registered --fallback serve files as application/octet-stream if no matching format is registered

View File

@ -240,74 +240,74 @@ func hasSupportedFiles(path string, formats types.Types) (bool, error) {
} }
} }
func walkPath(path string, fileChannel chan<- string, stats *scanStats, limit chan struct{}, formats types.Types) error { func walkPath(path string, fileChannel chan<- string, wg0 *sync.WaitGroup, stats *scanStats, limit chan struct{}, formats types.Types, errorChannel chan<- error) {
defer func() {
wg0.Done()
}()
limit <- struct{}{} limit <- struct{}{}
defer func() { defer func() {
<-limit <-limit
}() }()
errorChannel := make(chan error)
done := make(chan bool)
nodes, err := os.ReadDir(path) nodes, err := os.ReadDir(path)
if err != nil { if err != nil {
return err stats.directoriesSkipped <- 1
errorChannel <- err
return
} }
var directories, files = 0, 0 var files = 0
var skipDir = false var skipDir = false
for _, node := range nodes { for _, node := range nodes {
if node.IsDir() { if Ignore && !node.IsDir() && node.Name() == IgnoreFile {
directories++
} else {
if Ignore && node.Name() == IgnoreFile {
skipDir = true skipDir = true
} }
files++ files++
} }
}
var skipFiles = false var skipFiles = false
if files <= MaxFileCount && files >= MinFileCount && !skipDir { if files > MaxFileCount || files < MinFileCount || skipDir {
stats.directoriesMatched <- 1
} else {
stats.filesSkipped <- files stats.filesSkipped <- files
stats.directoriesSkipped <- 1 stats.directoriesSkipped <- 1
skipFiles = true skipFiles = true
} else {
stats.directoriesMatched <- 1
} }
var wg sync.WaitGroup var wg1 sync.WaitGroup
wg.Add(1) wg1.Add(1)
go func() { go func() {
defer wg.Done() defer wg1.Done()
for _, node := range nodes { for _, node := range nodes {
wg.Add(1) wg1.Add(1)
go func(node fs.DirEntry) { go func(node fs.DirEntry) {
defer wg.Done() defer wg1.Done()
fullPath := filepath.Join(path, node.Name()) fullPath := filepath.Join(path, node.Name())
switch { switch {
case node.IsDir() && Recursive: case node.IsDir() && Recursive:
err := walkPath(fullPath, fileChannel, stats, limit, formats) wg0.Add(1)
if err != nil {
errorChannel <- err
return walkPath(fullPath, fileChannel, wg0, stats, limit, formats, errorChannel)
}
case !node.IsDir() && !skipFiles: case !node.IsDir() && !skipFiles:
path, err := normalizePath(fullPath) path, err := normalizePath(fullPath)
if err != nil { if err != nil {
errorChannel <- err errorChannel <- err
stats.filesSkipped <- 1
return return
} }
@ -325,35 +325,16 @@ func walkPath(path string, fileChannel chan<- string, stats *scanStats, limit ch
} }
}() }()
go func() { wg1.Wait()
wg.Wait()
time.Sleep(1 * time.Microsecond)
close(done)
}()
Poll:
for {
select {
case err := <-errorChannel:
return err
case <-done:
break Poll
}
}
return nil
} }
func scanPaths(paths []string, sort string, index *fileIndex, formats types.Types) ([]string, error) { func scanPaths(paths []string, sort string, index *fileIndex, formats types.Types, errorChannel chan<- error) []string {
startTime := time.Now() startTime := time.Now()
var filesMatched, filesSkipped int var filesMatched, filesSkipped int
var directoriesMatched, directoriesSkipped int var directoriesMatched, directoriesSkipped int
fileChannel := make(chan string) fileChannel := make(chan string)
errorChannel := make(chan error)
done := make(chan bool) done := make(chan bool)
stats := &scanStats{ stats := &scanStats{
@ -422,41 +403,19 @@ func scanPaths(paths []string, sort string, index *fileIndex, formats types.Type
limit := make(chan struct{}, Concurrency) limit := make(chan struct{}, Concurrency)
var wg sync.WaitGroup var wg0 sync.WaitGroup
for i := 0; i < len(paths); i++ { for i := 0; i < len(paths); i++ {
wg.Add(1) wg0.Add(1)
go func(i int) { go func(i int) {
defer func() { walkPath(paths[i], fileChannel, &wg0, stats, limit, formats, errorChannel)
wg.Done()
}()
err := walkPath(paths[i], fileChannel, stats, limit, formats)
if err != nil {
errorChannel <- err
return
}
}(i) }(i)
} }
go func() { wg0.Wait()
wg.Wait()
close(done) close(done)
}()
Poll:
for {
select {
case err := <-errorChannel:
return []string{}, err
case <-done:
break Poll
}
}
if Verbose { if Verbose {
fmt.Printf("%s | INDEX: Selected %d/%d files across %d/%d directories in %s\n", fmt.Printf("%s | INDEX: Selected %d/%d files across %d/%d directories in %s\n",
@ -471,45 +430,27 @@ Poll:
slices.Sort(list) slices.Sort(list)
return list, nil return list
} }
func fileList(paths []string, filters *filters, sort string, index *fileIndex, formats types.Types) ([]string, error) { func fileList(paths []string, filters *filters, sort string, index *fileIndex, formats types.Types, errorChannel chan<- error) []string {
switch { switch {
case Index && !index.isEmpty() && filters.isEmpty(): case Index && !index.isEmpty() && filters.isEmpty():
return index.List(), nil return index.List()
case Index && !index.isEmpty() && !filters.isEmpty(): case Index && !index.isEmpty() && !filters.isEmpty():
return filters.apply(index.List()), nil return filters.apply(index.List())
case Index && index.isEmpty() && !filters.isEmpty(): case Index && index.isEmpty() && !filters.isEmpty():
list, err := scanPaths(paths, sort, index, formats) index.set(scanPaths(paths, sort, index, formats, errorChannel))
if err != nil {
return []string{}, err
}
index.set(list)
return filters.apply(index.List()), nil return filters.apply(index.List())
case Index && index.isEmpty() && filters.isEmpty(): case Index && index.isEmpty() && filters.isEmpty():
list, err := scanPaths(paths, sort, index, formats) index.set(scanPaths(paths, sort, index, formats, errorChannel))
if err != nil {
return []string{}, err
}
index.set(list)
return index.List(), nil return index.List()
case !Index && !filters.isEmpty(): case !Index && !filters.isEmpty():
list, err := scanPaths(paths, sort, index, formats) return filters.apply(scanPaths(paths, sort, index, formats, errorChannel))
if err != nil {
return []string{}, err
}
return filters.apply(list), nil
default: default:
list, err := scanPaths(paths, sort, index, formats) return scanPaths(paths, sort, index, formats, errorChannel)
if err != nil {
return []string{}, err
}
return list, nil
} }
} }

View File

@ -266,12 +266,7 @@ func serveIndexRebuild(args []string, index *fileIndex, formats types.Types, err
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
index.clear() index.clear()
_, err := fileList(args, &filters{}, "", index, formats) fileList(args, &filters{}, "", index, formats, errorChannel)
if err != nil {
errorChannel <- err
return
}
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
@ -279,18 +274,13 @@ func serveIndexRebuild(args []string, index *fileIndex, formats types.Types, err
} }
} }
func importIndex(args []string, index *fileIndex, formats types.Types) error { func importIndex(args []string, index *fileIndex, formats types.Types, errorChannel chan<- error) {
if IndexFile != "" { if IndexFile != "" {
err := index.Import(IndexFile) err := index.Import(IndexFile)
if err == nil { if err == nil {
return nil return
} }
} }
_, err := fileList(args, &filters{}, "", index, formats) fileList(args, &filters{}, "", index, formats, errorChannel)
if err != nil {
return err
}
return nil
} }

View File

@ -15,7 +15,7 @@ import (
const ( const (
AllowedCharacters string = `^[A-z0-9.\-_]+$` AllowedCharacters string = `^[A-z0-9.\-_]+$`
ReleaseVersion string = "3.12.1" ReleaseVersion string = "4.0.0"
) )
var ( var (
@ -31,6 +31,7 @@ var (
Compression string Compression string
CompressionFast bool CompressionFast bool
Concurrency int Concurrency int
Debug bool
DisableButtons bool DisableButtons bool
ExitOnError bool ExitOnError bool
Fallback bool Fallback bool
@ -138,7 +139,8 @@ func init() {
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().StringVar(&Compression, "compression", "zstd", "compression format to use for index (flate, gzip, lz5, lzw, none, snappy, zlib, zstd)") rootCmd.Flags().StringVar(&Compression, "compression", "zstd", "compression format to use for index (flate, gzip, lz5, lzw, none, snappy, zlib, zstd)")
rootCmd.Flags().BoolVar(&CompressionFast, "compression-fast", false, "use fastest compression level (default is best)") rootCmd.Flags().BoolVar(&CompressionFast, "compression-fast", false, "use fastest compression level (default is best)")
rootCmd.Flags().IntVar(&Concurrency, "concurrency", 8192, "maximum concurrency for scan threads") rootCmd.Flags().IntVar(&Concurrency, "concurrency", int(^uint(0)>>1), "maximum concurrency for scan threads")
rootCmd.Flags().BoolVarP(&Debug, "debug", "d", false, "display even more verbose logs")
rootCmd.Flags().BoolVar(&DisableButtons, "disable-buttons", false, "disable first/prev/next/last buttons") rootCmd.Flags().BoolVar(&DisableButtons, "disable-buttons", false, "disable first/prev/next/last buttons")
rootCmd.Flags().BoolVar(&ExitOnError, "exit-on-error", false, "shut down webserver on error, instead of just printing error") rootCmd.Flags().BoolVar(&ExitOnError, "exit-on-error", false, "shut down webserver on error, instead of just printing error")
rootCmd.Flags().BoolVar(&Fallback, "fallback", false, "serve files as application/octet-stream if no matching format is registered") rootCmd.Flags().BoolVar(&Fallback, "fallback", false, "serve files as application/octet-stream if no matching format is registered")
@ -172,6 +174,8 @@ func init() {
rootCmd.Flags().SetInterspersed(true) rootCmd.Flags().SetInterspersed(true)
rootCmd.MarkFlagsMutuallyExclusive("debug", "exit-on-error")
rootCmd.MarkFlagsOneRequired(RequiredArgs...) rootCmd.MarkFlagsOneRequired(RequiredArgs...)
rootCmd.SetHelpCommand(&cobra.Command{ rootCmd.SetHelpCommand(&cobra.Command{

View File

@ -206,14 +206,7 @@ func serveRoot(paths []string, regexes *regexes, index *fileIndex, formats types
} }
} }
list, err := fileList(paths, filters, sortOrder, index, formats) list := fileList(paths, filters, sortOrder, index, formats, errorChannel)
if err != nil {
errorChannel <- err
serverError(w, r, nil)
return
}
loop: loop:
for timeout := time.After(timeout); ; { for timeout := time.After(timeout); ; {
@ -552,6 +545,31 @@ func ServePage(args []string) error {
errorChannel := make(chan error) errorChannel := make(chan error)
go func() {
for err := range errorChannel {
prefix := "ERROR"
switch {
case errors.Is(err, os.ErrNotExist) && Debug:
prefix = "DEBUG"
case errors.Is(err, os.ErrNotExist):
continue
case errors.Is(err, os.ErrPermission) && Debug:
prefix = "DEBUG"
case errors.Is(err, os.ErrPermission):
continue
}
fmt.Printf("%s | %s: %v\n", time.Now().Format(logDate), prefix, err)
if ExitOnError {
fmt.Printf("%s | %s: Shutting down...\n", time.Now().Format(logDate), prefix)
srv.Shutdown(context.Background())
}
}
}()
registerHandler(mux, Prefix, serveRoot(paths, regexes, index, formats, errorChannel)) registerHandler(mux, Prefix, serveRoot(paths, regexes, index, formats, errorChannel))
Prefix = strings.TrimSuffix(Prefix, "/") Prefix = strings.TrimSuffix(Prefix, "/")
@ -573,10 +591,7 @@ func ServePage(args []string) error {
if Index { if Index {
registerHandler(mux, Prefix+AdminPrefix+"/index/rebuild", serveIndexRebuild(args, index, formats, errorChannel)) registerHandler(mux, Prefix+AdminPrefix+"/index/rebuild", serveIndexRebuild(args, index, formats, errorChannel))
err = importIndex(paths, index, formats) importIndex(paths, index, formats, errorChannel)
if err != nil {
return err
}
} }
if Info { if Info {
@ -591,18 +606,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())
}
}
}()
if Verbose { if Verbose {
fmt.Printf("%s | SERVE: Listening on http://%s%s/\n", fmt.Printf("%s | SERVE: Listening on http://%s%s/\n",
time.Now().Format(logDate), time.Now().Format(logDate),