Total restructure, just look at the diffs at this point
This commit is contained in:
parent
cbf7218453
commit
d7bc6e2451
16
README.md
16
README.md
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
14
cmd/root.go
14
cmd/root.go
|
@ -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")
|
||||
|
||||
|
|
26
cmd/web.go
26
cmd/web.go
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue