Compare commits
9 Commits
6fea978459
...
cbf7218453
Author | SHA1 | Date |
---|---|---|
Seednode | cbf7218453 | |
Seednode | c77a151a24 | |
Seednode | 62a8bde8ea | |
Seednode | a7daf81754 | |
Seednode | 57bcd40c29 | |
Seednode | cc00a1b63b | |
Seednode | 3b4f8f7499 | |
Seednode | e0d4b3882b | |
Seednode | 732d944036 |
18
README.md
18
README.md
|
@ -78,9 +78,13 @@ 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 `--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.
|
If the `-i|--index` flag is passed, four additional endpoints 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.
|
The first of these—`/html` and `/json`—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.
|
||||||
|
|
||||||
|
The other two endpoints—`/extensions` and `/mime_types`—return the registered file types.
|
||||||
|
|
||||||
## Statistics
|
## Statistics
|
||||||
|
|
||||||
|
@ -113,13 +117,13 @@ Flags:
|
||||||
-c, --cache generate directory cache at startup
|
-c, --cache generate directory cache at startup
|
||||||
--cache-file string path to optional persistent cache file
|
--cache-file string path to optional persistent cache file
|
||||||
-f, --filter enable filtering
|
-f, --filter enable filtering
|
||||||
--flash enable support for shockwave flash files (via ruffle.rs) (default true)
|
--flash enable support for shockwave flash files (via ruffle.rs)
|
||||||
-h, --help help for roulette
|
-h, --help help for roulette
|
||||||
--images enable support for image files (default true)
|
--images enable support for image files
|
||||||
-i, --index expose index endpoints
|
-i, --index expose index endpoints
|
||||||
--maximum-files uint skip directories with file counts above this value (default 18446744073709551615)
|
--maximum-files uint32 skip directories with file counts above this value (default 4294967295)
|
||||||
--minimum-files uint skip directories with file counts below this value (default 1)
|
--minimum-files uint32 skip directories with file counts below this value (default 1)
|
||||||
--page-length uint pagination length for statistics and debug pages
|
--page-length uint32 pagination length for statistics and debug pages
|
||||||
-p, --port uint16 port to listen on (default 8080)
|
-p, --port uint16 port to listen on (default 8080)
|
||||||
--profile register net/http/pprof handlers
|
--profile register net/http/pprof handlers
|
||||||
-r, --recursive recurse into subdirectories
|
-r, --recursive recurse into subdirectories
|
||||||
|
|
76
cmd/files.go
76
cmd/files.go
|
@ -20,7 +20,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"seedno.de/seednode/roulette/formats"
|
"seedno.de/seednode/roulette/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type maxConcurrency int
|
type maxConcurrency int
|
||||||
|
@ -53,10 +53,10 @@ func (f *Files) Append(directory, path string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScanStats struct {
|
type ScanStats struct {
|
||||||
filesMatched atomic.Uint64
|
filesMatched atomic.Uint32
|
||||||
filesSkipped atomic.Uint64
|
filesSkipped atomic.Uint32
|
||||||
directoriesMatched atomic.Uint64
|
directoriesMatched atomic.Uint32
|
||||||
directoriesSkipped atomic.Uint64
|
directoriesSkipped atomic.Uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type Path struct {
|
type Path struct {
|
||||||
|
@ -108,9 +108,9 @@ func preparePath(path string) string {
|
||||||
return MediaPrefix + path
|
return MediaPrefix + path
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendPath(directory, path string, files *Files, stats *ScanStats, registeredFormats *formats.SupportedFormats, shouldCache bool) error {
|
func appendPath(directory, path string, files *Files, stats *ScanStats, formats *types.Types, shouldCache bool) error {
|
||||||
if shouldCache {
|
if shouldCache {
|
||||||
registered, _, _, err := formats.FileType(path, registeredFormats)
|
registered, _, _, err := types.FileType(path, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func appendPath(directory, path string, files *Files, stats *ScanStats, register
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, types *formats.SupportedFormats) error {
|
func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, formats *types.Types) error {
|
||||||
shouldCache := Cache && filters.IsEmpty()
|
shouldCache := Cache && filters.IsEmpty()
|
||||||
|
|
||||||
absolutePath, err := filepath.Abs(path)
|
absolutePath, err := filepath.Abs(path)
|
||||||
|
@ -158,7 +158,7 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats,
|
||||||
filename,
|
filename,
|
||||||
filters.includes[i],
|
filters.includes[i],
|
||||||
) {
|
) {
|
||||||
err := appendPath(directory, path, files, stats, types, shouldCache)
|
err := appendPath(directory, path, files, stats, formats, shouldCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = appendPath(directory, path, files, stats, types, shouldCache)
|
err = appendPath(directory, path, files, stats, formats, shouldCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -180,8 +180,8 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) (string, error) {
|
func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *FileIndex, formats *types.Types) (string, error) {
|
||||||
filePath, err := pickFile(paths, filters, sortOrder, index, registeredFormats)
|
filePath, err := pickFile(paths, filters, sortOrder, index, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexe
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case sortOrder == "asc":
|
case sortOrder == "asc":
|
||||||
filePath, err = tryExtensions(path, registeredFormats)
|
filePath, err = tryExtensions(path, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexe
|
||||||
for {
|
for {
|
||||||
path.increment()
|
path.increment()
|
||||||
|
|
||||||
filePath, err = tryExtensions(path, registeredFormats)
|
filePath, err = tryExtensions(path, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexe
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
path.decrement()
|
path.decrement()
|
||||||
|
|
||||||
filePath, err = tryExtensions(path, registeredFormats)
|
filePath, err = tryExtensions(path, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,7 @@ func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexe
|
||||||
return filePath, nil
|
return filePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nextFile(filePath, sortOrder string, Regexes *Regexes, registeredFormats *formats.SupportedFormats) (string, error) {
|
func nextFile(filePath, sortOrder string, Regexes *Regexes, formats *types.Types) (string, error) {
|
||||||
path, err := splitPath(filePath, Regexes)
|
path, err := splitPath(filePath, Regexes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -239,7 +239,7 @@ func nextFile(filePath, sortOrder string, Regexes *Regexes, registeredFormats *f
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName, err := tryExtensions(path, registeredFormats)
|
fileName, err := tryExtensions(path, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -269,13 +269,11 @@ func splitPath(path string, Regexes *Regexes) (*Path, error) {
|
||||||
return &p, nil
|
return &p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryExtensions(p *Path, registeredFormats *formats.SupportedFormats) (string, error) {
|
func tryExtensions(p *Path, formats *types.Types) (string, error) {
|
||||||
var fileName string
|
var fileName string
|
||||||
|
|
||||||
for _, format := range registeredFormats.Extensions {
|
for extension := range formats.Extensions {
|
||||||
for _, extension := range format.Extensions {
|
|
||||||
fileName = fmt.Sprintf("%s%.3d%s", p.base, p.number, extension)
|
fileName = fmt.Sprintf("%s%.3d%s", p.base, p.number, extension)
|
||||||
}
|
|
||||||
|
|
||||||
exists, err := fileExists(fileName)
|
exists, err := fileExists(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -326,7 +324,7 @@ func pathIsValid(filePath string, paths []string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathHasSupportedFiles(path string, registeredFormats *formats.SupportedFormats) (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 {
|
||||||
|
@ -338,7 +336,7 @@ func pathHasSupportedFiles(path string, registeredFormats *formats.SupportedForm
|
||||||
case !Recursive && info.IsDir() && p != path:
|
case !Recursive && info.IsDir() && p != path:
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
case !info.IsDir():
|
case !info.IsDir():
|
||||||
registered, _, _, err := formats.FileType(p, registeredFormats)
|
registered, _, _, err := types.FileType(p, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -363,9 +361,9 @@ func pathHasSupportedFiles(path string, registeredFormats *formats.SupportedForm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathCount(path string) (uint64, uint64, error) {
|
func pathCount(path string) (uint32, uint32, error) {
|
||||||
var directories uint64 = 0
|
var directories uint32 = 0
|
||||||
var files uint64 = 0
|
var files uint32 = 0
|
||||||
|
|
||||||
nodes, err := os.ReadDir(path)
|
nodes, err := os.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -383,7 +381,7 @@ func pathCount(path string) (uint64, uint64, error) {
|
||||||
return files, directories, nil
|
return files, directories, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, concurrency *Concurrency, types *formats.SupportedFormats) error {
|
func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, concurrency *Concurrency, formats *types.Types) error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -410,7 +408,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = appendPaths(path, files, filters, stats, types)
|
err = appendPaths(path, files, filters, stats, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -444,7 +442,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileList(paths []string, filters *Filters, sort string, index *FileIndex, types *formats.SupportedFormats) ([]string, bool) {
|
func fileList(paths []string, filters *Filters, sort string, index *FileIndex, formats *types.Types) ([]string, bool) {
|
||||||
if Cache && filters.IsEmpty() && !index.IsEmpty() {
|
if Cache && filters.IsEmpty() && !index.IsEmpty() {
|
||||||
return index.Index(), true
|
return index.Index(), true
|
||||||
}
|
}
|
||||||
|
@ -457,10 +455,10 @@ func fileList(paths []string, filters *Filters, sort string, index *FileIndex, t
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := &ScanStats{
|
stats := &ScanStats{
|
||||||
filesMatched: atomic.Uint64{},
|
filesMatched: atomic.Uint32{},
|
||||||
filesSkipped: atomic.Uint64{},
|
filesSkipped: atomic.Uint32{},
|
||||||
directoriesMatched: atomic.Uint64{},
|
directoriesMatched: atomic.Uint32{},
|
||||||
directoriesSkipped: atomic.Uint64{},
|
directoriesSkipped: atomic.Uint32{},
|
||||||
}
|
}
|
||||||
|
|
||||||
concurrency := &Concurrency{
|
concurrency := &Concurrency{
|
||||||
|
@ -483,7 +481,7 @@ func fileList(paths []string, filters *Filters, sort string, index *FileIndex, t
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := scanPath(paths[i], files, filters, stats, concurrency, types)
|
err := scanPath(paths[i], files, filters, stats, concurrency, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -558,8 +556,8 @@ func prepareDirectories(files *Files, sort string) []string {
|
||||||
return directories
|
return directories
|
||||||
}
|
}
|
||||||
|
|
||||||
func pickFile(args []string, filters *Filters, sort string, index *FileIndex, registeredFormats *formats.SupportedFormats) (string, error) {
|
func pickFile(args []string, filters *Filters, sort string, index *FileIndex, formats *types.Types) (string, error) {
|
||||||
fileList, fromCache := fileList(args, filters, sort, index, registeredFormats)
|
fileList, fromCache := fileList(args, filters, sort, index, formats)
|
||||||
|
|
||||||
fileCount := len(fileList)
|
fileCount := len(fileList)
|
||||||
if fileCount < 1 {
|
if fileCount < 1 {
|
||||||
|
@ -586,7 +584,7 @@ func pickFile(args []string, filters *Filters, sort string, index *FileIndex, re
|
||||||
filePath := fileList[val]
|
filePath := fileList[val]
|
||||||
|
|
||||||
if !fromCache {
|
if !fromCache {
|
||||||
registered, _, _, err := formats.FileType(filePath, registeredFormats)
|
registered, _, _, err := types.FileType(filePath, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -618,7 +616,7 @@ func normalizePath(path string) (string, error) {
|
||||||
return absolutePath, nil
|
return absolutePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizePaths(args []string, types *formats.SupportedFormats) ([]string, error) {
|
func normalizePaths(args []string, formats *types.Types) ([]string, error) {
|
||||||
var paths []string
|
var paths []string
|
||||||
|
|
||||||
var pathList strings.Builder
|
var pathList strings.Builder
|
||||||
|
@ -632,7 +630,7 @@ func normalizePaths(args []string, types *formats.SupportedFormats) ([]string, e
|
||||||
|
|
||||||
pathMatches := (args[i] == path)
|
pathMatches := (args[i] == path)
|
||||||
|
|
||||||
hasSupportedFiles, err := pathHasSupportedFiles(path, types)
|
hasSupportedFiles, err := pathHasSupportedFiles(path, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
87
cmd/index.go
87
cmd/index.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,7 +21,7 @@ import (
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
"github.com/yosssi/gohtml"
|
"github.com/yosssi/gohtml"
|
||||||
"seedno.de/seednode/roulette/formats"
|
"seedno.de/seednode/roulette/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileIndex struct {
|
type FileIndex struct {
|
||||||
|
@ -66,12 +67,12 @@ func (i *FileIndex) setIndex(val []string) {
|
||||||
i.mutex.Unlock()
|
i.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *FileIndex) generateCache(args []string, registeredFormats *formats.SupportedFormats) {
|
func (i *FileIndex) generateCache(args []string, formats *types.Types) {
|
||||||
i.mutex.Lock()
|
i.mutex.Lock()
|
||||||
i.list = []string{}
|
i.list = []string{}
|
||||||
i.mutex.Unlock()
|
i.mutex.Unlock()
|
||||||
|
|
||||||
fileList(args, &Filters{}, "", i, registeredFormats)
|
fileList(args, &Filters{}, "", i, formats)
|
||||||
|
|
||||||
if Cache && CacheFile != "" {
|
if Cache && CacheFile != "" {
|
||||||
i.Export(CacheFile)
|
i.Export(CacheFile)
|
||||||
|
@ -138,9 +139,9 @@ func (i *FileIndex) Import(path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveCacheClear(args []string, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
func serveCacheClear(args []string, index *FileIndex, formats *types.Types) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
index.generateCache(args, registeredFormats)
|
index.generateCache(args, formats)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
|
||||||
|
@ -316,3 +317,79 @@ func serveIndexJson(args []string, index *FileIndex) httprouter.Handle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveExtensions(formats *types.Types) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
var output strings.Builder
|
||||||
|
|
||||||
|
extensions := make([]string, len(formats.Extensions))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range formats.Extensions {
|
||||||
|
extensions[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(extensions)
|
||||||
|
|
||||||
|
for _, v := range extensions {
|
||||||
|
output.WriteString(v + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
response := []byte(output.String())
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
|
|
||||||
|
if Verbose {
|
||||||
|
fmt.Printf("%s | Served registered extensions list (%s) to %s in %s\n",
|
||||||
|
startTime.Format(LogDate),
|
||||||
|
humanReadableSize(len(response)),
|
||||||
|
realIP(r),
|
||||||
|
time.Since(startTime).Round(time.Microsecond),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveMimeTypes(formats *types.Types) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
var output strings.Builder
|
||||||
|
|
||||||
|
mimeTypes := make([]string, len(formats.MimeTypes))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range formats.MimeTypes {
|
||||||
|
mimeTypes[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(mimeTypes)
|
||||||
|
|
||||||
|
for _, v := range mimeTypes {
|
||||||
|
output.WriteString(v + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
response := []byte(output.String())
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
|
|
||||||
|
if Verbose {
|
||||||
|
fmt.Printf("%s | Served registered MIME types list (%s) to %s in %s\n",
|
||||||
|
startTime.Format(LogDate),
|
||||||
|
humanReadableSize(len(response)),
|
||||||
|
realIP(r),
|
||||||
|
time.Since(startTime).Round(time.Microsecond),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
26
cmd/root.go
26
cmd/root.go
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ReleaseVersion string = "0.70.3"
|
ReleaseVersion string = "0.75.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -25,9 +25,9 @@ var (
|
||||||
Flash bool
|
Flash bool
|
||||||
Images bool
|
Images bool
|
||||||
Index bool
|
Index bool
|
||||||
MaximumFileCount uint64
|
MaximumFileCount uint32
|
||||||
MinimumFileCount uint64
|
MinimumFileCount uint32
|
||||||
PageLength uint64
|
PageLength uint32
|
||||||
Port uint16
|
Port uint16
|
||||||
Profile bool
|
Profile bool
|
||||||
Recursive bool
|
Recursive bool
|
||||||
|
@ -45,13 +45,7 @@ var (
|
||||||
Use: "roulette <path> [path]...",
|
Use: "roulette <path> [path]...",
|
||||||
Short: "Serves random media from the specified directories.",
|
Short: "Serves random media from the specified directories.",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// 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 {
|
if Index {
|
||||||
cmd.MarkFlagRequired("cache")
|
cmd.MarkFlagRequired("cache")
|
||||||
}
|
}
|
||||||
|
@ -59,9 +53,11 @@ var (
|
||||||
if RefreshInterval != "" {
|
if RefreshInterval != "" {
|
||||||
interval, err := time.ParseDuration(RefreshInterval)
|
interval, err := time.ParseDuration(RefreshInterval)
|
||||||
if err != nil || interval < 500*time.Millisecond {
|
if err != nil || interval < 500*time.Millisecond {
|
||||||
log.Fatal(ErrIncorrectRefreshInterval)
|
return ErrIncorrectRefreshInterval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := ServePage(args)
|
err := ServePage(args)
|
||||||
|
@ -91,9 +87,9 @@ func init() {
|
||||||
rootCmd.Flags().BoolVar(&Flash, "flash", false, "enable support for shockwave flash files (via ruffle.rs)")
|
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().BoolVar(&Images, "images", false, "enable support for image files")
|
||||||
rootCmd.Flags().BoolVarP(&Index, "index", "i", false, "expose index endpoints")
|
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().Uint32Var(&MaximumFileCount, "maximum-files", 1<<32-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().Uint32Var(&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().Uint32Var(&PageLength, "page-length", 0, "pagination length for statistics and debug pages")
|
||||||
rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on")
|
rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on")
|
||||||
rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers")
|
rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers")
|
||||||
rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories")
|
rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories")
|
||||||
|
|
57
cmd/web.go
57
cmd/web.go
|
@ -26,7 +26,7 @@ import (
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/yosssi/gohtml"
|
"github.com/yosssi/gohtml"
|
||||||
"seedno.de/seednode/roulette/formats"
|
"seedno.de/seednode/roulette/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -127,7 +127,7 @@ func serveStaticFile(paths []string, stats *ServeStats, index *FileIndex) httpro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveRoot(paths []string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
func serveRoot(paths []string, Regexes *Regexes, index *FileIndex, formats *types.Types) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
refererUri, err := stripQueryParams(refererToUri(r.Referer()))
|
refererUri, err := stripQueryParams(refererToUri(r.Referer()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -152,7 +152,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *FileIndex, registeredFor
|
||||||
var filePath string
|
var filePath string
|
||||||
|
|
||||||
if refererUri != "" {
|
if refererUri != "" {
|
||||||
filePath, err = nextFile(strippedRefererUri, sortOrder, Regexes, registeredFormats)
|
filePath, err = nextFile(strippedRefererUri, sortOrder, Regexes, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *FileIndex, registeredFor
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath, err = newFile(paths, filters, sortOrder, Regexes, index, registeredFormats)
|
filePath, err = newFile(paths, filters, sortOrder, Regexes, index, formats)
|
||||||
switch {
|
switch {
|
||||||
case err != nil && err == ErrNoMediaFound:
|
case err != nil && err == ErrNoMediaFound:
|
||||||
notFound(w, r, filePath)
|
notFound(w, r, filePath)
|
||||||
|
@ -200,7 +200,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *FileIndex, registeredFor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveMedia(paths []string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
func serveMedia(paths []string, Regexes *Regexes, index *FileIndex, formats *types.Types) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
filters := &Filters{
|
filters := &Filters{
|
||||||
includes: splitQueryParams(r.URL.Query().Get("include"), Regexes),
|
includes: splitQueryParams(r.URL.Query().Get("include"), Regexes),
|
||||||
|
@ -229,7 +229,7 @@ func serveMedia(paths []string, Regexes *Regexes, index *FileIndex, registeredFo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registered, fileType, mimeType, err := formats.FileType(filePath, registeredFormats)
|
registered, fileType, mimeType, err := types.FileType(filePath, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
||||||
|
@ -257,12 +257,7 @@ func serveMedia(paths []string, Regexes *Regexes, index *FileIndex, registeredFo
|
||||||
var htmlBody strings.Builder
|
var htmlBody strings.Builder
|
||||||
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
|
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
|
||||||
htmlBody.WriteString(FaviconHtml)
|
htmlBody.WriteString(FaviconHtml)
|
||||||
htmlBody.WriteString(`<style>html,body{margin:0;padding:0;height:100%;}`)
|
htmlBody.WriteString(fmt.Sprintf(`<style>%s</style>`, fileType.Css()))
|
||||||
htmlBody.WriteString(`a{color:inherit;display:block;height:100%;width:100%;text-decoration:none;}`)
|
|
||||||
htmlBody.WriteString(`img{margin:auto;display:block;max-width:97%;max-height:97%;object-fit:scale-down;`)
|
|
||||||
htmlBody.WriteString(`position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}`)
|
|
||||||
htmlBody.WriteString(fileType.Css)
|
|
||||||
htmlBody.WriteString(`</style>`)
|
|
||||||
htmlBody.WriteString((fileType.Title(queryParams, fileUri, filePath, fileName, mimeType)))
|
htmlBody.WriteString((fileType.Title(queryParams, fileUri, filePath, fileName, mimeType)))
|
||||||
htmlBody.WriteString(`</head><body>`)
|
htmlBody.WriteString(`</head><body>`)
|
||||||
if refreshInterval != "0ms" {
|
if refreshInterval != "0ms" {
|
||||||
|
@ -316,32 +311,34 @@ func ServePage(args []string) error {
|
||||||
|
|
||||||
mux := httprouter.New()
|
mux := httprouter.New()
|
||||||
|
|
||||||
registeredFormats := &formats.SupportedFormats{
|
formats := &types.Types{
|
||||||
Extensions: make(map[string]*formats.SupportedFormat),
|
Extensions: make(map[string]string),
|
||||||
MimeTypes: make(map[string]*formats.SupportedFormat),
|
MimeTypes: make(map[string]types.Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
if Audio || All {
|
if Audio || All {
|
||||||
registeredFormats.Add(formats.RegisterAudioFormats())
|
formats.Add(types.Audio{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if Flash || All {
|
if Flash || All {
|
||||||
registeredFormats.Add(formats.RegisterFlashFormats())
|
formats.Add(types.Flash{})
|
||||||
}
|
|
||||||
|
|
||||||
if Images || All {
|
|
||||||
registeredFormats.Add(formats.RegisterImageFormats())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if Text || All {
|
if Text || All {
|
||||||
registeredFormats.Add(formats.RegisterTextFormats())
|
formats.Add(types.Text{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if Videos || All {
|
if Videos || All {
|
||||||
registeredFormats.Add(formats.RegisterVideoFormats())
|
formats.Add(types.Video{})
|
||||||
}
|
}
|
||||||
|
|
||||||
paths, err := normalizePaths(args, registeredFormats)
|
// 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.Extensions) == 0 {
|
||||||
|
formats.Add(types.Images{})
|
||||||
|
}
|
||||||
|
|
||||||
|
paths, err := normalizePaths(args, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -382,13 +379,13 @@ func ServePage(args []string) error {
|
||||||
|
|
||||||
mux.PanicHandler = serverErrorHandler()
|
mux.PanicHandler = serverErrorHandler()
|
||||||
|
|
||||||
mux.GET("/", serveRoot(paths, regexes, index, registeredFormats))
|
mux.GET("/", serveRoot(paths, regexes, index, formats))
|
||||||
|
|
||||||
mux.GET("/favicons/*favicon", serveFavicons())
|
mux.GET("/favicons/*favicon", serveFavicons())
|
||||||
|
|
||||||
mux.GET("/favicon.ico", serveFavicons())
|
mux.GET("/favicon.ico", serveFavicons())
|
||||||
|
|
||||||
mux.GET(MediaPrefix+"/*media", serveMedia(paths, regexes, index, registeredFormats))
|
mux.GET(MediaPrefix+"/*media", serveMedia(paths, regexes, index, formats))
|
||||||
|
|
||||||
mux.GET(SourcePrefix+"/*static", serveStaticFile(paths, stats, index))
|
mux.GET(SourcePrefix+"/*static", serveStaticFile(paths, stats, index))
|
||||||
|
|
||||||
|
@ -405,10 +402,10 @@ func ServePage(args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !skipIndex {
|
if !skipIndex {
|
||||||
index.generateCache(args, registeredFormats)
|
index.generateCache(args, formats)
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.GET("/clear_cache", serveCacheClear(args, index, registeredFormats))
|
mux.GET("/clear_cache", serveCacheClear(args, index, formats))
|
||||||
}
|
}
|
||||||
|
|
||||||
if Index {
|
if Index {
|
||||||
|
@ -421,6 +418,10 @@ func ServePage(args []string) error {
|
||||||
if PageLength != 0 {
|
if PageLength != 0 {
|
||||||
mux.GET("/json/:page", serveIndexJson(args, index))
|
mux.GET("/json/:page", serveIndexJson(args, index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mux.GET("/extensions", serveExtensions(formats))
|
||||||
|
|
||||||
|
mux.GET("/mime_types", serveMimeTypes(formats))
|
||||||
}
|
}
|
||||||
|
|
||||||
if Profile {
|
if Profile {
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright © 2023 Seednode <seednode@seedno.de>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package formats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterAudioFormats() *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 {
|
|
||||||
return fmt.Sprintf(`<a href="/%s"><audio controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the audio tag.</audio></a>`,
|
|
||||||
queryParams,
|
|
||||||
fileUri,
|
|
||||||
mime,
|
|
||||||
fileName)
|
|
||||||
},
|
|
||||||
Extensions: []string{
|
|
||||||
`.mp3`,
|
|
||||||
`.ogg`,
|
|
||||||
`.oga`,
|
|
||||||
`.wav`,
|
|
||||||
},
|
|
||||||
MimeTypes: []string{
|
|
||||||
`audio/mpeg`,
|
|
||||||
`audio/ogg`,
|
|
||||||
`audio/wav`,
|
|
||||||
},
|
|
||||||
Validate: func(filePath string) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright © 2023 Seednode <seednode@seedno.de>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package formats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
_ "image/gif"
|
|
||||||
_ "image/jpeg"
|
|
||||||
_ "image/png"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
_ "golang.org/x/image/bmp"
|
|
||||||
_ "golang.org/x/image/webp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Dimensions struct {
|
|
||||||
Width int
|
|
||||||
Height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterImageFormats() *SupportedFormat {
|
|
||||||
return &SupportedFormat{
|
|
||||||
Css: ``,
|
|
||||||
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
|
||||||
dimensions, err := ImageDimensions(filePath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(`<title>%s (%dx%d)</title>`,
|
|
||||||
fileName,
|
|
||||||
dimensions.Width,
|
|
||||||
dimensions.Height)
|
|
||||||
},
|
|
||||||
Body: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
|
||||||
dimensions, err := ImageDimensions(filePath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(`<a href="/%s"><img src="%s" width="%d" height="%d" type="%s" alt="Roulette selected: %s"></a>`,
|
|
||||||
queryParams,
|
|
||||||
fileUri,
|
|
||||||
dimensions.Width,
|
|
||||||
dimensions.Height,
|
|
||||||
mime,
|
|
||||||
fileName)
|
|
||||||
},
|
|
||||||
Extensions: []string{
|
|
||||||
`.apng`,
|
|
||||||
`.avif`,
|
|
||||||
`.bmp`,
|
|
||||||
`.gif`,
|
|
||||||
`.jpg`,
|
|
||||||
`.jpeg`,
|
|
||||||
`.jfif`,
|
|
||||||
`.pjp`,
|
|
||||||
`.pjpeg`,
|
|
||||||
`.png`,
|
|
||||||
`.svg`,
|
|
||||||
`.webp`,
|
|
||||||
},
|
|
||||||
MimeTypes: []string{
|
|
||||||
`image/apng`,
|
|
||||||
`image/avif`,
|
|
||||||
`image/bmp`,
|
|
||||||
`image/gif`,
|
|
||||||
`image/jpeg`,
|
|
||||||
`image/png`,
|
|
||||||
`image/svg+xml`,
|
|
||||||
`image/webp`,
|
|
||||||
},
|
|
||||||
Validate: func(filePath string) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ImageDimensions(path string) (*Dimensions, error) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, os.ErrNotExist):
|
|
||||||
fmt.Printf("File %s does not exist\n", path)
|
|
||||||
return &Dimensions{}, nil
|
|
||||||
case err != nil:
|
|
||||||
fmt.Printf("File %s open returned error: %s\n", path, err)
|
|
||||||
return &Dimensions{}, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
decodedConfig, _, err := image.DecodeConfig(file)
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, image.ErrFormat):
|
|
||||||
fmt.Printf("File %s has invalid image format\n", path)
|
|
||||||
return &Dimensions{Width: 0, Height: 0}, nil
|
|
||||||
case err != nil:
|
|
||||||
fmt.Printf("File %s decode returned error: %s\n", path, err)
|
|
||||||
return &Dimensions{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Dimensions{Width: decodedConfig.Width, Height: decodedConfig.Height}, nil
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright © 2023 Seednode <seednode@seedno.de>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package formats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterTextFormats() *SupportedFormat {
|
|
||||||
return &SupportedFormat{
|
|
||||||
Css: `pre{margin:.5rem;}`,
|
|
||||||
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 {
|
|
||||||
body, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
body = []byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(`<a href="/%s"><pre>%s</pre></a>`,
|
|
||||||
queryParams,
|
|
||||||
body)
|
|
||||||
},
|
|
||||||
Extensions: []string{
|
|
||||||
`.css`,
|
|
||||||
`.csv`,
|
|
||||||
`.html`,
|
|
||||||
`.js`,
|
|
||||||
`.json`,
|
|
||||||
`.md`,
|
|
||||||
`.txt`,
|
|
||||||
`.xml`,
|
|
||||||
},
|
|
||||||
MimeTypes: []string{
|
|
||||||
`application/json`,
|
|
||||||
`application/xml`,
|
|
||||||
`text/css`,
|
|
||||||
`text/csv`,
|
|
||||||
`text/javascript`,
|
|
||||||
`text/plain`,
|
|
||||||
`text/plain; charset=utf-8`,
|
|
||||||
},
|
|
||||||
Validate: func(path string) bool {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, os.ErrNotExist):
|
|
||||||
return false
|
|
||||||
case err != nil:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
head := make([]byte, 512)
|
|
||||||
file.Read(head)
|
|
||||||
|
|
||||||
return utf8.Valid(head)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright © 2023 Seednode <seednode@seedno.de>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package formats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SupportedFormat struct {
|
|
||||||
Css string
|
|
||||||
Title func(queryParams, fileUri, filePath, fileName, mime string) string
|
|
||||||
Body func(queryParams, fileUri, filePath, fileName, mime string) string
|
|
||||||
Extensions []string
|
|
||||||
MimeTypes []string
|
|
||||||
Validate func(filePath string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type SupportedFormats struct {
|
|
||||||
Extensions map[string]*SupportedFormat
|
|
||||||
MimeTypes map[string]*SupportedFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SupportedFormats) Add(t *SupportedFormat) {
|
|
||||||
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 FileType(path string, registeredFormats *SupportedFormats) (bool, *SupportedFormat, string, error) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, os.ErrNotExist):
|
|
||||||
return false, nil, "", nil
|
|
||||||
case err != nil:
|
|
||||||
return false, nil, "", err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
head := make([]byte, 512)
|
|
||||||
file.Read(head)
|
|
||||||
|
|
||||||
mimeType := http.DetectContentType(head)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright © 2023 Seednode <seednode@seedno.de>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package formats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterVideoFormats() *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 {
|
|
||||||
return fmt.Sprintf(`<a href="/%s"><video controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the video tag.</video></a>`,
|
|
||||||
queryParams,
|
|
||||||
fileUri,
|
|
||||||
mime,
|
|
||||||
fileName)
|
|
||||||
},
|
|
||||||
Extensions: []string{
|
|
||||||
`.mp4`,
|
|
||||||
`.ogm`,
|
|
||||||
`.ogv`,
|
|
||||||
`.webm`,
|
|
||||||
},
|
|
||||||
MimeTypes: []string{
|
|
||||||
`video/mp4`,
|
|
||||||
`video/ogg`,
|
|
||||||
`video/webm`,
|
|
||||||
},
|
|
||||||
Validate: func(filePath string) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Audio struct{}
|
||||||
|
|
||||||
|
func (t Audio) 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;}`)
|
||||||
|
|
||||||
|
return css.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Audio) Title(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Audio) Body(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
return fmt.Sprintf(`<a href="/%s"><audio controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the audio tag.</audio></a>`,
|
||||||
|
queryParams,
|
||||||
|
fileUri,
|
||||||
|
mime,
|
||||||
|
fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Audio) Extensions() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
`.mp3`: `audio/mpeg`,
|
||||||
|
`.ogg`: `audio/ogg`,
|
||||||
|
`.oga`: `audio/ogg`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Audio) MimeTypes() []string {
|
||||||
|
return []string{
|
||||||
|
`application/ogg`,
|
||||||
|
`audio/mp3`,
|
||||||
|
`audio/mpeg`,
|
||||||
|
`audio/mpeg3`,
|
||||||
|
`audio/ogg`,
|
||||||
|
`audio/x-mpeg-3`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Audio) Validate(filePath string) bool {
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flash struct{}
|
||||||
|
|
||||||
|
func (t Flash) 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;}`)
|
||||||
|
|
||||||
|
return css.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Flash) Title(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Flash) Body(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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Flash) Extensions() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
`.swf`: `application/x-shockwave-flash`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Flash) MimeTypes() []string {
|
||||||
|
return []string{
|
||||||
|
`application/x-shockwave-flash`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Flash) Validate(filePath string) bool {
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
_ "image/gif"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "golang.org/x/image/bmp"
|
||||||
|
_ "golang.org/x/image/webp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dimensions struct {
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Images struct{}
|
||||||
|
|
||||||
|
func (t Images) 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;}`)
|
||||||
|
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%);}`)
|
||||||
|
|
||||||
|
return css.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Images) Title(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
dimensions, err := ImageDimensions(filePath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<title>%s (%dx%d)</title>`,
|
||||||
|
fileName,
|
||||||
|
dimensions.Width,
|
||||||
|
dimensions.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Images) Body(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
dimensions, err := ImageDimensions(filePath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<a href="/%s"><img src="%s" width="%d" height="%d" type="%s" alt="Roulette selected: %s"></a>`,
|
||||||
|
queryParams,
|
||||||
|
fileUri,
|
||||||
|
dimensions.Width,
|
||||||
|
dimensions.Height,
|
||||||
|
mime,
|
||||||
|
fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Images) Extensions() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
`.apng`: `image/apng`,
|
||||||
|
`.avif`: `image/avif`,
|
||||||
|
`.bmp`: `image/bmp`,
|
||||||
|
`.gif`: `image/gif`,
|
||||||
|
`.jpg`: `image/jpeg`,
|
||||||
|
`.jpeg`: `image/jpeg`,
|
||||||
|
`.jfif`: `image/jpeg`,
|
||||||
|
`.pjp`: `image/jpeg`,
|
||||||
|
`.pjpeg`: `image/jpeg`,
|
||||||
|
`.png`: `image/png`,
|
||||||
|
`.svg`: `image/svg+xml`,
|
||||||
|
`.webp`: `image/webp`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Images) MimeTypes() []string {
|
||||||
|
return []string{
|
||||||
|
`image/apng`,
|
||||||
|
`image/avif`,
|
||||||
|
`image/bmp`,
|
||||||
|
`image/gif`,
|
||||||
|
`image/jpeg`,
|
||||||
|
`image/png`,
|
||||||
|
`image/svg+xml`,
|
||||||
|
`image/webp`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Images) Validate(filePath string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageDimensions(path string) (*Dimensions, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, os.ErrNotExist):
|
||||||
|
fmt.Printf("File %s does not exist\n", path)
|
||||||
|
return &Dimensions{}, nil
|
||||||
|
case err != nil:
|
||||||
|
fmt.Printf("File %s open returned error: %s\n", path, err)
|
||||||
|
return &Dimensions{}, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
decodedConfig, _, err := image.DecodeConfig(file)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, image.ErrFormat):
|
||||||
|
fmt.Printf("File %s has invalid image format\n", path)
|
||||||
|
return &Dimensions{Width: 0, Height: 0}, nil
|
||||||
|
case err != nil:
|
||||||
|
fmt.Printf("File %s decode returned error: %s\n", path, err)
|
||||||
|
return &Dimensions{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Dimensions{Width: decodedConfig.Width, Height: decodedConfig.Height}, nil
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Text struct{}
|
||||||
|
|
||||||
|
func (t Text) 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;overflow:hidden;}`)
|
||||||
|
css.WriteString(`textarea{border:none;caret-color:transparent;outline:none;margin:.5rem;`)
|
||||||
|
css.WriteString(`height:99%;width:99%;white-space:pre;overflow:auto;}`)
|
||||||
|
|
||||||
|
return css.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Text) Title(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Text) Body(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
body, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
body = []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<a href="/%s"><textarea autofocus readonly>%s</textarea></a>`,
|
||||||
|
queryParams,
|
||||||
|
body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Text) Extensions() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
`.css`: `text/css`,
|
||||||
|
`.csv`: `text/csv`,
|
||||||
|
`.htm`: `text/html`,
|
||||||
|
`.html`: `text/html`,
|
||||||
|
`.js`: `text/javascript`,
|
||||||
|
`.json`: `application/json`,
|
||||||
|
`.md`: `text/markdown`,
|
||||||
|
`.txt`: `text/plain`,
|
||||||
|
`.xml`: `application/xml`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Text) MimeTypes() []string {
|
||||||
|
return []string{
|
||||||
|
`application/json`,
|
||||||
|
`application/xml`,
|
||||||
|
`text/css`,
|
||||||
|
`text/csv`,
|
||||||
|
`text/html`,
|
||||||
|
`text/javascript`,
|
||||||
|
`text/plain`,
|
||||||
|
`text/plain; charset=utf-8`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Text) Validate(filePath string) bool {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, os.ErrNotExist):
|
||||||
|
return false
|
||||||
|
case err != nil:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
head := make([]byte, 512)
|
||||||
|
file.Read(head)
|
||||||
|
|
||||||
|
return utf8.Valid(head)
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Type interface {
|
||||||
|
Css() string
|
||||||
|
Title(queryParams, fileUri, filePath, fileName, mime string) string
|
||||||
|
Body(queryParams, fileUri, filePath, fileName, mime string) string
|
||||||
|
Extensions() map[string]string
|
||||||
|
MimeTypes() []string
|
||||||
|
Validate(filePath string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Types struct {
|
||||||
|
Extensions map[string]string
|
||||||
|
MimeTypes map[string]Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Types) Add(t Type) {
|
||||||
|
for k, v := range t.Extensions() {
|
||||||
|
_, exists := s.Extensions[k]
|
||||||
|
if !exists {
|
||||||
|
s.Extensions[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range t.MimeTypes() {
|
||||||
|
_, exists := s.Extensions[v]
|
||||||
|
if !exists {
|
||||||
|
s.MimeTypes[v] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FileType(path string, registeredFormats *Types) (bool, Type, string, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, os.ErrNotExist):
|
||||||
|
return false, nil, "", nil
|
||||||
|
case err != nil:
|
||||||
|
return false, nil, "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
head := make([]byte, 512)
|
||||||
|
file.Read(head)
|
||||||
|
|
||||||
|
mimeType := http.DetectContentType(head)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
mimeType, exists = registeredFormats.Extensions[filepath.Ext(path)]
|
||||||
|
if exists {
|
||||||
|
fileType, exists := registeredFormats.MimeTypes[mimeType]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return fileType.Validate(path), fileType, mimeType, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil, "", nil
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Video struct{}
|
||||||
|
|
||||||
|
func (t Video) 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;}`)
|
||||||
|
css.WriteString(`video{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%);}`)
|
||||||
|
|
||||||
|
return css.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Video) Title(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Video) Body(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
return fmt.Sprintf(`<a href="/%s"><video controls autoplay loop preload="auto"><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the video tag.</video></a>`,
|
||||||
|
queryParams,
|
||||||
|
fileUri,
|
||||||
|
mime,
|
||||||
|
fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Video) Extensions() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
`.mp4`: `video/mp4`,
|
||||||
|
`.ogm`: `video/ogg`,
|
||||||
|
`.ogv`: `video/ogg`,
|
||||||
|
`.webm`: `video/webm`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Video) MimeTypes() []string {
|
||||||
|
return []string{
|
||||||
|
`video/mp4`,
|
||||||
|
`video/ogg`,
|
||||||
|
`video/webm`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Video) Validate(filePath string) bool {
|
||||||
|
return true
|
||||||
|
}
|
Loading…
Reference in New Issue