Compare commits
6 Commits
7942ea85b5
...
6fea978459
Author | SHA1 | Date |
---|---|---|
Seednode | 6fea978459 | |
Seednode | c0265d2a6e | |
Seednode | f872d0a27e | |
Seednode | d18dc9a41c | |
Seednode | c996f3fc53 | |
Seednode | a29390aa76 |
12
README.md
12
README.md
|
@ -78,17 +78,16 @@ The cache can be regenerated at any time by accessing the `/clear_cache` endpoin
|
|||
|
||||
If `--cache-file` is set, the cache will be loaded from the specified file on start, and written to the file whenever it is re-generated.
|
||||
|
||||
If the `-i|--index` flag is passed, two additional endpoints—`/html` and `/json`—are registered.
|
||||
|
||||
When accessed, these endpoints return the contents of the index, in HTML and JSON formats respectively. This can prove useful when confirming whether the index is generated successfully, or whether a given file is in the index.
|
||||
|
||||
## Statistics
|
||||
|
||||
If the `--stats` flag is passed, an additional endpoint, `/stats`, is registered.
|
||||
|
||||
When accessed, this endpoint returns a JSON document listing every file served, along with the number of times it has been served, its filesize, and timestamps of when it was served.
|
||||
|
||||
## Debug
|
||||
If the `-d|--debug` flag is passed, two additional endpoints—`/html` and `/json`—are registered.
|
||||
|
||||
When accessed, these endpoints return the contents of the index, in HTML and JSON formats respectively. This can prove useful when confirming whether the index is generated successfully, or whether a given file is in the index.
|
||||
|
||||
## Russian
|
||||
If the `--russian` flag is passed, everything functions exactly as you would expect.
|
||||
|
||||
|
@ -113,10 +112,11 @@ Flags:
|
|||
-b, --bind string address to bind to (default "0.0.0.0")
|
||||
-c, --cache generate directory cache at startup
|
||||
--cache-file string path to optional persistent cache file
|
||||
-d, --debug expose debug endpoint
|
||||
-f, --filter enable filtering
|
||||
--flash enable support for shockwave flash files (via ruffle.rs) (default true)
|
||||
-h, --help help for roulette
|
||||
--images enable support for image files (default true)
|
||||
-i, --index expose index endpoints
|
||||
--maximum-files uint skip directories with file counts above this value (default 18446744073709551615)
|
||||
--minimum-files uint skip directories with file counts below this value (default 1)
|
||||
--page-length uint pagination length for statistics and debug pages
|
||||
|
|
188
cmd/debug.go
188
cmd/debug.go
|
@ -1,188 +0,0 @@
|
|||
/*
|
||||
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/yosssi/gohtml"
|
||||
)
|
||||
|
||||
func serveDebugHtml(args []string, index *Index, paginate bool) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
indexDump := index.Index()
|
||||
|
||||
fileCount := len(indexDump)
|
||||
|
||||
var startIndex, stopIndex int
|
||||
|
||||
page, err := strconv.Atoi(p.ByName("page"))
|
||||
if err != nil || page <= 0 {
|
||||
startIndex = 0
|
||||
stopIndex = fileCount
|
||||
} else {
|
||||
startIndex = ((page - 1) * int(pageLength))
|
||||
stopIndex = (startIndex + int(pageLength))
|
||||
}
|
||||
|
||||
if startIndex > (fileCount - 1) {
|
||||
indexDump = []string{}
|
||||
}
|
||||
|
||||
if stopIndex > fileCount {
|
||||
stopIndex = fileCount
|
||||
}
|
||||
|
||||
sort.SliceStable(indexDump, func(p, q int) bool {
|
||||
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
|
||||
})
|
||||
|
||||
var htmlBody strings.Builder
|
||||
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
|
||||
htmlBody.WriteString(FaviconHtml)
|
||||
htmlBody.WriteString(`<style>a{text-decoration:none;height:100%;width:100%;color:inherit;cursor:pointer}`)
|
||||
htmlBody.WriteString(`table,td,tr{border:1px solid black;border-collapse:collapse}td{white-space:nowrap;padding:.5em}</style>`)
|
||||
htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
|
||||
if len(indexDump) > 0 {
|
||||
for _, v := range indexDump[startIndex:stopIndex] {
|
||||
var shouldSort = ""
|
||||
|
||||
if sorting {
|
||||
shouldSort = "?sort=asc"
|
||||
}
|
||||
htmlBody.WriteString(fmt.Sprintf("<tr><td><a href=\"%s%s%s\">%s</a></td></tr>\n", MediaPrefix, v, shouldSort, v))
|
||||
}
|
||||
}
|
||||
if pageLength != 0 {
|
||||
var firstPage int = 1
|
||||
var lastPage int
|
||||
|
||||
if fileCount%int(pageLength) == 0 {
|
||||
lastPage = fileCount / int(pageLength)
|
||||
} else {
|
||||
lastPage = (fileCount / int(pageLength)) + 1
|
||||
}
|
||||
|
||||
if paginate {
|
||||
var prevStatus, nextStatus string = "", ""
|
||||
|
||||
if page <= 1 {
|
||||
prevStatus = " disabled"
|
||||
}
|
||||
|
||||
if page >= lastPage {
|
||||
nextStatus = " disabled"
|
||||
}
|
||||
|
||||
prevPage := page - 1
|
||||
if prevPage < 1 {
|
||||
prevPage = 1
|
||||
}
|
||||
|
||||
nextPage := page + 1
|
||||
if nextPage > lastPage {
|
||||
nextPage = fileCount / int(pageLength)
|
||||
}
|
||||
|
||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">First</button>",
|
||||
firstPage))
|
||||
|
||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\"%s>Prev</button>",
|
||||
prevPage,
|
||||
prevStatus))
|
||||
|
||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\"%s>Next</button>",
|
||||
nextPage,
|
||||
nextStatus))
|
||||
|
||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">Last</button>",
|
||||
lastPage))
|
||||
}
|
||||
}
|
||||
|
||||
htmlBody.WriteString(`</table></body></html>`)
|
||||
|
||||
b, err := io.WriteString(w, gohtml.Format(htmlBody.String()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Printf("%s | Served HTML debug page (%s) to %s in %s\n",
|
||||
startTime.Format(LogDate),
|
||||
humanReadableSize(b),
|
||||
realIP(r),
|
||||
time.Since(startTime).Round(time.Microsecond),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serveDebugJson(args []string, index *Index) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
indexDump := index.Index()
|
||||
|
||||
fileCount := len(indexDump)
|
||||
|
||||
sort.SliceStable(indexDump, func(p, q int) bool {
|
||||
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
|
||||
})
|
||||
|
||||
var startIndex, stopIndex int
|
||||
|
||||
page, err := strconv.Atoi(p.ByName("page"))
|
||||
if err != nil || page <= 0 {
|
||||
startIndex = 0
|
||||
stopIndex = fileCount
|
||||
} else {
|
||||
startIndex = ((page - 1) * int(pageLength))
|
||||
stopIndex = (startIndex + int(pageLength))
|
||||
}
|
||||
|
||||
if startIndex > (fileCount - 1) {
|
||||
indexDump = []string{}
|
||||
}
|
||||
|
||||
if stopIndex > fileCount {
|
||||
stopIndex = fileCount
|
||||
}
|
||||
|
||||
response, err := json.MarshalIndent(indexDump[startIndex:stopIndex], "", " ")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
||||
serverError(w, r, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(response)
|
||||
|
||||
if verbose {
|
||||
fmt.Printf("%s | Served JSON debug page (%s) to %s in %s\n",
|
||||
startTime.Format(LogDate),
|
||||
humanReadableSize(len(response)),
|
||||
realIP(r),
|
||||
time.Since(startTime).Round(time.Microsecond),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ func newErrorPage(title, body string) string {
|
|||
func notFound(w http.ResponseWriter, r *http.Request, filePath string) error {
|
||||
startTime := time.Now()
|
||||
|
||||
if verbose {
|
||||
if Verbose {
|
||||
fmt.Printf("%s | Unavailable file %s requested by %s\n",
|
||||
startTime.Format(LogDate),
|
||||
filePath,
|
||||
|
@ -57,7 +57,7 @@ func notFound(w http.ResponseWriter, r *http.Request, filePath string) error {
|
|||
func serverError(w http.ResponseWriter, r *http.Request, i interface{}) {
|
||||
startTime := time.Now()
|
||||
|
||||
if verbose {
|
||||
if Verbose {
|
||||
fmt.Printf("%s | Invalid request for %s from %s\n",
|
||||
startTime.Format(LogDate),
|
||||
r.URL.Path,
|
||||
|
|
26
cmd/files.go
26
cmd/files.go
|
@ -128,7 +128,7 @@ func appendPath(directory, path string, files *Files, stats *ScanStats, register
|
|||
}
|
||||
|
||||
func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, types *formats.SupportedFormats) error {
|
||||
shouldCache := cache && filters.IsEmpty()
|
||||
shouldCache := Cache && filters.IsEmpty()
|
||||
|
||||
absolutePath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
|
@ -180,7 +180,7 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats,
|
|||
return nil
|
||||
}
|
||||
|
||||
func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *Index, registeredFormats *formats.SupportedFormats) (string, error) {
|
||||
func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) (string, error) {
|
||||
filePath, err := pickFile(paths, filters, sortOrder, index, registeredFormats)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
|
@ -272,8 +272,10 @@ func splitPath(path string, Regexes *Regexes) (*Path, error) {
|
|||
func tryExtensions(p *Path, registeredFormats *formats.SupportedFormats) (string, error) {
|
||||
var fileName string
|
||||
|
||||
for _, extension := range registeredFormats.Extensions() {
|
||||
for _, format := range registeredFormats.Extensions {
|
||||
for _, extension := range format.Extensions {
|
||||
fileName = fmt.Sprintf("%s%.3d%s", p.base, p.number, extension)
|
||||
}
|
||||
|
||||
exists, err := fileExists(fileName)
|
||||
if err != nil {
|
||||
|
@ -310,7 +312,7 @@ func pathIsValid(filePath string, paths []string) bool {
|
|||
}
|
||||
|
||||
switch {
|
||||
case verbose && !matchesPrefix:
|
||||
case Verbose && !matchesPrefix:
|
||||
fmt.Printf("%s | Error: Failed to serve file outside specified path(s): %s\n",
|
||||
time.Now().Format(LogDate),
|
||||
filePath,
|
||||
|
@ -333,7 +335,7 @@ func pathHasSupportedFiles(path string, registeredFormats *formats.SupportedForm
|
|||
}
|
||||
|
||||
switch {
|
||||
case !recursive && info.IsDir() && p != path:
|
||||
case !Recursive && info.IsDir() && p != path:
|
||||
return filepath.SkipDir
|
||||
case !info.IsDir():
|
||||
registered, _, _, err := formats.FileType(p, registeredFormats)
|
||||
|
@ -390,7 +392,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con
|
|||
}
|
||||
|
||||
switch {
|
||||
case !recursive && info.IsDir() && p != path:
|
||||
case !Recursive && info.IsDir() && p != path:
|
||||
return filepath.SkipDir
|
||||
case !info.IsDir():
|
||||
wg.Add(1)
|
||||
|
@ -419,7 +421,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con
|
|||
fmt.Println(err)
|
||||
}
|
||||
|
||||
if files > 0 && (files < minimumFileCount) || (files > maximumFileCount) {
|
||||
if files > 0 && (files < MinimumFileCount) || (files > MaximumFileCount) {
|
||||
// This count will not otherwise include the parent directory itself, so increment by one
|
||||
stats.directoriesSkipped.Add(directories + 1)
|
||||
stats.filesSkipped.Add(files)
|
||||
|
@ -442,8 +444,8 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con
|
|||
return nil
|
||||
}
|
||||
|
||||
func fileList(paths []string, filters *Filters, sort string, index *Index, types *formats.SupportedFormats) ([]string, bool) {
|
||||
if cache && filters.IsEmpty() && !index.IsEmpty() {
|
||||
func fileList(paths []string, filters *Filters, sort string, index *FileIndex, types *formats.SupportedFormats) ([]string, bool) {
|
||||
if Cache && filters.IsEmpty() && !index.IsEmpty() {
|
||||
return index.Index(), true
|
||||
}
|
||||
|
||||
|
@ -496,7 +498,7 @@ func fileList(paths []string, filters *Filters, sort string, index *Index, types
|
|||
return []string{}, false
|
||||
}
|
||||
|
||||
if verbose {
|
||||
if Verbose {
|
||||
fmt.Printf("%s | Indexed %d/%d files across %d/%d directories in %s\n",
|
||||
time.Now().Format(LogDate),
|
||||
stats.filesMatched.Load(),
|
||||
|
@ -507,7 +509,7 @@ func fileList(paths []string, filters *Filters, sort string, index *Index, types
|
|||
)
|
||||
}
|
||||
|
||||
if cache && filters.IsEmpty() {
|
||||
if Cache && filters.IsEmpty() {
|
||||
index.setIndex(fileList)
|
||||
}
|
||||
|
||||
|
@ -556,7 +558,7 @@ func prepareDirectories(files *Files, sort string) []string {
|
|||
return directories
|
||||
}
|
||||
|
||||
func pickFile(args []string, filters *Filters, sort string, index *Index, registeredFormats *formats.SupportedFormats) (string, error) {
|
||||
func pickFile(args []string, filters *Filters, sort string, index *FileIndex, registeredFormats *formats.SupportedFormats) (string, error) {
|
||||
fileList, fromCache := fileList(args, filters, sort, index, registeredFormats)
|
||||
|
||||
fileCount := len(fileList)
|
||||
|
|
199
cmd/index.go
199
cmd/index.go
|
@ -6,21 +6,29 @@ package cmd
|
|||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/yosssi/gohtml"
|
||||
"seedno.de/seednode/roulette/formats"
|
||||
)
|
||||
|
||||
type Index struct {
|
||||
type FileIndex struct {
|
||||
mutex sync.RWMutex
|
||||
list []string
|
||||
}
|
||||
|
||||
func (i *Index) Index() []string {
|
||||
func (i *FileIndex) Index() []string {
|
||||
i.mutex.RLock()
|
||||
val := i.list
|
||||
i.mutex.RUnlock()
|
||||
|
@ -28,7 +36,7 @@ func (i *Index) Index() []string {
|
|||
return val
|
||||
}
|
||||
|
||||
func (i *Index) Remove(path string) {
|
||||
func (i *FileIndex) Remove(path string) {
|
||||
i.mutex.RLock()
|
||||
tempIndex := make([]string, len(i.list))
|
||||
copy(tempIndex, i.list)
|
||||
|
@ -52,25 +60,25 @@ func (i *Index) Remove(path string) {
|
|||
i.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (i *Index) setIndex(val []string) {
|
||||
func (i *FileIndex) setIndex(val []string) {
|
||||
i.mutex.Lock()
|
||||
i.list = val
|
||||
i.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (i *Index) generateCache(args []string, registeredFormats *formats.SupportedFormats) {
|
||||
func (i *FileIndex) generateCache(args []string, registeredFormats *formats.SupportedFormats) {
|
||||
i.mutex.Lock()
|
||||
i.list = []string{}
|
||||
i.mutex.Unlock()
|
||||
|
||||
fileList(args, &Filters{}, "", i, registeredFormats)
|
||||
|
||||
if cache && cacheFile != "" {
|
||||
i.Export(cacheFile)
|
||||
if Cache && CacheFile != "" {
|
||||
i.Export(CacheFile)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) IsEmpty() bool {
|
||||
func (i *FileIndex) IsEmpty() bool {
|
||||
i.mutex.RLock()
|
||||
length := len(i.list)
|
||||
i.mutex.RUnlock()
|
||||
|
@ -78,7 +86,7 @@ func (i *Index) IsEmpty() bool {
|
|||
return length == 0
|
||||
}
|
||||
|
||||
func (i *Index) Export(path string) error {
|
||||
func (i *FileIndex) Export(path string) error {
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -102,7 +110,7 @@ func (i *Index) Export(path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) Import(path string) error {
|
||||
func (i *FileIndex) Import(path string) error {
|
||||
file, err := os.OpenFile(path, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -130,7 +138,7 @@ func (i *Index) Import(path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func serveCacheClear(args []string, index *Index, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||
func serveCacheClear(args []string, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
index.generateCache(args, registeredFormats)
|
||||
|
||||
|
@ -139,3 +147,172 @@ func serveCacheClear(args []string, index *Index, registeredFormats *formats.Sup
|
|||
w.Write([]byte("Ok"))
|
||||
}
|
||||
}
|
||||
|
||||
func serveIndexHtml(args []string, index *FileIndex, paginate bool) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
indexDump := index.Index()
|
||||
|
||||
fileCount := len(indexDump)
|
||||
|
||||
var startIndex, stopIndex int
|
||||
|
||||
page, err := strconv.Atoi(p.ByName("page"))
|
||||
if err != nil || page <= 0 {
|
||||
startIndex = 0
|
||||
stopIndex = fileCount
|
||||
} else {
|
||||
startIndex = ((page - 1) * int(PageLength))
|
||||
stopIndex = (startIndex + int(PageLength))
|
||||
}
|
||||
|
||||
if startIndex > (fileCount - 1) {
|
||||
indexDump = []string{}
|
||||
}
|
||||
|
||||
if stopIndex > fileCount {
|
||||
stopIndex = fileCount
|
||||
}
|
||||
|
||||
sort.SliceStable(indexDump, func(p, q int) bool {
|
||||
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
|
||||
})
|
||||
|
||||
var htmlBody strings.Builder
|
||||
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
|
||||
htmlBody.WriteString(FaviconHtml)
|
||||
htmlBody.WriteString(`<style>a{text-decoration:none;height:100%;width:100%;color:inherit;cursor:pointer}`)
|
||||
htmlBody.WriteString(`table,td,tr{border:1px solid black;border-collapse:collapse}td{white-space:nowrap;padding:.5em}</style>`)
|
||||
htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
|
||||
if len(indexDump) > 0 {
|
||||
for _, v := range indexDump[startIndex:stopIndex] {
|
||||
var shouldSort = ""
|
||||
|
||||
if Sorting {
|
||||
shouldSort = "?sort=asc"
|
||||
}
|
||||
htmlBody.WriteString(fmt.Sprintf("<tr><td><a href=\"%s%s%s\">%s</a></td></tr>\n", MediaPrefix, v, shouldSort, v))
|
||||
}
|
||||
}
|
||||
if PageLength != 0 {
|
||||
var firstPage int = 1
|
||||
var lastPage int
|
||||
|
||||
if fileCount%int(PageLength) == 0 {
|
||||
lastPage = fileCount / int(PageLength)
|
||||
} else {
|
||||
lastPage = (fileCount / int(PageLength)) + 1
|
||||
}
|
||||
|
||||
if paginate {
|
||||
var prevStatus, nextStatus string = "", ""
|
||||
|
||||
if page <= 1 {
|
||||
prevStatus = " disabled"
|
||||
}
|
||||
|
||||
if page >= lastPage {
|
||||
nextStatus = " disabled"
|
||||
}
|
||||
|
||||
prevPage := page - 1
|
||||
if prevPage < 1 {
|
||||
prevPage = 1
|
||||
}
|
||||
|
||||
nextPage := page + 1
|
||||
if nextPage > lastPage {
|
||||
nextPage = fileCount / int(PageLength)
|
||||
}
|
||||
|
||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">First</button>",
|
||||
firstPage))
|
||||
|
||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\"%s>Prev</button>",
|
||||
prevPage,
|
||||
prevStatus))
|
||||
|
||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\"%s>Next</button>",
|
||||
nextPage,
|
||||
nextStatus))
|
||||
|
||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">Last</button>",
|
||||
lastPage))
|
||||
}
|
||||
}
|
||||
|
||||
htmlBody.WriteString(`</table></body></html>`)
|
||||
|
||||
b, err := io.WriteString(w, gohtml.Format(htmlBody.String()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if Verbose {
|
||||
fmt.Printf("%s | Served HTML index page (%s) to %s in %s\n",
|
||||
startTime.Format(LogDate),
|
||||
humanReadableSize(b),
|
||||
realIP(r),
|
||||
time.Since(startTime).Round(time.Microsecond),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serveIndexJson(args []string, index *FileIndex) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
indexDump := index.Index()
|
||||
|
||||
fileCount := len(indexDump)
|
||||
|
||||
sort.SliceStable(indexDump, func(p, q int) bool {
|
||||
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
|
||||
})
|
||||
|
||||
var startIndex, stopIndex int
|
||||
|
||||
page, err := strconv.Atoi(p.ByName("page"))
|
||||
if err != nil || page <= 0 {
|
||||
startIndex = 0
|
||||
stopIndex = fileCount
|
||||
} else {
|
||||
startIndex = ((page - 1) * int(PageLength))
|
||||
stopIndex = (startIndex + int(PageLength))
|
||||
}
|
||||
|
||||
if startIndex > (fileCount - 1) {
|
||||
indexDump = []string{}
|
||||
}
|
||||
|
||||
if stopIndex > fileCount {
|
||||
stopIndex = fileCount
|
||||
}
|
||||
|
||||
response, err := json.MarshalIndent(indexDump[startIndex:stopIndex], "", " ")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
||||
serverError(w, r, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(response)
|
||||
|
||||
if Verbose {
|
||||
fmt.Printf("%s | Served JSON index page (%s) to %s in %s\n",
|
||||
startTime.Format(LogDate),
|
||||
humanReadableSize(len(response)),
|
||||
realIP(r),
|
||||
time.Since(startTime).Round(time.Microsecond),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
110
cmd/root.go
110
cmd/root.go
|
@ -12,45 +12,52 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
Version string = "0.69.4"
|
||||
ReleaseVersion string = "0.70.3"
|
||||
)
|
||||
|
||||
var (
|
||||
all bool
|
||||
audio bool
|
||||
bind string
|
||||
cache bool
|
||||
cacheFile string
|
||||
debug bool
|
||||
filtering bool
|
||||
images bool
|
||||
maximumFileCount uint64
|
||||
minimumFileCount uint64
|
||||
pageLength uint64
|
||||
port uint16
|
||||
profile bool
|
||||
recursive bool
|
||||
refreshInterval string
|
||||
russian bool
|
||||
sorting bool
|
||||
statistics bool
|
||||
statisticsFile string
|
||||
text bool
|
||||
verbose bool
|
||||
version bool
|
||||
videos bool
|
||||
All bool
|
||||
Audio bool
|
||||
Bind string
|
||||
Cache bool
|
||||
CacheFile string
|
||||
Filtering bool
|
||||
Flash bool
|
||||
Images bool
|
||||
Index bool
|
||||
MaximumFileCount uint64
|
||||
MinimumFileCount uint64
|
||||
PageLength uint64
|
||||
Port uint16
|
||||
Profile bool
|
||||
Recursive bool
|
||||
RefreshInterval string
|
||||
Russian bool
|
||||
Sorting bool
|
||||
Statistics bool
|
||||
StatisticsFile string
|
||||
Text bool
|
||||
Verbose bool
|
||||
Version bool
|
||||
Videos bool
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "roulette <path> [path]...",
|
||||
Short: "Serves random media from the specified directories.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
if debug {
|
||||
// enable image support if no other flags are passed, to retain backwards compatibility
|
||||
// to be replaced with MarkFlagsOneRequired on next spf13/cobra update
|
||||
if !(All || Audio || Flash || Images || Text || Videos) {
|
||||
Images = true
|
||||
}
|
||||
|
||||
if Index {
|
||||
cmd.MarkFlagRequired("cache")
|
||||
}
|
||||
|
||||
if refreshInterval != "" {
|
||||
interval, err := time.ParseDuration(refreshInterval)
|
||||
if RefreshInterval != "" {
|
||||
interval, err := time.ParseDuration(RefreshInterval)
|
||||
if err != nil || interval < 500*time.Millisecond {
|
||||
log.Fatal(ErrIncorrectRefreshInterval)
|
||||
}
|
||||
|
@ -75,29 +82,30 @@ func Execute() {
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.Flags().BoolVar(&all, "all", false, "enable all supported file types")
|
||||
rootCmd.Flags().BoolVar(&audio, "audio", false, "enable support for audio files")
|
||||
rootCmd.Flags().StringVarP(&bind, "bind", "b", "0.0.0.0", "address to bind to")
|
||||
rootCmd.Flags().BoolVarP(&cache, "cache", "c", false, "generate directory cache at startup")
|
||||
rootCmd.Flags().StringVar(&cacheFile, "cache-file", "", "path to optional persistent cache file")
|
||||
rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "expose debug endpoint")
|
||||
rootCmd.Flags().BoolVarP(&filtering, "filter", "f", false, "enable filtering")
|
||||
rootCmd.Flags().BoolVar(&images, "images", true, "enable support for image files")
|
||||
rootCmd.Flags().Uint64Var(&maximumFileCount, "maximum-files", 1<<64-1, "skip directories with file counts above this value")
|
||||
rootCmd.Flags().Uint64Var(&minimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
|
||||
rootCmd.Flags().Uint64Var(&pageLength, "page-length", 0, "pagination length for statistics and debug pages")
|
||||
rootCmd.Flags().Uint16VarP(&port, "port", "p", 8080, "port to listen on")
|
||||
rootCmd.Flags().BoolVar(&profile, "profile", false, "register net/http/pprof handlers")
|
||||
rootCmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "recurse into subdirectories")
|
||||
rootCmd.Flags().StringVar(&refreshInterval, "refresh-interval", "", "force refresh interval equal to this duration (minimum 500ms)")
|
||||
rootCmd.Flags().BoolVar(&russian, "russian", false, "remove selected images after serving")
|
||||
rootCmd.Flags().BoolVarP(&sorting, "sort", "s", false, "enable sorting")
|
||||
rootCmd.Flags().BoolVar(&statistics, "stats", false, "expose stats endpoint")
|
||||
rootCmd.Flags().StringVar(&statisticsFile, "stats-file", "", "path to optional persistent stats file")
|
||||
rootCmd.Flags().BoolVar(&text, "text", false, "enable support for text files")
|
||||
rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "log accessed files to stdout")
|
||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "display version and exit")
|
||||
rootCmd.Flags().BoolVar(&videos, "video", false, "enable support for video files")
|
||||
rootCmd.Flags().BoolVar(&All, "all", false, "enable all supported file types")
|
||||
rootCmd.Flags().BoolVar(&Audio, "audio", false, "enable support for audio files")
|
||||
rootCmd.Flags().StringVarP(&Bind, "bind", "b", "0.0.0.0", "address to bind to")
|
||||
rootCmd.Flags().BoolVarP(&Cache, "cache", "c", false, "generate directory cache at startup")
|
||||
rootCmd.Flags().StringVar(&CacheFile, "cache-file", "", "path to optional persistent cache file")
|
||||
rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")
|
||||
rootCmd.Flags().BoolVar(&Flash, "flash", false, "enable support for shockwave flash files (via ruffle.rs)")
|
||||
rootCmd.Flags().BoolVar(&Images, "images", false, "enable support for image files")
|
||||
rootCmd.Flags().BoolVarP(&Index, "index", "i", false, "expose index endpoints")
|
||||
rootCmd.Flags().Uint64Var(&MaximumFileCount, "maximum-files", 1<<64-1, "skip directories with file counts above this value")
|
||||
rootCmd.Flags().Uint64Var(&MinimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
|
||||
rootCmd.Flags().Uint64Var(&PageLength, "page-length", 0, "pagination length for statistics and debug pages")
|
||||
rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on")
|
||||
rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers")
|
||||
rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories")
|
||||
rootCmd.Flags().StringVar(&RefreshInterval, "refresh-interval", "", "force refresh interval equal to this duration (minimum 500ms)")
|
||||
rootCmd.Flags().BoolVar(&Russian, "russian", false, "remove selected images after serving")
|
||||
rootCmd.Flags().BoolVarP(&Sorting, "sort", "s", false, "enable sorting")
|
||||
rootCmd.Flags().BoolVar(&Statistics, "stats", false, "expose stats endpoint")
|
||||
rootCmd.Flags().StringVar(&StatisticsFile, "stats-file", "", "path to optional persistent stats file")
|
||||
rootCmd.Flags().BoolVar(&Text, "text", false, "enable support for text files")
|
||||
rootCmd.Flags().BoolVarP(&Verbose, "verbose", "v", false, "log accessed files to stdout")
|
||||
rootCmd.Flags().BoolVarP(&Version, "version", "V", false, "display version and exit")
|
||||
rootCmd.Flags().BoolVar(&Videos, "video", false, "enable support for video files")
|
||||
|
||||
rootCmd.Flags().SetInterspersed(true)
|
||||
|
||||
|
@ -109,5 +117,5 @@ func init() {
|
|||
})
|
||||
|
||||
rootCmd.SetVersionTemplate("roulette v{{.Version}}\n")
|
||||
rootCmd.Version = Version
|
||||
rootCmd.Version = ReleaseVersion
|
||||
}
|
||||
|
|
10
cmd/stats.go
10
cmd/stats.go
|
@ -118,8 +118,8 @@ func (s *ServeStats) ListFiles(page int) ([]byte, error) {
|
|||
startIndex = 0
|
||||
stopIndex = len(stats.List) - 1
|
||||
} else {
|
||||
startIndex = ((page - 1) * int(pageLength))
|
||||
stopIndex = (startIndex + int(pageLength))
|
||||
startIndex = ((page - 1) * int(PageLength))
|
||||
stopIndex = (startIndex + int(PageLength))
|
||||
}
|
||||
|
||||
if startIndex > len(stats.List)-1 {
|
||||
|
@ -231,7 +231,7 @@ func serveStats(args []string, stats *ServeStats) httprouter.Handle {
|
|||
|
||||
w.Write(response)
|
||||
|
||||
if verbose {
|
||||
if Verbose {
|
||||
fmt.Printf("%s | Served statistics page (%s) to %s in %s\n",
|
||||
startTime.Format(LogDate),
|
||||
humanReadableSize(len(response)),
|
||||
|
@ -240,8 +240,8 @@ func serveStats(args []string, stats *ServeStats) httprouter.Handle {
|
|||
)
|
||||
}
|
||||
|
||||
if statisticsFile != "" {
|
||||
stats.Export(statisticsFile)
|
||||
if StatisticsFile != "" {
|
||||
stats.Export(StatisticsFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
cmd/uri.go
10
cmd/uri.go
|
@ -13,13 +13,13 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func RefreshInterval(r *http.Request) (int64, string) {
|
||||
func refreshInterval(r *http.Request) (int64, string) {
|
||||
var interval string
|
||||
|
||||
if refreshInterval == "" {
|
||||
if RefreshInterval == "" {
|
||||
interval = r.URL.Query().Get("refresh")
|
||||
} else {
|
||||
interval = refreshInterval
|
||||
interval = RefreshInterval
|
||||
}
|
||||
|
||||
duration, err := time.ParseDuration(interval)
|
||||
|
@ -68,7 +68,7 @@ func generateQueryParams(filters *Filters, sortOrder, refreshInterval string) st
|
|||
|
||||
queryParams.WriteString("?")
|
||||
|
||||
if filtering {
|
||||
if Filtering {
|
||||
queryParams.WriteString("include=")
|
||||
if filters.HasIncludes() {
|
||||
queryParams.WriteString(filters.Includes())
|
||||
|
@ -82,7 +82,7 @@ func generateQueryParams(filters *Filters, sortOrder, refreshInterval string) st
|
|||
hasParams = true
|
||||
}
|
||||
|
||||
if sorting {
|
||||
if Sorting {
|
||||
if hasParams {
|
||||
queryParams.WriteString("&")
|
||||
}
|
||||
|
|
81
cmd/web.go
81
cmd/web.go
|
@ -37,7 +37,7 @@ const (
|
|||
Timeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter.Handle {
|
||||
func serveStaticFile(paths []string, stats *ServeStats, index *FileIndex) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
path := strings.TrimPrefix(r.URL.Path, SourcePrefix)
|
||||
|
||||
|
@ -95,7 +95,7 @@ func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter
|
|||
|
||||
fileSize := humanReadableSize(len(buf))
|
||||
|
||||
if russian {
|
||||
if Russian {
|
||||
err = os.Remove(filePath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
@ -105,12 +105,12 @@ func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter
|
|||
return
|
||||
}
|
||||
|
||||
if cache {
|
||||
if Cache {
|
||||
index.Remove(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
if Verbose {
|
||||
fmt.Printf("%s | Served %s (%s) to %s in %s\n",
|
||||
startTime.Format(LogDate),
|
||||
filePath,
|
||||
|
@ -120,14 +120,14 @@ func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter
|
|||
)
|
||||
}
|
||||
|
||||
if statistics {
|
||||
if Statistics {
|
||||
stats.incrementCounter(filePath, startTime, fileSize)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func serveRoot(paths []string, Regexes *Regexes, index *Index, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||
func serveRoot(paths []string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
refererUri, err := stripQueryParams(refererToUri(r.Referer()))
|
||||
if err != nil {
|
||||
|
@ -147,7 +147,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *Index, registeredFormats
|
|||
|
||||
sortOrder := SortOrder(r)
|
||||
|
||||
_, refreshInterval := RefreshInterval(r)
|
||||
_, refreshInterval := refreshInterval(r)
|
||||
|
||||
var filePath string
|
||||
|
||||
|
@ -200,7 +200,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *Index, registeredFormats
|
|||
}
|
||||
}
|
||||
|
||||
func serveMedia(paths []string, Regexes *Regexes, index *Index, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||
func serveMedia(paths []string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
filters := &Filters{
|
||||
includes: splitQueryParams(r.URL.Query().Get("include"), Regexes),
|
||||
|
@ -250,7 +250,7 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index, registeredFormat
|
|||
|
||||
w.Header().Add("Content-Type", "text/html")
|
||||
|
||||
refreshTimer, refreshInterval := RefreshInterval(r)
|
||||
refreshTimer, refreshInterval := refreshInterval(r)
|
||||
|
||||
queryParams := generateQueryParams(filters, sortOrder, refreshInterval)
|
||||
|
||||
|
@ -286,7 +286,7 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index, registeredFormat
|
|||
|
||||
func serveVersion() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
data := []byte(fmt.Sprintf("roulette v%s\n", Version))
|
||||
data := []byte(fmt.Sprintf("roulette v%s\n", ReleaseVersion))
|
||||
|
||||
w.Header().Write(bytes.NewBufferString("Content-Length: " + strconv.Itoa(len(data))))
|
||||
|
||||
|
@ -304,7 +304,7 @@ func ServePage(args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
bindHost, err := net.LookupHost(bind)
|
||||
bindHost, err := net.LookupHost(Bind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -314,21 +314,30 @@ func ServePage(args []string) error {
|
|||
return errors.New("invalid bind address provided")
|
||||
}
|
||||
|
||||
registeredFormats := &formats.SupportedFormats{}
|
||||
mux := httprouter.New()
|
||||
|
||||
if audio || all {
|
||||
registeredFormats := &formats.SupportedFormats{
|
||||
Extensions: make(map[string]*formats.SupportedFormat),
|
||||
MimeTypes: make(map[string]*formats.SupportedFormat),
|
||||
}
|
||||
|
||||
if Audio || All {
|
||||
registeredFormats.Add(formats.RegisterAudioFormats())
|
||||
}
|
||||
|
||||
if images || all {
|
||||
if Flash || All {
|
||||
registeredFormats.Add(formats.RegisterFlashFormats())
|
||||
}
|
||||
|
||||
if Images || All {
|
||||
registeredFormats.Add(formats.RegisterImageFormats())
|
||||
}
|
||||
|
||||
if text || all {
|
||||
if Text || All {
|
||||
registeredFormats.Add(formats.RegisterTextFormats())
|
||||
}
|
||||
|
||||
if videos || all {
|
||||
if Videos || All {
|
||||
registeredFormats.Add(formats.RegisterVideoFormats())
|
||||
}
|
||||
|
||||
|
@ -341,13 +350,11 @@ func ServePage(args []string) error {
|
|||
return ErrNoMediaFound
|
||||
}
|
||||
|
||||
if russian {
|
||||
if Russian {
|
||||
fmt.Printf("WARNING! Files *will* be deleted after serving!\n\n")
|
||||
}
|
||||
|
||||
mux := httprouter.New()
|
||||
|
||||
index := &Index{
|
||||
index := &FileIndex{
|
||||
mutex: sync.RWMutex{},
|
||||
list: []string{},
|
||||
}
|
||||
|
@ -358,7 +365,7 @@ func ServePage(args []string) error {
|
|||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: net.JoinHostPort(bind, strconv.Itoa(int(port))),
|
||||
Addr: net.JoinHostPort(Bind, strconv.Itoa(int(Port))),
|
||||
Handler: mux,
|
||||
IdleTimeout: 10 * time.Minute,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
|
@ -387,11 +394,11 @@ func ServePage(args []string) error {
|
|||
|
||||
mux.GET("/version", serveVersion())
|
||||
|
||||
if cache {
|
||||
if Cache {
|
||||
skipIndex := false
|
||||
|
||||
if cacheFile != "" {
|
||||
err := index.Import(cacheFile)
|
||||
if CacheFile != "" {
|
||||
err := index.Import(CacheFile)
|
||||
if err == nil {
|
||||
skipIndex = true
|
||||
}
|
||||
|
@ -404,19 +411,19 @@ func ServePage(args []string) error {
|
|||
mux.GET("/clear_cache", serveCacheClear(args, index, registeredFormats))
|
||||
}
|
||||
|
||||
if debug {
|
||||
mux.GET("/html/", serveDebugHtml(args, index, false))
|
||||
if pageLength != 0 {
|
||||
mux.GET("/html/:page", serveDebugHtml(args, index, true))
|
||||
if Index {
|
||||
mux.GET("/html/", serveIndexHtml(args, index, false))
|
||||
if PageLength != 0 {
|
||||
mux.GET("/html/:page", serveIndexHtml(args, index, true))
|
||||
}
|
||||
|
||||
mux.GET("/json", serveDebugJson(args, index))
|
||||
if pageLength != 0 {
|
||||
mux.GET("/json/:page", serveDebugJson(args, index))
|
||||
mux.GET("/json", serveIndexJson(args, index))
|
||||
if PageLength != 0 {
|
||||
mux.GET("/json/:page", serveIndexJson(args, index))
|
||||
}
|
||||
}
|
||||
|
||||
if profile {
|
||||
if Profile {
|
||||
mux.HandlerFunc("GET", "/debug/pprof/", pprof.Index)
|
||||
mux.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline)
|
||||
mux.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile)
|
||||
|
@ -424,9 +431,9 @@ func ServePage(args []string) error {
|
|||
mux.HandlerFunc("GET", "/debug/pprof/trace", pprof.Trace)
|
||||
}
|
||||
|
||||
if statistics {
|
||||
if statisticsFile != "" {
|
||||
stats.Import(statisticsFile)
|
||||
if Statistics {
|
||||
if StatisticsFile != "" {
|
||||
stats.Import(StatisticsFile)
|
||||
|
||||
gracefulShutdown := make(chan os.Signal, 1)
|
||||
signal.Notify(gracefulShutdown, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
@ -434,14 +441,14 @@ func ServePage(args []string) error {
|
|||
go func() {
|
||||
<-gracefulShutdown
|
||||
|
||||
stats.Export(statisticsFile)
|
||||
stats.Export(StatisticsFile)
|
||||
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
mux.GET("/stats", serveStats(args, stats))
|
||||
if pageLength != 0 {
|
||||
if PageLength != 0 {
|
||||
mux.GET("/stats/:page", serveStats(args, stats))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||
*/
|
||||
|
||||
package formats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RegisterFlashFormats() *SupportedFormat {
|
||||
return &SupportedFormat{
|
||||
Css: ``,
|
||||
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||
},
|
||||
Body: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||
var html strings.Builder
|
||||
|
||||
html.WriteString(fmt.Sprintf(`<script src="https://unpkg.com/@ruffle-rs/ruffle"></script><script>window.RufflePlayer.config = {autoplay:"on"};</script><embed src="%s"></embed>`, fileUri))
|
||||
html.WriteString(fmt.Sprintf(`<br /><button onclick=\"window.location.href = '/%s';\">Next</button>`, queryParams))
|
||||
|
||||
return html.String()
|
||||
},
|
||||
Extensions: []string{
|
||||
`.swf`,
|
||||
},
|
||||
MimeTypes: []string{
|
||||
`application/x-shockwave-flash`,
|
||||
},
|
||||
Validate: func(filePath string) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
}
|
|
@ -39,7 +39,6 @@ func RegisterTextFormats() *SupportedFormat {
|
|||
},
|
||||
MimeTypes: []string{
|
||||
`application/json`,
|
||||
`application/octet-stream`,
|
||||
`application/xml`,
|
||||
`text/css`,
|
||||
`text/csv`,
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type SupportedFormat struct {
|
||||
|
@ -20,46 +21,27 @@ type SupportedFormat struct {
|
|||
}
|
||||
|
||||
type SupportedFormats struct {
|
||||
types []*SupportedFormat
|
||||
Extensions map[string]*SupportedFormat
|
||||
MimeTypes map[string]*SupportedFormat
|
||||
}
|
||||
|
||||
func (s *SupportedFormats) Add(t *SupportedFormat) {
|
||||
s.types = append(s.types, t)
|
||||
for _, v := range t.Extensions {
|
||||
_, exists := s.Extensions[v]
|
||||
if !exists {
|
||||
s.Extensions[v] = t
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range t.MimeTypes {
|
||||
_, exists := s.Extensions[v]
|
||||
if !exists {
|
||||
s.MimeTypes[v] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SupportedFormats) Extensions() []string {
|
||||
var extensions []string
|
||||
|
||||
for _, t := range s.types {
|
||||
extensions = append(extensions, t.Extensions...)
|
||||
}
|
||||
|
||||
return extensions
|
||||
}
|
||||
|
||||
func (s *SupportedFormats) MimeTypes() []string {
|
||||
var mimeTypes []string
|
||||
|
||||
for _, t := range s.types {
|
||||
mimeTypes = append(mimeTypes, t.MimeTypes...)
|
||||
}
|
||||
|
||||
return mimeTypes
|
||||
}
|
||||
|
||||
func (s *SupportedFormats) Type(mimeType string) *SupportedFormat {
|
||||
for i := range s.types {
|
||||
for _, m := range s.types[i].MimeTypes {
|
||||
if mimeType == m {
|
||||
return s.types[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func FileType(path string, types *SupportedFormats) (bool, *SupportedFormat, string, error) {
|
||||
func FileType(path string, registeredFormats *SupportedFormats) (bool, *SupportedFormat, string, error) {
|
||||
file, err := os.Open(path)
|
||||
switch {
|
||||
case errors.Is(err, os.ErrNotExist):
|
||||
|
@ -74,12 +56,16 @@ func FileType(path string, types *SupportedFormats) (bool, *SupportedFormat, str
|
|||
|
||||
mimeType := http.DetectContentType(head)
|
||||
|
||||
for _, v := range types.MimeTypes() {
|
||||
if mimeType == v {
|
||||
fileType := types.Type(mimeType)
|
||||
|
||||
// try identifying files by mime types first
|
||||
fileType, exists := registeredFormats.MimeTypes[mimeType]
|
||||
if exists {
|
||||
return fileType.Validate(path), fileType, mimeType, nil
|
||||
}
|
||||
|
||||
// if mime type detection fails, use the file extension
|
||||
fileType, exists = registeredFormats.Extensions[filepath.Ext(path)]
|
||||
if exists {
|
||||
return fileType.Validate(path), fileType, mimeType, nil
|
||||
}
|
||||
|
||||
return false, nil, "", nil
|
||||
|
|
Loading…
Reference in New Issue