diff --git a/cmd/files.go b/cmd/files.go index fd55a0d..80b414c 100644 --- a/cmd/files.go +++ b/cmd/files.go @@ -25,6 +25,7 @@ import ( _ "golang.org/x/image/bmp" _ "golang.org/x/image/webp" + "seedno.de/seednode/roulette/formats" ) type maxConcurrency int @@ -151,9 +152,9 @@ func preparePath(path string) string { return MediaPrefix + path } -func appendPath(directory, path string, files *Files, stats *ScanStats, types *SupportedTypes, shouldCache bool) error { +func appendPath(directory, path string, files *Files, stats *ScanStats, registeredFormats *formats.SupportedFormats, shouldCache bool) error { if shouldCache { - supported, _, _, err := fileType(path, types) + supported, _, _, err := formats.FileType(path, registeredFormats) if err != nil { return err } @@ -170,7 +171,7 @@ func appendPath(directory, path string, files *Files, stats *ScanStats, types *S return nil } -func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, types *SupportedTypes) error { +func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, types *formats.SupportedFormats) error { shouldCache := cache && filters.IsEmpty() absolutePath, err := filepath.Abs(path) @@ -223,7 +224,7 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, return nil } -func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *Index, types *SupportedTypes) (string, error) { +func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *Index, types *formats.SupportedFormats) (string, error) { filePath, err := pickFile(paths, filters, sortOrder, index, types) if err != nil { return "", nil @@ -367,7 +368,7 @@ func pathIsValid(filePath string, paths []string) bool { } } -func pathHasSupportedFiles(path string, types *SupportedTypes) (bool, error) { +func pathHasSupportedFiles(path string, types *formats.SupportedFormats) (bool, error) { hasSupportedFiles := make(chan bool, 1) err := filepath.WalkDir(path, func(p string, info os.DirEntry, err error) error { @@ -379,7 +380,7 @@ func pathHasSupportedFiles(path string, types *SupportedTypes) (bool, error) { case !recursive && info.IsDir() && p != path: return filepath.SkipDir case !info.IsDir(): - supported, _, _, err := fileType(p, types) + supported, _, _, err := formats.FileType(p, types) if err != nil { return err } @@ -424,7 +425,7 @@ func pathCount(path string) (uint32, uint32, error) { return files, directories, nil } -func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, concurrency *Concurrency, types *SupportedTypes) error { +func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, concurrency *Concurrency, types *formats.SupportedFormats) error { var wg sync.WaitGroup err := filepath.WalkDir(path, func(p string, info os.DirEntry, err error) error { @@ -485,7 +486,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con return nil } -func fileList(paths []string, filters *Filters, sort string, index *Index, types *SupportedTypes) ([]string, bool) { +func fileList(paths []string, filters *Filters, sort string, index *Index, types *formats.SupportedFormats) ([]string, bool) { if cache && filters.IsEmpty() && !index.IsEmpty() { return index.Index(), true } @@ -599,8 +600,8 @@ func prepareDirectories(files *Files, sort string) []string { return directories } -func pickFile(args []string, filters *Filters, sort string, index *Index, types *SupportedTypes) (string, error) { - fileList, fromCache := fileList(args, filters, sort, index, types) +func pickFile(args []string, filters *Filters, sort string, index *Index, registeredFormats *formats.SupportedFormats) (string, error) { + fileList, fromCache := fileList(args, filters, sort, index, registeredFormats) fileCount := len(fileList) if fileCount < 1 { @@ -627,7 +628,7 @@ func pickFile(args []string, filters *Filters, sort string, index *Index, types filePath := fileList[val] if !fromCache { - supported, _, _, err := fileType(filePath, types) + supported, _, _, err := formats.FileType(filePath, registeredFormats) if err != nil { return "", err } @@ -659,7 +660,7 @@ func normalizePath(path string) (string, error) { return absolutePath, nil } -func normalizePaths(args []string, types *SupportedTypes) ([]string, error) { +func normalizePaths(args []string, types *formats.SupportedFormats) ([]string, error) { var paths []string var pathList strings.Builder diff --git a/cmd/types.go b/cmd/types.go deleted file mode 100644 index 50b0dc8..0000000 --- a/cmd/types.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright © 2023 Seednode -*/ - -package cmd - -import ( - "errors" - "os" - "path/filepath" - "strings" - - "github.com/h2non/filetype" -) - -type SupportedType struct { - title func(queryParams, filePath, mime, fileName string, width, height int) string - body func(queryParams, filePath, mime, fileName string, width, height int) string - extensions []string - validator func([]byte) bool -} - -func (i *SupportedType) Extensions() []string { - return i.extensions -} - -type SupportedTypes struct { - types []*SupportedType -} - -func (s *SupportedTypes) Add(t *SupportedType) { - s.types = append(s.types, t) -} - -func (s *SupportedTypes) Extensions() []string { - var r []string - - for _, t := range s.types { - r = append(r, t.Extensions()...) - } - - return r -} - -func (s *SupportedTypes) Type(extension string) *SupportedType { - for i := range s.types { - for _, e := range s.types[i].Extensions() { - if extension == e { - return s.types[i] - } - } - } - - return nil -} - -func (s *SupportedTypes) IsSupported(head []byte) bool { - r := false - - for i := range s.types { - if s.types[i].validator(head) { - r = true - } - } - - return r -} - -func fileType(path string, types *SupportedTypes) (bool, *SupportedType, 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, 261) - file.Read(head) - - extension := filepath.Ext(path) - - fileType := types.Type(extension) - - isSupported := types.IsSupported(head) - if !isSupported { - return false, nil, "", nil - } - - mimeType := (filetype.GetType(strings.TrimPrefix(extension, "."))).MIME.Value - - return isSupported, fileType, mimeType, nil -} diff --git a/cmd/web.go b/cmd/web.go index d3cb3fd..21064a6 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -32,6 +32,7 @@ import ( "github.com/julienschmidt/httprouter" "github.com/klauspost/compress/zstd" "github.com/yosssi/gohtml" + "seedno.de/seednode/roulette/formats" ) //go:embed favicons/* @@ -126,12 +127,12 @@ func (i *Index) setIndex(val []string) { i.mutex.Unlock() } -func (i *Index) generateCache(args []string, types *SupportedTypes) { +func (i *Index) generateCache(args []string, supportedFormats *formats.SupportedFormats) { i.mutex.Lock() i.list = []string{} i.mutex.Unlock() - fileList(args, &Filters{}, "", i, types) + fileList(args, &Filters{}, "", i, supportedFormats) if cache && cacheFile != "" { i.Export(cacheFile) @@ -592,9 +593,9 @@ func realIP(r *http.Request) string { } } -func serveCacheClear(args []string, index *Index, types *SupportedTypes) httprouter.Handle { +func serveCacheClear(args []string, index *Index, supportedFormats *formats.SupportedFormats) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - index.generateCache(args, types) + index.generateCache(args, supportedFormats) w.Header().Set("Content-Type", "text/plain") @@ -872,7 +873,7 @@ func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter } } -func serveRoot(paths []string, Regexes *Regexes, index *Index, types *SupportedTypes) httprouter.Handle { +func serveRoot(paths []string, Regexes *Regexes, index *Index, supportedFormats *formats.SupportedFormats) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { refererUri, err := stripQueryParams(refererToUri(r.Referer())) if err != nil { @@ -919,7 +920,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *Index, types *SupportedT break loop } - filePath, err = newFile(paths, filters, sortOrder, Regexes, index, types) + filePath, err = newFile(paths, filters, sortOrder, Regexes, index, supportedFormats) switch { case err != nil && err == ErrNoMediaFound: notFound(w, r, filePath) @@ -945,7 +946,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *Index, types *SupportedT } } -func serveMedia(paths []string, Regexes *Regexes, index *Index, types *SupportedTypes) httprouter.Handle { +func serveMedia(paths []string, Regexes *Regexes, index *Index, supportedFormats *formats.SupportedFormats) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { filters := &Filters{ includes: splitQueryParams(r.URL.Query().Get("include"), Regexes), @@ -974,7 +975,7 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index, types *Supported return } - supported, fileType, mime, err := fileType(filePath, types) + supported, fileType, mime, err := formats.FileType(filePath, supportedFormats) if err != nil { fmt.Println(err) @@ -1015,14 +1016,14 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index, types *Supported htmlBody.WriteString(`a{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.title(queryParams, path, mime, fileName, dimensions.height, dimensions.width))) + htmlBody.WriteString((fileType.Title(queryParams, path, mime, fileName, dimensions.height, dimensions.width))) htmlBody.WriteString(``) if refreshInterval != "0ms" { htmlBody.WriteString(fmt.Sprintf("", queryParams, refreshTimer)) } - htmlBody.WriteString((fileType.body(queryParams, path, mime, fileName, dimensions.height, dimensions.width))) + htmlBody.WriteString((fileType.Body(queryParams, path, mime, fileName, dimensions.height, dimensions.width))) htmlBody.WriteString(``) _, err = io.WriteString(w, gohtml.Format(htmlBody.String())) @@ -1081,21 +1082,21 @@ func ServePage(args []string) error { return errors.New("invalid bind address provided") } - types := &SupportedTypes{} + supportedFormats := &formats.SupportedFormats{} if audio { - types.Add(RegisterAudioFormats()) + supportedFormats.Add(formats.RegisterAudioFormats()) } if images { - types.Add(RegisterImageFormats()) + supportedFormats.Add(formats.RegisterImageFormats()) } if videos { - types.Add(RegisterVideoFormats()) + supportedFormats.Add(formats.RegisterVideoFormats()) } - paths, err := normalizePaths(args, types) + paths, err := normalizePaths(args, supportedFormats) if err != nil { return err } @@ -1138,13 +1139,13 @@ func ServePage(args []string) error { mux.PanicHandler = serverErrorHandler() - mux.GET("/", serveRoot(paths, regexes, index, types)) + mux.GET("/", serveRoot(paths, regexes, index, supportedFormats)) mux.GET("/favicons/*favicon", serveFavicons()) mux.GET("/favicon.ico", serveFavicons()) - mux.GET(MediaPrefix+"/*media", serveMedia(paths, regexes, index, types)) + mux.GET(MediaPrefix+"/*media", serveMedia(paths, regexes, index, supportedFormats)) mux.GET(SourcePrefix+"/*static", serveStaticFile(paths, stats, index)) @@ -1161,10 +1162,10 @@ func ServePage(args []string) error { } if !skipIndex { - index.generateCache(args, types) + index.generateCache(args, supportedFormats) } - mux.GET("/clear_cache", serveCacheClear(args, index, types)) + mux.GET("/clear_cache", serveCacheClear(args, index, supportedFormats)) } if debug { diff --git a/cmd/audio.go b/formats/audio.go similarity index 70% rename from cmd/audio.go rename to formats/audio.go index 1721b8f..23a8d90 100644 --- a/cmd/audio.go +++ b/formats/audio.go @@ -2,7 +2,7 @@ Copyright © 2023 Seednode */ -package cmd +package formats import ( "fmt" @@ -10,19 +10,19 @@ import ( "github.com/h2non/filetype" ) -func RegisterAudioFormats() *SupportedType { - return &SupportedType{ - title: func(queryParams, filePath, mime, fileName string, width, height int) string { +func RegisterAudioFormats() *SupportedFormat { + return &SupportedFormat{ + Title: func(queryParams, filePath, mime, fileName string, width, height int) string { return fmt.Sprintf(`%s`, fileName) }, - body: func(queryParams, filePath, mime, fileName string, width, height int) string { + Body: func(queryParams, filePath, mime, fileName string, width, height int) string { return fmt.Sprintf(``, queryParams, filePath, mime, fileName) }, - extensions: []string{ + Extensions: []string{ `.mp3`, `.ogg`, `.wav`, diff --git a/cmd/images.go b/formats/images.go similarity index 74% rename from cmd/images.go rename to formats/images.go index 24f6fd2..f3491a2 100644 --- a/cmd/images.go +++ b/formats/images.go @@ -2,7 +2,7 @@ Copyright © 2023 Seednode */ -package cmd +package formats import ( "fmt" @@ -15,15 +15,15 @@ import ( _ "golang.org/x/image/webp" ) -func RegisterImageFormats() *SupportedType { - return &SupportedType{ - title: func(queryParams, filePath, mime, fileName string, width, height int) string { +func RegisterImageFormats() *SupportedFormat { + return &SupportedFormat{ + Title: func(queryParams, filePath, mime, fileName string, width, height int) string { return fmt.Sprintf(`%s (%dx%d)`, fileName, width, height) }, - body: func(queryParams, filePath, mime, fileName string, width, height int) string { + Body: func(queryParams, filePath, mime, fileName string, width, height int) string { return fmt.Sprintf(`Roulette selected: %s`, queryParams, filePath, @@ -32,7 +32,7 @@ func RegisterImageFormats() *SupportedType { mime, fileName) }, - extensions: []string{ + Extensions: []string{ `.bmp`, `.gif`, `.jpeg`, diff --git a/formats/types.go b/formats/types.go new file mode 100644 index 0000000..b8b0d6a --- /dev/null +++ b/formats/types.go @@ -0,0 +1,93 @@ +/* +Copyright © 2023 Seednode +*/ + +package formats + +import ( + "errors" + "os" + "path/filepath" + "strings" + + "github.com/h2non/filetype" +) + +type SupportedFormat struct { + Title func(queryParams, filePath, mime, fileName string, width, height int) string + Body func(queryParams, filePath, mime, fileName string, width, height int) string + Extensions []string + validator func([]byte) bool +} + +type SupportedFormats struct { + types []*SupportedFormat +} + +func (s *SupportedFormats) Add(t *SupportedFormat) { + s.types = append(s.types, t) +} + +func (s *SupportedFormats) Extensions() []string { + var r []string + + for _, t := range s.types { + r = append(r, t.Extensions...) + } + + return r +} + +func (s *SupportedFormats) Type(extension string) *SupportedFormat { + for i := range s.types { + for _, e := range s.types[i].Extensions { + if extension == e { + return s.types[i] + } + } + } + + return nil +} + +func (s *SupportedFormats) IsSupported(head []byte) bool { + r := false + + for i := range s.types { + if s.types[i].validator(head) { + r = true + } + } + + return r +} + +func FileType(path string, types *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, 261) + file.Read(head) + + if types.IsSupported(head) { + extension := filepath.Ext(path) + + for _, v := range types.Extensions() { + if extension == v { + fileType := types.Type(extension) + + mimeType := (filetype.GetType(strings.TrimPrefix(extension, "."))).MIME.Value + + return true, fileType, mimeType, nil + } + } + } + + return false, nil, "", nil +} diff --git a/cmd/video.go b/formats/video.go similarity index 70% rename from cmd/video.go rename to formats/video.go index fed8882..acea30d 100644 --- a/cmd/video.go +++ b/formats/video.go @@ -2,7 +2,7 @@ Copyright © 2023 Seednode */ -package cmd +package formats import ( "fmt" @@ -10,19 +10,19 @@ import ( "github.com/h2non/filetype" ) -func RegisterVideoFormats() *SupportedType { - return &SupportedType{ - title: func(queryParams, filePath, mime, fileName string, width, height int) string { +func RegisterVideoFormats() *SupportedFormat { + return &SupportedFormat{ + Title: func(queryParams, filePath, mime, fileName string, width, height int) string { return fmt.Sprintf(`%s`, fileName) }, - body: func(queryParams, filePath, mime, fileName string, width, height int) string { + Body: func(queryParams, filePath, mime, fileName string, width, height int) string { return fmt.Sprintf(``, queryParams, filePath, mime, fileName) }, - extensions: []string{ + Extensions: []string{ `.mp4`, `.ogv`, `.webm`,