diff --git a/cmd/audio.go b/cmd/audio.go new file mode 100644 index 0000000..1721b8f --- /dev/null +++ b/cmd/audio.go @@ -0,0 +1,34 @@ +/* +Copyright © 2023 Seednode +*/ + +package cmd + +import ( + "fmt" + + "github.com/h2non/filetype" +) + +func RegisterAudioFormats() *SupportedType { + return &SupportedType{ + 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 { + return fmt.Sprintf(``, + queryParams, + filePath, + mime, + fileName) + }, + extensions: []string{ + `.mp3`, + `.ogg`, + `.wav`, + }, + validator: func(head []byte) bool { + return filetype.IsAudio(head) + }, + } +} diff --git a/cmd/files.go b/cmd/files.go index 7bf0630..a28da30 100644 --- a/cmd/files.go +++ b/cmd/files.go @@ -153,9 +153,9 @@ func preparePath(path string) string { return MediaPrefix + path } -func appendPath(directory, path string, files *Files, stats *ScanStats, shouldCache bool) error { +func appendPath(directory, path string, files *Files, stats *ScanStats, types *SupportedTypes, shouldCache bool) error { if shouldCache { - supported, _, _, err := fileType(path) + supported, _, _, err := fileType(path, types) if err != nil { return err } @@ -172,7 +172,7 @@ func appendPath(directory, path string, files *Files, stats *ScanStats, shouldCa return nil } -func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats) error { +func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, types *SupportedTypes) error { shouldCache := cache && filters.IsEmpty() absolutePath, err := filepath.Abs(path) @@ -203,7 +203,7 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats) filename, filters.includes[i], ) { - err := appendPath(directory, path, files, stats, shouldCache) + err := appendPath(directory, path, files, stats, types, shouldCache) if err != nil { return err } @@ -217,7 +217,7 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats) return nil } - err = appendPath(directory, path, files, stats, shouldCache) + err = appendPath(directory, path, files, stats, types, shouldCache) if err != nil { return err } @@ -225,8 +225,8 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats) return nil } -func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *Index) (string, error) { - filePath, err := pickFile(paths, filters, sortOrder, index) +func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *Index, types *SupportedTypes) (string, error) { + filePath, err := pickFile(paths, filters, sortOrder, index, types) if err != nil { return "", nil } @@ -369,13 +369,13 @@ func pathIsValid(filePath string, paths []string) bool { } } -func fileType(path string) (bool, string, string, error) { +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 + return false, nil, "", nil case err != nil: - return false, "", "", err + return false, nil, "", err } defer file.Close() @@ -384,35 +384,19 @@ func fileType(path string) (bool, string, string, error) { extension := filepath.Ext(path) - isSupported := false - - for _, e := range Extensions { - if e == extension { - isSupported = true - - break - } - } + fileType := types.Type(extension) + isSupported := types.IsSupported(head) if !isSupported { - return false, "", "", nil + return false, nil, "", nil } - fileType := filetype.GetType(strings.TrimPrefix(extension, ".")) + mimeType := (filetype.GetType(strings.TrimPrefix(extension, "."))).MIME.Value - switch { - case filetype.IsAudio(head) && audio: - return true, "audio", fileType.MIME.Value, nil - case filetype.IsImage(head) && images: - return true, "image", fileType.MIME.Value, nil - case filetype.IsVideo(head) && videos: - return true, "video", fileType.MIME.Value, nil - default: - return false, "", "", nil - } + return isSupported, fileType, mimeType, nil } -func pathHasSupportedFiles(path string) (bool, error) { +func pathHasSupportedFiles(path string, types *SupportedTypes) (bool, error) { hasSupportedFiles := make(chan bool, 1) err := filepath.WalkDir(path, func(p string, info os.DirEntry, err error) error { @@ -424,7 +408,7 @@ func pathHasSupportedFiles(path string) (bool, error) { case !recursive && info.IsDir() && p != path: return filepath.SkipDir case !info.IsDir(): - supported, _, _, err := fileType(p) + supported, _, _, err := fileType(p, types) if err != nil { return err } @@ -469,7 +453,7 @@ func pathCount(path string) (uint32, uint32, error) { return files, directories, nil } -func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, concurrency *Concurrency) error { +func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, concurrency *Concurrency, types *SupportedTypes) error { var wg sync.WaitGroup err := filepath.WalkDir(path, func(p string, info os.DirEntry, err error) error { @@ -496,7 +480,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con fmt.Println(err) } - err = appendPaths(path, files, filters, stats) + err = appendPaths(path, files, filters, stats, types) if err != nil { fmt.Println(err) } @@ -530,7 +514,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con return nil } -func fileList(paths []string, filters *Filters, sort string, index *Index) ([]string, bool) { +func fileList(paths []string, filters *Filters, sort string, index *Index, types *SupportedTypes) ([]string, bool) { if cache && filters.IsEmpty() && !index.IsEmpty() { return index.Index(), true } @@ -569,7 +553,7 @@ func fileList(paths []string, filters *Filters, sort string, index *Index) ([]st wg.Done() }() - err := scanPath(paths[i], files, filters, stats, concurrency) + err := scanPath(paths[i], files, filters, stats, concurrency, types) if err != nil { fmt.Println(err) } @@ -644,8 +628,8 @@ func prepareDirectories(files *Files, sort string) []string { return directories } -func pickFile(args []string, filters *Filters, sort string, index *Index) (string, error) { - fileList, fromCache := fileList(args, filters, sort, index) +func pickFile(args []string, filters *Filters, sort string, index *Index, types *SupportedTypes) (string, error) { + fileList, fromCache := fileList(args, filters, sort, index, types) fileCount := len(fileList) if fileCount < 1 { @@ -672,7 +656,7 @@ func pickFile(args []string, filters *Filters, sort string, index *Index) (strin filePath := fileList[val] if !fromCache { - supported, _, _, err := fileType(filePath) + supported, _, _, err := fileType(filePath, types) if err != nil { return "", err } @@ -704,7 +688,7 @@ func normalizePath(path string) (string, error) { return absolutePath, nil } -func normalizePaths(args []string) ([]string, error) { +func normalizePaths(args []string, types *SupportedTypes) ([]string, error) { var paths []string var pathList strings.Builder @@ -718,7 +702,7 @@ func normalizePaths(args []string) ([]string, error) { pathMatches := (args[i] == path) - hasSupportedFiles, err := pathHasSupportedFiles(path) + hasSupportedFiles, err := pathHasSupportedFiles(path, types) if err != nil { return nil, err } diff --git a/cmd/images.go b/cmd/images.go new file mode 100644 index 0000000..24f6fd2 --- /dev/null +++ b/cmd/images.go @@ -0,0 +1,47 @@ +/* +Copyright © 2023 Seednode +*/ + +package cmd + +import ( + "fmt" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + + "github.com/h2non/filetype" + _ "golang.org/x/image/bmp" + _ "golang.org/x/image/webp" +) + +func RegisterImageFormats() *SupportedType { + return &SupportedType{ + 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 { + return fmt.Sprintf(`Roulette selected: %s`, + queryParams, + filePath, + width, + height, + mime, + fileName) + }, + extensions: []string{ + `.bmp`, + `.gif`, + `.jpeg`, + `.jpg`, + `.png`, + `.webp`, + }, + validator: func(head []byte) bool { + return filetype.IsImage(head) + }, + } +} diff --git a/cmd/root.go b/cmd/root.go index ac44218..d6fce15 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,7 +17,7 @@ var ( ) const ( - Version string = "0.63.2" + Version string = "0.64.0" ) var ( diff --git a/cmd/types.go b/cmd/types.go new file mode 100644 index 0000000..2e15ff7 --- /dev/null +++ b/cmd/types.go @@ -0,0 +1,58 @@ +/* +Copyright © 2023 Seednode +*/ + +package cmd + +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 +} diff --git a/cmd/video.go b/cmd/video.go new file mode 100644 index 0000000..fed8882 --- /dev/null +++ b/cmd/video.go @@ -0,0 +1,34 @@ +/* +Copyright © 2023 Seednode +*/ + +package cmd + +import ( + "fmt" + + "github.com/h2non/filetype" +) + +func RegisterVideoFormats() *SupportedType { + return &SupportedType{ + 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 { + return fmt.Sprintf(``, + queryParams, + filePath, + mime, + fileName) + }, + extensions: []string{ + `.mp4`, + `.ogv`, + `.webm`, + }, + validator: func(head []byte) bool { + return filetype.IsVideo(head) + }, + } +} diff --git a/cmd/web.go b/cmd/web.go index d1bd57d..d3cb3fd 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -126,12 +126,12 @@ func (i *Index) setIndex(val []string) { i.mutex.Unlock() } -func (i *Index) generateCache(args []string) { +func (i *Index) generateCache(args []string, types *SupportedTypes) { i.mutex.Lock() i.list = []string{} i.mutex.Unlock() - fileList(args, &Filters{}, "", i) + fileList(args, &Filters{}, "", i, types) if cache && cacheFile != "" { i.Export(cacheFile) @@ -592,9 +592,9 @@ func realIP(r *http.Request) string { } } -func serveCacheClear(args []string, index *Index) httprouter.Handle { +func serveCacheClear(args []string, index *Index, types *SupportedTypes) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - index.generateCache(args) + index.generateCache(args, types) w.Header().Set("Content-Type", "text/plain") @@ -872,7 +872,7 @@ func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter } } -func serveRoot(paths []string, Regexes *Regexes, index *Index) httprouter.Handle { +func serveRoot(paths []string, Regexes *Regexes, index *Index, types *SupportedTypes) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { refererUri, err := stripQueryParams(refererToUri(r.Referer())) if err != nil { @@ -919,7 +919,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *Index) httprouter.Handle break loop } - filePath, err = newFile(paths, filters, sortOrder, Regexes, index) + filePath, err = newFile(paths, filters, sortOrder, Regexes, index, types) switch { case err != nil && err == ErrNoMediaFound: notFound(w, r, filePath) @@ -945,7 +945,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *Index) httprouter.Handle } } -func serveMedia(paths []string, Regexes *Regexes, index *Index) httprouter.Handle { +func serveMedia(paths []string, Regexes *Regexes, index *Index, types *SupportedTypes) 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 +974,7 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index) httprouter.Handl return } - supported, fileType, mime, err := fileType(filePath) + supported, fileType, mime, err := fileType(filePath, types) if err != nil { fmt.Println(err) @@ -1006,6 +1006,8 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index) httprouter.Handl queryParams := generateQueryParams(filters, sortOrder, refreshInterval) + path := generateFilePath(filePath) + var htmlBody strings.Builder htmlBody.WriteString(``) htmlBody.WriteString(FaviconHtml) @@ -1013,48 +1015,14 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index) httprouter.Handl 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%);}`) - - switch fileType { - case "image": - htmlBody.WriteString(fmt.Sprintf(`%s (%dx%d)`, - fileName, - dimensions.width, - dimensions.height)) - default: - htmlBody.WriteString(fmt.Sprintf(`%s`, - fileName)) - } - + htmlBody.WriteString((fileType.title(queryParams, path, mime, fileName, dimensions.height, dimensions.width))) htmlBody.WriteString(``) if refreshInterval != "0ms" { htmlBody.WriteString(fmt.Sprintf("", queryParams, refreshTimer)) } - - switch fileType { - case "audio": - htmlBody.WriteString(fmt.Sprintf(``, - queryParams, - generateFilePath(filePath), - mime, - fileName)) - case "image": - htmlBody.WriteString(fmt.Sprintf(`Roulette selected: %s`, - queryParams, - generateFilePath(filePath), - dimensions.width, - dimensions.height, - mime, - fileName)) - case "video": - htmlBody.WriteString(fmt.Sprintf(``, - queryParams, - generateFilePath(filePath), - mime, - fileName)) - } - + htmlBody.WriteString((fileType.body(queryParams, path, mime, fileName, dimensions.height, dimensions.width))) htmlBody.WriteString(``) _, err = io.WriteString(w, gohtml.Format(htmlBody.String())) @@ -1113,7 +1081,21 @@ func ServePage(args []string) error { return errors.New("invalid bind address provided") } - paths, err := normalizePaths(args) + types := &SupportedTypes{} + + if audio { + types.Add(RegisterAudioFormats()) + } + + if images { + types.Add(RegisterImageFormats()) + } + + if videos { + types.Add(RegisterVideoFormats()) + } + + paths, err := normalizePaths(args, types) if err != nil { return err } @@ -1156,13 +1138,13 @@ func ServePage(args []string) error { mux.PanicHandler = serverErrorHandler() - mux.GET("/", serveRoot(paths, regexes, index)) + mux.GET("/", serveRoot(paths, regexes, index, types)) mux.GET("/favicons/*favicon", serveFavicons()) mux.GET("/favicon.ico", serveFavicons()) - mux.GET(MediaPrefix+"/*media", serveMedia(paths, regexes, index)) + mux.GET(MediaPrefix+"/*media", serveMedia(paths, regexes, index, types)) mux.GET(SourcePrefix+"/*static", serveStaticFile(paths, stats, index)) @@ -1179,10 +1161,10 @@ func ServePage(args []string) error { } if !skipIndex { - index.generateCache(args) + index.generateCache(args, types) } - mux.GET("/clear_cache", serveCacheClear(args, index)) + mux.GET("/clear_cache", serveCacheClear(args, index, types)) } if debug {