Total restructure, just look at the diffs at this point

This commit is contained in:
Seednode 2023-09-12 23:35:17 -05:00
parent cbf7218453
commit d7bc6e2451
11 changed files with 361 additions and 240 deletions

View File

@ -78,13 +78,17 @@ 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, four additional endpoints are registered.
## Info
If the `-i|--info` flag is passed, six additional endpoints are registered.
The first of these—`/html` and `/json`—return the contents of the index, in HTML and JSON formats respectively.
If `--page-length` is also set, these can be viewed in paginated form by appending `/n`, e.g. `/html/5` for the fifth page.
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.
The remaining four endpoints—`/available_extensions`, `/enabled_extensions`, `/available_mime_types` and `/enabled_mime_types`—return information about the registered file types.
## Statistics
@ -92,6 +96,8 @@ 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.
If `--page-length` is also set, this can be viewed in paginated form by appending `/n`, e.g. `/stats/5` for the fifth page.
## Russian
If the `--russian` flag is passed, everything functions exactly as you would expect.
@ -111,7 +117,7 @@ Usage:
roulette <path> [path]... [flags]
Flags:
--all enable all supported file types
-a, --all enable all supported file types
--audio enable support for audio files
-b, --bind string address to bind to (default "0.0.0.0")
-c, --cache generate directory cache at startup
@ -120,7 +126,7 @@ Flags:
--flash enable support for shockwave flash files (via ruffle.rs)
-h, --help help for roulette
--images enable support for image files
-i, --index expose index endpoints
-i, --info expose informational endpoints
--maximum-files uint32 skip directories with file counts above this value (default 4294967295)
--minimum-files uint32 skip directories with file counts below this value (default 1)
--page-length uint32 pagination length for statistics and debug pages
@ -133,7 +139,7 @@ Flags:
--stats expose stats endpoint
--stats-file string path to optional persistent stats file
--text enable support for text files
-v, --verbose log accessed files to stdout
-v, --verbose log accessed files and other information to stdout
-V, --version display version and exit
--video enable support for video files
```

141
cmd/cache.go Normal file
View File

@ -0,0 +1,141 @@
/*
Copyright © 2023 Seednode <seednode@seedno.de>
*/
package cmd
import (
"encoding/gob"
"net/http"
"os"
"sync"
"github.com/julienschmidt/httprouter"
"github.com/klauspost/compress/zstd"
"seedno.de/seednode/roulette/types"
)
type FileIndex struct {
mutex sync.RWMutex
list []string
}
func (i *FileIndex) Index() []string {
i.mutex.RLock()
val := i.list
i.mutex.RUnlock()
return val
}
func (i *FileIndex) Remove(path string) {
i.mutex.RLock()
tempIndex := make([]string, len(i.list))
copy(tempIndex, i.list)
i.mutex.RUnlock()
var position int
for k, v := range tempIndex {
if path == v {
position = k
break
}
}
tempIndex[position] = tempIndex[len(tempIndex)-1]
i.mutex.Lock()
i.list = make([]string, len(tempIndex)-1)
copy(i.list, tempIndex[:len(tempIndex)-1])
i.mutex.Unlock()
}
func (i *FileIndex) setIndex(val []string) {
i.mutex.Lock()
i.list = val
i.mutex.Unlock()
}
func (i *FileIndex) generateCache(args []string, formats *types.Types) {
i.mutex.Lock()
i.list = []string{}
i.mutex.Unlock()
fileList(args, &Filters{}, "", i, formats)
if Cache && CacheFile != "" {
i.Export(CacheFile)
}
}
func (i *FileIndex) IsEmpty() bool {
i.mutex.RLock()
length := len(i.list)
i.mutex.RUnlock()
return length == 0
}
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
}
defer file.Close()
z, err := zstd.NewWriter(file)
if err != nil {
return err
}
defer z.Close()
enc := gob.NewEncoder(z)
i.mutex.RLock()
enc.Encode(&i.list)
i.mutex.RUnlock()
return nil
}
func (i *FileIndex) Import(path string) error {
file, err := os.OpenFile(path, os.O_RDONLY, 0600)
if err != nil {
return err
}
defer file.Close()
z, err := zstd.NewReader(file)
if err != nil {
return err
}
defer z.Close()
dec := gob.NewDecoder(z)
i.mutex.Lock()
err = dec.Decode(&i.list)
i.mutex.Unlock()
if err != nil {
return err
}
return nil
}
func serveCacheClear(args []string, index *FileIndex, formats *types.Types) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
index.generateCache(args, formats)
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("Ok"))
}
}

View File

@ -5,150 +5,20 @@ Copyright © 2023 Seednode <seednode@seedno.de>
package cmd
import (
"encoding/gob"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"slices"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/julienschmidt/httprouter"
"github.com/klauspost/compress/zstd"
"github.com/yosssi/gohtml"
"seedno.de/seednode/roulette/types"
)
type FileIndex struct {
mutex sync.RWMutex
list []string
}
func (i *FileIndex) Index() []string {
i.mutex.RLock()
val := i.list
i.mutex.RUnlock()
return val
}
func (i *FileIndex) Remove(path string) {
i.mutex.RLock()
tempIndex := make([]string, len(i.list))
copy(tempIndex, i.list)
i.mutex.RUnlock()
var position int
for k, v := range tempIndex {
if path == v {
position = k
break
}
}
tempIndex[position] = tempIndex[len(tempIndex)-1]
i.mutex.Lock()
i.list = make([]string, len(tempIndex)-1)
copy(i.list, tempIndex[:len(tempIndex)-1])
i.mutex.Unlock()
}
func (i *FileIndex) setIndex(val []string) {
i.mutex.Lock()
i.list = val
i.mutex.Unlock()
}
func (i *FileIndex) generateCache(args []string, formats *types.Types) {
i.mutex.Lock()
i.list = []string{}
i.mutex.Unlock()
fileList(args, &Filters{}, "", i, formats)
if Cache && CacheFile != "" {
i.Export(CacheFile)
}
}
func (i *FileIndex) IsEmpty() bool {
i.mutex.RLock()
length := len(i.list)
i.mutex.RUnlock()
return length == 0
}
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
}
defer file.Close()
z, err := zstd.NewWriter(file)
if err != nil {
return err
}
defer z.Close()
enc := gob.NewEncoder(z)
i.mutex.RLock()
enc.Encode(&i.list)
i.mutex.RUnlock()
return nil
}
func (i *FileIndex) Import(path string) error {
file, err := os.OpenFile(path, os.O_RDONLY, 0600)
if err != nil {
return err
}
defer file.Close()
z, err := zstd.NewReader(file)
if err != nil {
return err
}
defer z.Close()
dec := gob.NewDecoder(z)
i.mutex.Lock()
err = dec.Decode(&i.list)
i.mutex.Unlock()
if err != nil {
return err
}
return nil
}
func serveCacheClear(args []string, index *FileIndex, formats *types.Types) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
index.generateCache(args, formats)
w.Header().Set("Content-Type", "text/plain")
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")
@ -318,30 +188,34 @@ func serveIndexJson(args []string, index *FileIndex) httprouter.Handle {
}
}
func serveExtensions(formats *types.Types) httprouter.Handle {
func serveAvailableExtensions() 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
response := []byte(types.SupportedFormats.GetExtensions())
extensions := make([]string, len(formats.Extensions))
w.Write(response)
i := 0
for k := range formats.Extensions {
extensions[i] = k
i++
if Verbose {
fmt.Printf("%s | Served available extensions list (%s) to %s in %s\n",
startTime.Format(LogDate),
humanReadableSize(len(response)),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
}
slices.Sort(extensions)
for _, v := range extensions {
output.WriteString(v + "\n")
}
}
response := []byte(output.String())
func serveEnabledExtensions(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()
response := []byte(formats.GetExtensions())
w.Write(response)
@ -356,30 +230,34 @@ func serveExtensions(formats *types.Types) httprouter.Handle {
}
}
func serveMimeTypes(formats *types.Types) httprouter.Handle {
func serveAvailableMimeTypes() 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
response := []byte(types.SupportedFormats.GetMimeTypes())
mimeTypes := make([]string, len(formats.MimeTypes))
w.Write(response)
i := 0
for k := range formats.MimeTypes {
mimeTypes[i] = k
i++
if Verbose {
fmt.Printf("%s | Served available MIME types list (%s) to %s in %s\n",
startTime.Format(LogDate),
humanReadableSize(len(response)),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
}
slices.Sort(mimeTypes)
for _, v := range mimeTypes {
output.WriteString(v + "\n")
}
}
response := []byte(output.String())
func serveEnabledMimeTypes(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()
response := []byte(formats.GetMimeTypes())
w.Write(response)

View File

@ -12,7 +12,7 @@ import (
)
const (
ReleaseVersion string = "0.75.0"
ReleaseVersion string = "0.76.0"
)
var (
@ -24,7 +24,7 @@ var (
Filtering bool
Flash bool
Images bool
Index bool
Info bool
MaximumFileCount uint32
MinimumFileCount uint32
PageLength uint32
@ -46,10 +46,6 @@ var (
Short: "Serves random media from the specified directories.",
Args: cobra.MinimumNArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
if Index {
cmd.MarkFlagRequired("cache")
}
if RefreshInterval != "" {
interval, err := time.ParseDuration(RefreshInterval)
if err != nil || interval < 500*time.Millisecond {
@ -78,7 +74,7 @@ func Execute() {
}
func init() {
rootCmd.Flags().BoolVar(&All, "all", false, "enable all supported file types")
rootCmd.Flags().BoolVarP(&All, "all", "a", 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")
@ -86,7 +82,7 @@ func init() {
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().BoolVarP(&Info, "info", "i", false, "expose informational endpoints")
rootCmd.Flags().Uint32Var(&MaximumFileCount, "maximum-files", 1<<32-1, "skip directories with file counts above this value")
rootCmd.Flags().Uint32Var(&MinimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
rootCmd.Flags().Uint32Var(&PageLength, "page-length", 0, "pagination length for statistics and debug pages")
@ -99,7 +95,7 @@ func init() {
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(&Verbose, "verbose", "v", false, "log accessed files and other information to stdout")
rootCmd.Flags().BoolVarP(&Version, "version", "V", false, "display version and exit")
rootCmd.Flags().BoolVar(&Videos, "video", false, "enable support for video files")

View File

@ -27,6 +27,11 @@ import (
"github.com/julienschmidt/httprouter"
"github.com/yosssi/gohtml"
"seedno.de/seednode/roulette/types"
"seedno.de/seednode/roulette/types/audio"
"seedno.de/seednode/roulette/types/flash"
"seedno.de/seednode/roulette/types/images"
"seedno.de/seednode/roulette/types/text"
"seedno.de/seednode/roulette/types/video"
)
const (
@ -317,25 +322,25 @@ func ServePage(args []string) error {
}
if Audio || All {
formats.Add(types.Audio{})
formats.Add(audio.Format{})
}
if Flash || All {
formats.Add(types.Flash{})
formats.Add(flash.Format{})
}
if Text || All {
formats.Add(types.Text{})
formats.Add(text.Format{})
}
if Videos || All {
formats.Add(types.Video{})
formats.Add(video.Format{})
}
// 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{})
formats.Add(images.Format{})
}
paths, err := normalizePaths(args, formats)
@ -408,7 +413,8 @@ func ServePage(args []string) error {
mux.GET("/clear_cache", serveCacheClear(args, index, formats))
}
if Index {
if Info {
if Cache {
mux.GET("/html/", serveIndexHtml(args, index, false))
if PageLength != 0 {
mux.GET("/html/:page", serveIndexHtml(args, index, true))
@ -418,10 +424,12 @@ func ServePage(args []string) error {
if PageLength != 0 {
mux.GET("/json/:page", serveIndexJson(args, index))
}
}
mux.GET("/extensions", serveExtensions(formats))
mux.GET("/mime_types", serveMimeTypes(formats))
mux.GET("/available_extensions", serveAvailableExtensions())
mux.GET("/enabled_extensions", serveEnabledExtensions(formats))
mux.GET("/available_mime_types", serveAvailableMimeTypes())
mux.GET("/enabled_mime_types", serveEnabledMimeTypes(formats))
}
if Profile {

View File

@ -2,16 +2,18 @@
Copyright © 2023 Seednode <seednode@seedno.de>
*/
package types
package audio
import (
"fmt"
"strings"
"seedno.de/seednode/roulette/types"
)
type Audio struct{}
type Format struct{}
func (t Audio) Css() string {
func (t Format) Css() string {
var css strings.Builder
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`)
@ -20,11 +22,11 @@ func (t Audio) Css() string {
return css.String()
}
func (t Audio) Title(queryParams, fileUri, filePath, fileName, mime string) string {
func (t Format) 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 {
func (t Format) 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,
@ -32,7 +34,7 @@ func (t Audio) Body(queryParams, fileUri, filePath, fileName, mime string) strin
fileName)
}
func (t Audio) Extensions() map[string]string {
func (t Format) Extensions() map[string]string {
return map[string]string{
`.mp3`: `audio/mpeg`,
`.ogg`: `audio/ogg`,
@ -40,7 +42,7 @@ func (t Audio) Extensions() map[string]string {
}
}
func (t Audio) MimeTypes() []string {
func (t Format) MimeTypes() []string {
return []string{
`application/ogg`,
`audio/mp3`,
@ -51,6 +53,12 @@ func (t Audio) MimeTypes() []string {
}
}
func (t Audio) Validate(filePath string) bool {
func (t Format) Validate(filePath string) bool {
return true
}
func init() {
format := Format{}
types.Register(format)
}

View File

@ -2,16 +2,18 @@
Copyright © 2023 Seednode <seednode@seedno.de>
*/
package types
package flash
import (
"fmt"
"strings"
"seedno.de/seednode/roulette/types"
)
type Flash struct{}
type Format struct{}
func (t Flash) Css() string {
func (t Format) Css() string {
var css strings.Builder
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`)
@ -20,11 +22,11 @@ func (t Flash) Css() string {
return css.String()
}
func (t Flash) Title(queryParams, fileUri, filePath, fileName, mime string) string {
func (t Format) 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 {
func (t Format) 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))
@ -33,18 +35,24 @@ func (t Flash) Body(queryParams, fileUri, filePath, fileName, mime string) strin
return html.String()
}
func (t Flash) Extensions() map[string]string {
func (t Format) Extensions() map[string]string {
return map[string]string{
`.swf`: `application/x-shockwave-flash`,
}
}
func (t Flash) MimeTypes() []string {
func (t Format) MimeTypes() []string {
return []string{
`application/x-shockwave-flash`,
}
}
func (t Flash) Validate(filePath string) bool {
func (t Format) Validate(filePath string) bool {
return true
}
func init() {
format := Format{}
types.Register(format)
}

View File

@ -2,7 +2,7 @@
Copyright © 2023 Seednode <seednode@seedno.de>
*/
package types
package images
import (
"errors"
@ -16,16 +16,17 @@ import (
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/webp"
"seedno.de/seednode/roulette/types"
)
type Dimensions struct {
Width int
Height int
type dimensions struct {
width int
height int
}
type Images struct{}
type Format struct{}
func (t Images) Css() string {
func (t Format) Css() string {
var css strings.Builder
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`)
@ -36,7 +37,7 @@ func (t Images) Css() string {
return css.String()
}
func (t Images) Title(queryParams, fileUri, filePath, fileName, mime string) string {
func (t Format) Title(queryParams, fileUri, filePath, fileName, mime string) string {
dimensions, err := ImageDimensions(filePath)
if err != nil {
fmt.Println(err)
@ -44,11 +45,11 @@ func (t Images) Title(queryParams, fileUri, filePath, fileName, mime string) str
return fmt.Sprintf(`<title>%s (%dx%d)</title>`,
fileName,
dimensions.Width,
dimensions.Height)
dimensions.width,
dimensions.height)
}
func (t Images) Body(queryParams, fileUri, filePath, fileName, mime string) string {
func (t Format) Body(queryParams, fileUri, filePath, fileName, mime string) string {
dimensions, err := ImageDimensions(filePath)
if err != nil {
fmt.Println(err)
@ -57,13 +58,13 @@ func (t Images) Body(queryParams, fileUri, filePath, fileName, mime string) stri
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,
dimensions.width,
dimensions.height,
mime,
fileName)
}
func (t Images) Extensions() map[string]string {
func (t Format) Extensions() map[string]string {
return map[string]string{
`.apng`: `image/apng`,
`.avif`: `image/avif`,
@ -80,7 +81,7 @@ func (t Images) Extensions() map[string]string {
}
}
func (t Images) MimeTypes() []string {
func (t Format) MimeTypes() []string {
return []string{
`image/apng`,
`image/avif`,
@ -93,19 +94,19 @@ func (t Images) MimeTypes() []string {
}
}
func (t Images) Validate(filePath string) bool {
func (t Format) Validate(filePath string) bool {
return true
}
func ImageDimensions(path string) (*Dimensions, error) {
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
return &dimensions{}, nil
case err != nil:
fmt.Printf("File %s open returned error: %s\n", path, err)
return &Dimensions{}, err
return &dimensions{}, err
}
defer file.Close()
@ -113,11 +114,17 @@ func ImageDimensions(path string) (*Dimensions, error) {
switch {
case errors.Is(err, image.ErrFormat):
fmt.Printf("File %s has invalid image format\n", path)
return &Dimensions{Width: 0, Height: 0}, nil
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{}, err
}
return &Dimensions{Width: decodedConfig.Width, Height: decodedConfig.Height}, nil
return &dimensions{width: decodedConfig.Width, height: decodedConfig.Height}, nil
}
func init() {
format := Format{}
types.Register(format)
}

View File

@ -2,7 +2,7 @@
Copyright © 2023 Seednode <seednode@seedno.de>
*/
package types
package text
import (
"errors"
@ -10,11 +10,13 @@ import (
"os"
"strings"
"unicode/utf8"
"seedno.de/seednode/roulette/types"
)
type Text struct{}
type Format struct{}
func (t Text) Css() string {
func (t Format) Css() string {
var css strings.Builder
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`)
@ -25,11 +27,11 @@ func (t Text) Css() string {
return css.String()
}
func (t Text) Title(queryParams, fileUri, filePath, fileName, mime string) string {
func (t Format) 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 {
func (t Format) Body(queryParams, fileUri, filePath, fileName, mime string) string {
body, err := os.ReadFile(filePath)
if err != nil {
body = []byte{}
@ -40,7 +42,7 @@ func (t Text) Body(queryParams, fileUri, filePath, fileName, mime string) string
body)
}
func (t Text) Extensions() map[string]string {
func (t Format) Extensions() map[string]string {
return map[string]string{
`.css`: `text/css`,
`.csv`: `text/csv`,
@ -54,7 +56,7 @@ func (t Text) Extensions() map[string]string {
}
}
func (t Text) MimeTypes() []string {
func (t Format) MimeTypes() []string {
return []string{
`application/json`,
`application/xml`,
@ -67,7 +69,7 @@ func (t Text) MimeTypes() []string {
}
}
func (t Text) Validate(filePath string) bool {
func (t Format) Validate(filePath string) bool {
file, err := os.Open(filePath)
switch {
case errors.Is(err, os.ErrNotExist):
@ -82,3 +84,9 @@ func (t Text) Validate(filePath string) bool {
return utf8.Valid(head)
}
func init() {
format := Format{}
types.Register(format)
}

View File

@ -9,8 +9,15 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strings"
)
var SupportedFormats = &Types{
Extensions: make(map[string]string),
MimeTypes: make(map[string]Type),
}
type Type interface {
Css() string
Title(queryParams, fileUri, filePath, fileName, mime string) string
@ -74,3 +81,49 @@ func FileType(path string, registeredFormats *Types) (bool, Type, string, error)
return false, nil, "", nil
}
func Register(t Type) {
SupportedFormats.Add(t)
}
func (t Types) GetExtensions() string {
var output strings.Builder
extensions := make([]string, len(t.Extensions))
i := 0
for k := range t.Extensions {
extensions[i] = k
i++
}
slices.Sort(extensions)
for _, v := range extensions {
output.WriteString(v + "\n")
}
return output.String()
}
func (t Types) GetMimeTypes() string {
var output strings.Builder
mimeTypes := make([]string, len(t.MimeTypes))
i := 0
for k := range t.MimeTypes {
mimeTypes[i] = k
i++
}
slices.Sort(mimeTypes)
for _, v := range mimeTypes {
output.WriteString(v + "\n")
}
return output.String()
}

View File

@ -2,16 +2,18 @@
Copyright © 2023 Seednode <seednode@seedno.de>
*/
package types
package video
import (
"fmt"
"strings"
"seedno.de/seednode/roulette/types"
)
type Video struct{}
type Format struct{}
func (t Video) Css() string {
func (t Format) Css() string {
var css strings.Builder
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`)
@ -22,11 +24,11 @@ func (t Video) Css() string {
return css.String()
}
func (t Video) Title(queryParams, fileUri, filePath, fileName, mime string) string {
func (t Format) 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 {
func (t Format) 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,
@ -34,7 +36,7 @@ func (t Video) Body(queryParams, fileUri, filePath, fileName, mime string) strin
fileName)
}
func (t Video) Extensions() map[string]string {
func (t Format) Extensions() map[string]string {
return map[string]string{
`.mp4`: `video/mp4`,
`.ogm`: `video/ogg`,
@ -43,7 +45,7 @@ func (t Video) Extensions() map[string]string {
}
}
func (t Video) MimeTypes() []string {
func (t Format) MimeTypes() []string {
return []string{
`video/mp4`,
`video/ogg`,
@ -51,6 +53,12 @@ func (t Video) MimeTypes() []string {
}
}
func (t Video) Validate(filePath string) bool {
func (t Format) Validate(filePath string) bool {
return true
}
func init() {
format := Format{}
types.Register(format)
}