diff --git a/cmd/files.go b/cmd/files.go index 20b73fb..05efdc1 100644 --- a/cmd/files.go +++ b/cmd/files.go @@ -87,7 +87,7 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats types.Ty } case sortOrder == "desc": for { - splitPath.increment() + splitPath.number = splitPath.increment() path, err = tryExtensions(splitPath, formats) if err != nil { @@ -95,7 +95,7 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats types.Ty } if path == "" { - splitPath.decrement() + splitPath.number = splitPath.decrement() path, err = tryExtensions(splitPath, formats) if err != nil { @@ -119,9 +119,9 @@ func nextFile(filePath, sortOrder string, regexes *regexes, formats types.Types) switch { case sortOrder == "asc": - splitPath.increment() + splitPath.number = splitPath.increment() case sortOrder == "desc": - splitPath.decrement() + splitPath.number = splitPath.decrement() default: return "", nil } diff --git a/cmd/index.go b/cmd/index.go index 6a8837c..f1a9a37 100644 --- a/cmd/index.go +++ b/cmd/index.go @@ -186,13 +186,15 @@ func registerIndexHandlers(mux *httprouter.Router, args []string, index *fileInd func importIndex(args []string, index *fileIndex, formats types.Types) error { if IndexFile != "" { err := index.Import(IndexFile) - if err != nil { - _, err := fileList(args, &filters{}, "", index, formats) - if err != nil { - return err - } + if err == nil { + return nil } } + _, err := fileList(args, &filters{}, "", index, formats) + if err != nil { + return err + } + return nil } diff --git a/cmd/info.go b/cmd/info.go index 852ffa4..9d7de79 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -19,7 +19,7 @@ import ( "seedno.de/seednode/roulette/types" ) -func paginate(page int, fileCount int, ending bool) string { +func paginateIndex(page int, fileCount int, ending bool) string { var html strings.Builder var firstPage int = 1 @@ -120,8 +120,8 @@ func serveIndexHtml(args []string, index *fileIndex, shouldPaginate bool) httpro htmlBody.WriteString(`table,td,tr{border:none;}td{border-bottom:1px solid black;}td{white-space:nowrap;padding:.5em}`) htmlBody.WriteString(fmt.Sprintf("Index contains %d files", fileCount)) - if shouldPaginate { - htmlBody.WriteString(paginate(page, fileCount, false)) + if shouldPaginate && !DisableButtons { + htmlBody.WriteString(paginateIndex(page, fileCount, false)) } if len(indexDump) > 0 { @@ -135,8 +135,8 @@ func serveIndexHtml(args []string, index *fileIndex, shouldPaginate bool) httpro } } - if shouldPaginate { - htmlBody.WriteString(paginate(page, fileCount, true)) + if shouldPaginate && !DisableButtons { + htmlBody.WriteString(paginateIndex(page, fileCount, true)) } htmlBody.WriteString(`
`) diff --git a/cmd/root.go b/cmd/root.go index 0959f95..3da7ada 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,40 +12,41 @@ import ( ) const ( - ReleaseVersion string = "3.0.2" + ReleaseVersion string = "3.1.0" ) var ( - All bool - Audio bool - Bind string - CaseSensitive bool - Code bool - CodeTheme string - ExitOnError bool - Fallback bool - Filtering bool - Flash bool - Fun bool - Handlers bool - Images bool - Index bool - IndexFile string - Info bool - MaxFileCount int - MinFileCount int - PageLength int - Port int - Prefix string - Profile bool - Recursive bool - Refresh bool - Russian bool - Sorting bool - Text bool - Verbose bool - Version bool - Videos bool + All bool + Audio bool + Bind string + CaseSensitive bool + Code bool + CodeTheme string + DisableButtons bool + ExitOnError bool + Fallback bool + Filtering bool + Flash bool + Fun bool + Handlers bool + Images bool + Index bool + IndexFile string + Info bool + MaxFileCount int + MinFileCount int + PageLength int + Port int + Prefix string + Profile bool + Recursive bool + Refresh bool + Russian bool + Sorting bool + Text bool + Verbose bool + Version bool + Videos bool rootCmd = &cobra.Command{ Use: "roulette [path]...", @@ -88,6 +89,7 @@ func init() { rootCmd.Flags().BoolVar(&CaseSensitive, "case-sensitive", false, "use case-sensitive matching for filters") 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().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 the error") rootCmd.Flags().BoolVar(&Fallback, "fallback", false, "serve files as application/octet-stream if no matching format is registered") rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering") diff --git a/cmd/sort.go b/cmd/sort.go index 254ae88..e2c2888 100644 --- a/cmd/sort.go +++ b/cmd/sort.go @@ -6,8 +6,12 @@ package cmd import ( "fmt" + "sort" + "strings" "strconv" + + "seedno.de/seednode/roulette/types" ) type splitPath struct { @@ -16,22 +20,22 @@ type splitPath struct { extension string } -func (splitPath *splitPath) increment() { +func (splitPath *splitPath) increment() string { asInt, err := strconv.Atoi(splitPath.number) if err != nil { - return + return "" } - splitPath.number = fmt.Sprintf("%0*d", len(splitPath.number), asInt+1) + return fmt.Sprintf("%0*d", len(splitPath.number), asInt+1) } -func (splitPath *splitPath) decrement() { +func (splitPath *splitPath) decrement() string { asInt, err := strconv.Atoi(splitPath.number) if err != nil { - return + return "" } - splitPath.number = fmt.Sprintf("%0*d", len(splitPath.number), asInt-1) + return fmt.Sprintf("%0*d", len(splitPath.number), asInt-1) } func split(path string, regexes *regexes) (*splitPath, error) { @@ -49,3 +53,126 @@ func split(path string, regexes *regexes) (*splitPath, error) { return p, nil } + +func getRange(path string, regexes *regexes, index *fileIndex) (string, string, error) { + splitPath, err := split(path, regexes) + if err != nil { + return "", "", err + } + + list := index.List() + + sort.Slice(list, func(i, j int) bool { + return list[i] <= list[j] + }) + + var first, last, previous string + +Loop: + for _, val := range list { + splitVal, err := split(val, regexes) + if err != nil { + return "", "", err + } + + switch { + case splitVal.base == splitPath.base && first == "": + first = val + case splitVal.base != splitPath.base && first != "": + last = previous + + break Loop + } + + previous = val + } + + return first, last, nil +} + +func paginateSorted(path, first, last, queryParams string, regexes *regexes, formats types.Types) (string, error) { + split, err := split(path, regexes) + if err != nil { + return "", err + } + + var firstStatus, prevStatus, nextStatus, lastStatus string = "", "", "", "" + + if path <= first { + firstStatus = " disabled" + prevStatus = " disabled" + } + + if path >= last { + nextStatus = " disabled" + lastStatus = " disabled" + } + + prevPath := &splitPath{ + base: split.base, + number: split.decrement(), + extension: split.extension, + } + + prevPage, err := tryExtensions(prevPath, formats) + switch { + case err != nil: + return "", err + case prevPage == "": + prevStatus = " disabled" + case prevPage < first: + prevPage = first + } + + nextPath := &splitPath{ + base: split.base, + number: split.increment(), + extension: split.extension, + } + + nextPage, err := tryExtensions(nextPath, formats) + switch { + case err != nil: + return "", err + case nextPage == "": + nextStatus = " disabled" + case nextPage > last: + nextPage = last + } + + var html strings.Builder + + html.WriteString(`
`) + + html.WriteString(fmt.Sprintf(``, + Prefix, + mediaPrefix, + first, + queryParams, + firstStatus)) + + html.WriteString(fmt.Sprintf(``, + Prefix, + mediaPrefix, + prevPage, + queryParams, + prevStatus)) + + html.WriteString(fmt.Sprintf(``, + Prefix, + mediaPrefix, + nextPage, + queryParams, + nextStatus)) + + html.WriteString(fmt.Sprintf(``, + Prefix, + mediaPrefix, + last, + queryParams, + lastStatus)) + + html.WriteString("
") + + return html.String(), nil +} diff --git a/cmd/web.go b/cmd/web.go index 364218b..a75bc26 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -307,7 +307,9 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type refreshTimer, refreshInterval := refreshInterval(r) - rootUrl := Prefix + "/" + generateQueryParams(filters, sortOrder, refreshInterval) + queryParams := generateQueryParams(filters, sortOrder, refreshInterval) + + rootUrl := Prefix + "/" + queryParams var htmlBody strings.Builder htmlBody.WriteString(``) @@ -324,6 +326,33 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type } htmlBody.WriteString(title) htmlBody.WriteString(``) + + var first, last string + + if Index && sortOrder != "" { + first, last, err = getRange(path, regexes, index) + if err != nil { + errorChannel <- err + + serverError(w, r, nil) + + return + } + } + + if Index && !DisableButtons && sortOrder != "" { + paginate, err := paginateSorted(path, first, last, queryParams, regexes, formats) + if err != nil { + errorChannel <- err + + serverError(w, r, nil) + + return + } + + htmlBody.WriteString(paginate) + } + if refreshInterval != "0ms" { htmlBody.WriteString(refreshFunction(rootUrl, refreshTimer)) } @@ -337,6 +366,7 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type return } htmlBody.WriteString(body) + htmlBody.WriteString(``) startTime := time.Now() @@ -456,7 +486,7 @@ func ServePage(args []string) error { // enable image support if no other flags are passed, to retain backwards compatibility // to be replaced with rootCmd.MarkFlagsOneRequired on next spf13/cobra update if Images || All || len(formats) == 0 { - formats.Add(images.Format{Fun: Fun}) + formats.Add(images.Format{DisableButtons: DisableButtons, Fun: Fun}) } paths, err := validatePaths(args, formats) diff --git a/types/images/images.go b/types/images/images.go index 59766cb..c20e11d 100644 --- a/types/images/images.go +++ b/types/images/images.go @@ -26,14 +26,21 @@ type dimensions struct { } type Format struct { - Fun bool + DisableButtons bool + Fun bool } func (t Format) Css() string { var css strings.Builder css.WriteString(`html,body{margin:0;padding:0;height:100%;}`) - css.WriteString(`a{color:inherit;display:block;height:100%;width:100%;text-decoration:none;}`) + + if t.DisableButtons { + css.WriteString(`a{color:inherit;display:block;height:100%;width:100%;text-decoration:none;}`) + } else { + css.WriteString(`a{color:inherit;display:block;height:97%;width:100%;text-decoration:none;}`) + } + css.WriteString(`img{margin:auto;display:block;max-width:97%;max-height:97%;`) css.WriteString(`object-fit:scale-down;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)`) if t.Fun {