reorganise code into func areas
This commit is contained in:
@@ -4,6 +4,7 @@ go 1.25.6
|
||||
|
||||
require (
|
||||
code.aneur.in/go/rest v0.0.0-20260710144250-d3e2b415e4ac
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
code.aneur.in/go/rest v0.0.0-20260710144250-d3e2b415e4ac h1:rmdYhq+CsQzPqK/tQ6G/DtWIS+IIME47Draei/mR9BM=
|
||||
code.aneur.in/go/rest v0.0.0-20260710144250-d3e2b415e4ac/go.mod h1:HtXodmPBkxQyYkXn4UHURJEs44a+Jm+4tUaKXj246uE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
|
||||
-142
@@ -1,142 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"code.aneur.in/go/rest"
|
||||
"code.aneur.in/zampler/zampler/internal/dto"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Files Files
|
||||
FileIDsByPath map[string]string
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
func (app *App) Cmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "zampler root-dir ...",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: app.PreRunE,
|
||||
RunE: app.RunE,
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
|
||||
f.StringP("address", "a", "", "HTTP bind address")
|
||||
f.IntP("port", "p", 7777, "HTTP listen port")
|
||||
|
||||
f.StringP("log-level", "l", "info", "logging level (trace, debug, info, warn, error)")
|
||||
|
||||
f.StringArrayP("extension", "x", []string{"wav"}, "allowed audio file extensions")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (app *App) PreRunE(cmd *cobra.Command, _ []string) error {
|
||||
app.Log = zerolog.New(zerolog.NewConsoleWriter()).With().Timestamp().Logger()
|
||||
|
||||
levelStr, _ := cmd.Flags().GetString("log-level")
|
||||
level, err := zerolog.ParseLevel(levelStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app.Log = app.Log.Level(level)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *App) RunE(cmd *cobra.Command, rootDirs []string) error {
|
||||
extensions, _ := cmd.Flags().GetStringArray("extension")
|
||||
host, _ := cmd.Flags().GetString("host")
|
||||
port, _ := cmd.Flags().GetInt("port")
|
||||
|
||||
if len(extensions) == 0 {
|
||||
return errors.New("at least one extension required")
|
||||
}
|
||||
|
||||
files, err := app.MultiScan(rootDirs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app.Files = files.WithExtensions(extensions...)
|
||||
app.FileIDsByPath = app.Files.PathMap()
|
||||
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.Path("/api/files").Methods(http.MethodGet).HandlerFunc(app.listFiles)
|
||||
router.Path("/api/files/{path:.+}").Methods(http.MethodGet).HandlerFunc(app.getFileByPath)
|
||||
router.Path("/api/file/{id}").Methods(http.MethodGet).HandlerFunc(app.getFile)
|
||||
|
||||
errch := make(chan error)
|
||||
go func() {
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
errch <- http.ListenAndServe(addr, router)
|
||||
}()
|
||||
|
||||
app.Log.Info().Int("port", port).Msg("Listening for HTTP connections")
|
||||
return <-errch
|
||||
}
|
||||
|
||||
func (app *App) getFile(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
|
||||
id := vars["id"]
|
||||
file := app.Files[id]
|
||||
|
||||
if file == nil {
|
||||
rest.ErrNotFound.WriteJSON(w)
|
||||
return
|
||||
}
|
||||
|
||||
rest.WriteResponseJSON(w, http.StatusOK, dto.File{
|
||||
ID: file.ID(),
|
||||
Path: file.Path,
|
||||
Size: file.Size(),
|
||||
})
|
||||
}
|
||||
|
||||
func (app *App) getFileByPath(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
|
||||
path := fmt.Sprintf("/%s", vars["path"])
|
||||
id := app.FileIDsByPath[path]
|
||||
if id == "" {
|
||||
rest.ErrNotFound.WriteJSON(w)
|
||||
}
|
||||
|
||||
file := app.Files[id]
|
||||
if file == nil {
|
||||
rest.ErrNotFound.WriteJSON(w)
|
||||
return
|
||||
}
|
||||
|
||||
rest.WriteResponseJSON(w, http.StatusOK, dto.File{
|
||||
ID: file.ID(),
|
||||
Path: file.Path,
|
||||
Size: file.Size(),
|
||||
})
|
||||
}
|
||||
|
||||
func (app *App) listFiles(w http.ResponseWriter, req *http.Request) {
|
||||
dtos := []dto.File{}
|
||||
|
||||
for _, file := range app.Files {
|
||||
dtos = append(dtos, dto.File{
|
||||
ID: file.ID(),
|
||||
Path: file.Path,
|
||||
Size: file.Size(),
|
||||
})
|
||||
}
|
||||
|
||||
rest.WriteResponseJSON(w, http.StatusOK, dtos)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
RootDir string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (file *File) AbsPath() string {
|
||||
return path.Join(file.RootDir, file.Path)
|
||||
}
|
||||
|
||||
func (f *File) Extension() string {
|
||||
if i := strings.LastIndexByte(f.Path, '.'); i > -1 {
|
||||
return strings.ToLower(f.Path[i+1:])
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (file *File) Filename() string {
|
||||
return path.Base(file.Path)
|
||||
}
|
||||
|
||||
func (file *File) Hash() (string, error) {
|
||||
b, err := os.ReadFile(file.AbsPath())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(b)
|
||||
|
||||
return hex.EncodeToString(sum[0:]), nil
|
||||
}
|
||||
|
||||
func (f *File) ID() string {
|
||||
b := []byte(f.AbsPath())
|
||||
|
||||
sum := sha256.Sum256(b)
|
||||
|
||||
return hex.EncodeToString(sum[0:])
|
||||
}
|
||||
|
||||
func (f *File) Info() (os.FileInfo, error) {
|
||||
return os.Stat(f.AbsPath())
|
||||
}
|
||||
|
||||
func (f *File) Size() int64 {
|
||||
if info, err := f.Info(); err == nil {
|
||||
return info.Size()
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (file *File) String() string {
|
||||
return file.AbsPath()
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Files map[string]*File
|
||||
|
||||
func (files Files) PathMap() map[string]string {
|
||||
out := map[string]string{}
|
||||
|
||||
for _, file := range files {
|
||||
out[file.Path] = file.ID()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (files Files) WithExtensions(exts ...string) Files {
|
||||
out := Files{}
|
||||
|
||||
for id, file := range files {
|
||||
if slices.Contains(exts, file.Extension()) {
|
||||
out[id] = file
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
func (app *App) MultiScan(rootDirs ...string) (Files, error) {
|
||||
files := Files{}
|
||||
|
||||
for _, rootDir := range rootDirs {
|
||||
moreFiles, err := app.Scan(rootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maps.Copy(files, moreFiles)
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (app *App) Scan(rootDir string) (Files, error) {
|
||||
if !path.IsAbs(rootDir) {
|
||||
return nil, errors.New("rootDir must be absolute path")
|
||||
}
|
||||
|
||||
log := app.Log.With().Str("rootDir", rootDir).Logger()
|
||||
|
||||
log.Debug().Msg("Scanning for files")
|
||||
paths, err := scanRecursive(rootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := len(rootDir)
|
||||
files := Files{}
|
||||
for _, path := range paths {
|
||||
file := &File{
|
||||
RootDir: rootDir,
|
||||
Path: path[l:],
|
||||
}
|
||||
|
||||
id := file.ID()
|
||||
files[id] = file
|
||||
|
||||
log.Trace().Str("id", id).Str("path", files[id].Path).Msg("Found")
|
||||
}
|
||||
|
||||
log.Debug().Int("files", len(files)).Msg("Completed scan")
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func scanRecursive(dir string) ([]string, error) {
|
||||
paths := []string{}
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
path := path.Join(dir, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
subPaths, err := scanRecursive(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, subPaths...)
|
||||
} else {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.aneur.in/zampler/zampler/internal"
|
||||
"code.aneur.in/zampler/zampler/internal/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := internal.App{}
|
||||
cli := cli.NewCLI()
|
||||
|
||||
if err := app.Cmd().Execute(); err != nil {
|
||||
if err := cli.Cmd().Execute(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user