diff --git a/README.md b/README.md index bb17824..ca12e05 100644 --- a/README.md +++ b/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]... [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 ``` diff --git a/cmd/cache.go b/cmd/cache.go new file mode 100644 index 0000000..a56eb86 --- /dev/null +++ b/cmd/cache.go @@ -0,0 +1,141 @@ +/* +Copyright © 2023 Seednode +*/ + +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")) + } +} diff --git a/cmd/index.go b/cmd/info.go similarity index 67% rename from cmd/index.go rename to cmd/info.go index 3efdb48..d57e3bf 100644 --- a/cmd/index.go +++ b/cmd/info.go @@ -5,150 +5,20 @@ Copyright © 2023 Seednode 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) +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") - for _, v := range extensions { - output.WriteString(v + "\n") - } + startTime := time.Now() - response := []byte(output.String()) + 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) +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") - for _, v := range mimeTypes { - output.WriteString(v + "\n") - } + startTime := time.Now() - response := []byte(output.String()) + response := []byte(formats.GetMimeTypes()) w.Write(response) diff --git a/cmd/root.go b/cmd/root.go index c206c21..f6345fe 100644 --- a/cmd/root.go +++ b/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") diff --git a/cmd/web.go b/cmd/web.go index 7acf948..4f990a8 100644 --- a/cmd/web.go +++ b/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,20 +413,23 @@ func ServePage(args []string) error { mux.GET("/clear_cache", serveCacheClear(args, index, formats)) } - if Index { - mux.GET("/html/", serveIndexHtml(args, index, false)) - if PageLength != 0 { - mux.GET("/html/:page", serveIndexHtml(args, index, true)) + if Info { + if Cache { + mux.GET("/html/", serveIndexHtml(args, index, false)) + if PageLength != 0 { + mux.GET("/html/:page", serveIndexHtml(args, index, true)) + } + + mux.GET("/json", serveIndexJson(args, index)) + if PageLength != 0 { + mux.GET("/json/:page", serveIndexJson(args, index)) + } } - mux.GET("/json", serveIndexJson(args, index)) - 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 { diff --git a/types/audio.go b/types/audio/audio.go similarity index 65% rename from types/audio.go rename to types/audio/audio.go index 8b3fdd2..2b9be5b 100644 --- a/types/audio.go +++ b/types/audio/audio.go @@ -2,16 +2,18 @@ Copyright © 2023 Seednode */ -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(`%s`, 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(``, 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) +} diff --git a/types/flash.go b/types/flash/flash.go similarity index 65% rename from types/flash.go rename to types/flash/flash.go index 31a2eaa..b5a446e 100644 --- a/types/flash.go +++ b/types/flash/flash.go @@ -2,16 +2,18 @@ Copyright © 2023 Seednode */ -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(`%s`, 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(``, 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) +} diff --git a/types/images.go b/types/images/images.go similarity index 71% rename from types/images.go rename to types/images/images.go index 8ee0041..0362c55 100644 --- a/types/images.go +++ b/types/images/images.go @@ -2,7 +2,7 @@ Copyright © 2023 Seednode */ -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(`%s (%dx%d)`, 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(`Roulette selected: %s`, 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) } diff --git a/types/text.go b/types/text/text.go similarity index 75% rename from types/text.go rename to types/text/text.go index cfa2ec5..78f6b4c 100644 --- a/types/text.go +++ b/types/text/text.go @@ -2,7 +2,7 @@ Copyright © 2023 Seednode */ -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(`%s`, 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) +} diff --git a/types/types.go b/types/types.go index 5e3e3da..ffb6960 100644 --- a/types/types.go +++ b/types/types.go @@ -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() +} diff --git a/types/video.go b/types/video/video.go similarity index 69% rename from types/video.go rename to types/video/video.go index b870f54..ce5106b 100644 --- a/types/video.go +++ b/types/video/video.go @@ -2,16 +2,18 @@ Copyright © 2023 Seednode */ -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(`%s`, 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(``, 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) +}