diff options
| -rw-r--r-- | cmd/cmd.go | 44 | ||||
| -rw-r--r-- | cmd/completions.go | 7 | ||||
| -rw-r--r-- | cmd/help.go | 29 | ||||
| -rw-r--r-- | cmd/index.go | 25 | ||||
| -rw-r--r-- | cmd/query.go | 50 | ||||
| -rw-r--r-- | cmd/server.go | 27 | ||||
| -rw-r--r-- | main.go (renamed from cmd/atlas.go) | 125 | ||||
| -rw-r--r-- | makefile | 4 | ||||
| -rw-r--r-- | pkg/data/update.go | 2 | ||||
| -rw-r--r-- | pkg/index/filters.go | 4 | ||||
| -rw-r--r-- | pkg/index/index.go | 9 | ||||
| -rw-r--r-- | pkg/query/errors.go | 2 | ||||
| -rw-r--r-- | pkg/query/parser.go | 5 | ||||
| -rw-r--r-- | pkg/server/server.go | 8 | ||||
| -rw-r--r-- | pkg/server/unix.go | 89 |
15 files changed, 341 insertions, 89 deletions
diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..5a4f942 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "errors" + "flag" + "io/fs" + "os" + "runtime" + "strings" + "time" + + "github.com/adrg/xdg" +) + +type GlobalFlags struct { + IndexRoot string + DBPath string + LogLevel string + LogJson bool + NumWorkers uint + DateFormat string + LogFile string +} + +func SetupGlobalFlags(fs_ *flag.FlagSet, flags *GlobalFlags) { + home, _ := os.UserHomeDir() + dataHome := xdg.DataHome + if dataHome == "" { + dataHome = strings.Join([]string{home, ".local", "share"}, string(os.PathSeparator)) + } + dataHome += string(os.PathSeparator) + "atlas" + if err := os.Mkdir(dataHome, 0755); errors.Is(err, fs.ErrExist) { + } else if err != nil { + panic(err) + } + + flag.StringVar(&flags.IndexRoot, "root", xdg.UserDirs.Documents, "root `directory` for indexing") + flag.StringVar(&flags.DBPath, "db", dataHome+string(os.PathSeparator)+"default.db", "`path` to document database") + flag.StringVar(&flags.LogLevel, "logLevel", "error", "set log `level` (debug, info, warn, error)") + flag.BoolVar(&flags.LogJson, "logJson", false, "log to json") + flag.UintVar(&flags.NumWorkers, "numWorkers", uint(runtime.NumCPU()), "number of worker threads to use (defaults to core count)") + flag.StringVar(&flags.DateFormat, "dateFormat", time.RFC3339, "`format` for dates (see https://pkg.go.dev/time#Layout for more details)") + flag.StringVar(&flags.LogFile, "logFile", "", "`file` to log errors to, use '-' for stdout and empty for stderr") +} diff --git a/cmd/completions.go b/cmd/completions.go new file mode 100644 index 0000000..e169952 --- /dev/null +++ b/cmd/completions.go @@ -0,0 +1,7 @@ +package cmd + +import "fmt" + +func ZshCompletions() { + fmt.Println("Not implemented yet!") +} diff --git a/cmd/help.go b/cmd/help.go new file mode 100644 index 0000000..b3844ac --- /dev/null +++ b/cmd/help.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "fmt" + "os" +) + +var CommandHelp map[string]string + +func PrintHelp() { + fmt.Println("atlas is a note indexing and querying tool") + fmt.Printf("\nUsage:\n %s [global-flags] <command>\n\n", os.Args[0]) + fmt.Println("Commands:") + fmt.Println(" index - build, update, or modify an index") + fmt.Println(" query - search against an index") + fmt.Println(" shell - start a debug shell") + fmt.Println(" server - start an http query server (EXPERIMENTAL)") + fmt.Println(" help - print this help then exit") +} + +func init() { + CommandHelp = make(map[string]string) + CommandHelp["query"] = "" + CommandHelp["index"] = "" + CommandHelp["server"] = "" + CommandHelp["completions"] = "" + CommandHelp["shell"] = "" + CommandHelp["help"] = "" +} diff --git a/cmd/index.go b/cmd/index.go index dde9550..5454a8a 100644 --- a/cmd/index.go +++ b/cmd/index.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "context" @@ -18,7 +18,7 @@ type IndexFlags struct { index.ParseOpts } -func setupIndexFlags(args []string, fs *flag.FlagSet, flags *IndexFlags) { +func SetupIndexFlags(args []string, fs *flag.FlagSet, flags *IndexFlags) { flags.ParseLinks = true flags.ParseMeta = true fs.BoolVar(&flags.IgnoreDateError, "ignoreBadDates", false, "ignore malformed dates while indexing") @@ -77,7 +77,7 @@ func setupIndexFlags(args []string, fs *flag.FlagSet, flags *IndexFlags) { } } -func runIndex(gFlags GlobalFlags, iFlags IndexFlags, db *data.Query) byte { +func RunIndex(gFlags GlobalFlags, iFlags IndexFlags, db *data.Query) byte { switch iFlags.Subcommand { case "build", "update": @@ -99,19 +99,27 @@ func runIndex(gFlags GlobalFlags, iFlags IndexFlags, db *data.Query) byte { filteredFiles := idx.Filter(traversedFiles, gFlags.NumWorkers) fmt.Print(", Filtered ", len(filteredFiles)) - idx.Documents = index.ParseDocs(filteredFiles, gFlags.NumWorkers, iFlags.ParseOpts) + var errCnt uint64 + idx.Documents, errCnt = index.ParseDocs(filteredFiles, gFlags.NumWorkers, iFlags.ParseOpts) fmt.Print(", Parsed ", len(idx.Documents), "\n") + if errCnt > 0 { + fmt.Printf("Encountered %d document parse errors", errCnt) + if !slog.Default().Enabled(context.Background(), slog.LevelWarn) { + fmt.Print(" (set log level to warn for more info)") + } + fmt.Println() + } var err error // switch in order to appease gopls... switch iFlags.Subcommand { - case "index": + case "build": err = db.Put(idx) case "update": err = db.Update(idx) } if err != nil { - fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, "Error modifying index:", err) return 1 } case "tidy": @@ -120,9 +128,12 @@ func runIndex(gFlags GlobalFlags, iFlags IndexFlags, db *data.Query) byte { return 1 } default: - fmt.Fprintln(os.Stderr, "Unrecognised index subcommands: ", iFlags.Subcommand) + fmt.Fprintln(os.Stderr, "Unrecognized index subcommands: ", iFlags.Subcommand) return 2 } return 0 } + +func init() { +} diff --git a/cmd/query.go b/cmd/query.go index 649b9ea..d00a792 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -1,9 +1,12 @@ -package main +package cmd import ( "flag" "fmt" + "log/slog" "os" + "slices" + "strings" "github.com/jpappel/atlas/pkg/data" "github.com/jpappel/atlas/pkg/index" @@ -16,9 +19,11 @@ type QueryFlags struct { ListSeparator string CustomFormat string OptimizationLevel int + SortBy string + SortDesc bool } -func setupQueryFlags(args []string, fs *flag.FlagSet, flags *QueryFlags, dateFormat string) { +func SetupQueryFlags(args []string, fs *flag.FlagSet, flags *QueryFlags, dateFormat string) { // NOTE: providing `-outFormat` before `-outCustomFormat` might ignore user specified format fs.Func("outFormat", "output `format` for queries (default, json, pathonly, custom)", func(arg string) error { @@ -39,6 +44,9 @@ func setupQueryFlags(args []string, fs *flag.FlagSet, flags *QueryFlags, dateFor } return fmt.Errorf("Unrecognized output format: %s", arg) }) + + fs.StringVar(&flags.SortBy, "sortBy", "", "category to sort by (path,title,date,filetime,meta)") + fs.BoolVar(&flags.SortDesc, "sortDesc", false, "sort in descending order") fs.StringVar(&flags.CustomFormat, "outCustomFormat", query.DefaultOutputFormat, "format string for --outFormat custom, see Output Format for more details") fs.IntVar(&flags.OptimizationLevel, "optLevel", 0, "optimization `level` for queries, 0 is automatic, <0 to disable") fs.StringVar(&flags.DocumentSeparator, "docSeparator", "\n", "separator for custom output format") @@ -81,7 +89,7 @@ func setupQueryFlags(args []string, fs *flag.FlagSet, flags *QueryFlags, dateFor fs.Parse(args) } -func runQuery(gFlags GlobalFlags, qFlags QueryFlags, db *data.Query, searchQuery string) byte { +func RunQuery(gFlags GlobalFlags, qFlags QueryFlags, db *data.Query, searchQuery string) byte { tokens := query.Lex(searchQuery) clause, err := query.Parse(tokens) if err != nil { @@ -114,6 +122,42 @@ func runQuery(gFlags GlobalFlags, qFlags QueryFlags, db *data.Query, searchQuery outputableResults = append(outputableResults, v) } + var docCmp func(a, b *index.Document) int + descMod := 1 + if qFlags.SortDesc { + descMod = -1 + } + switch qFlags.SortBy { + case "": + case "path": + docCmp = func(a, b *index.Document) int { + return descMod * strings.Compare(a.Path, b.Path) + } + case "title": + docCmp = func(a, b *index.Document) int { + return descMod * strings.Compare(a.Title, b.Title) + } + case "date": + docCmp = func(a, b *index.Document) int { + return descMod * a.Date.Compare(b.Date) + } + case "filetime": + docCmp = func(a, b *index.Document) int { + return descMod * a.FileTime.Compare(b.FileTime) + } + case "meta": + docCmp = func(a, b *index.Document) int { + return descMod * strings.Compare(a.OtherMeta, b.OtherMeta) + } + default: + slog.Error("Unrecognized category to sort by, leaving documents unsorted") + qFlags.SortBy = "" + } + + if qFlags.SortBy != "" { + slices.SortFunc(outputableResults, docCmp) + } + _, err = qFlags.Outputer.OutputTo(os.Stdout, outputableResults) if err != nil { fmt.Fprintln(os.Stderr, "Error while outputting results: ", err) diff --git a/cmd/server.go b/cmd/server.go index 21f4661..be072cf 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "context" @@ -8,6 +8,7 @@ import ( "net/http" "os" "os/signal" + "strings" "syscall" "time" @@ -20,17 +21,26 @@ type ServerFlags struct { Port int } -func setupServerFlags(args []string, fs *flag.FlagSet, flags *ServerFlags) { - fs.StringVar(&flags.Address, "address", "", "the address to listen on") +func SetupServerFlags(args []string, fs *flag.FlagSet, flags *ServerFlags) { + fs.StringVar(&flags.Address, "address", "", "the address to listen on, prefix with 'unix:' to create a unixsocket") fs.IntVar(&flags.Port, "port", 8080, "the port to bind to") fs.Parse(args) } -func runServer(sFlags ServerFlags, db *data.Query) byte { - addr := fmt.Sprintf("%s:%d", sFlags.Address, sFlags.Port) +func RunServer(sFlags ServerFlags, db *data.Query) byte { - s := http.Server{Addr: addr, Handler: server.New(db)} + var addr string + var s server.Server + if after, ok := strings.CutPrefix(sFlags.Address, "unix:"); ok { + slog.Debug("Preparing unix domain socket") + addr = after + s = &server.UnixServer{Addr: addr, Db: db} + } else { + slog.Debug("Preparing http server") + addr = fmt.Sprintf("%s:%d", sFlags.Address, sFlags.Port) + s = &http.Server{Addr: addr, Handler: server.NewMux(db)} + } serverErrors := make(chan error, 1) exit := make(chan os.Signal, 1) @@ -42,13 +52,16 @@ func runServer(sFlags ServerFlags, db *data.Query) byte { if err := s.ListenAndServe(); err != nil { serverErrors <- err } + close(serverErrors) }(serverErrors) select { case <-exit: slog.Info("Recieved signal to shutdown") case err := <-serverErrors: - slog.Error("Server error", slog.String("err", err.Error())) + if err != nil { + slog.Error("Server error", slog.String("err", err.Error())) + } } slog.Info("Shutting down server") @@ -1,35 +1,25 @@ package main import ( - "errors" "flag" "fmt" "io" - "io/fs" "log/slog" + "maps" "os" - "runtime" + "slices" "strings" - "time" - "github.com/adrg/xdg" + "github.com/jpappel/atlas/cmd" "github.com/jpappel/atlas/pkg/data" "github.com/jpappel/atlas/pkg/query" "github.com/jpappel/atlas/pkg/shell" + "github.com/jpappel/atlas/pkg/util" ) -const VERSION = "0.0.1" +const VERSION = "0.4.1" const ExitCommand = 2 // exit because of a command parsing error -type GlobalFlags struct { - IndexRoot string - DBPath string - LogLevel string - LogJson bool - NumWorkers uint - DateFormat string -} - func addGlobalFlagUsage(fs *flag.FlagSet) func() { return func() { f := fs.Output() @@ -40,57 +30,30 @@ func addGlobalFlagUsage(fs *flag.FlagSet) func() { } } -func printHelp() { - fmt.Println("atlas is a note indexing and querying tool") - fmt.Printf("\nUsage:\n %s [global-flags] <command>\n\n", os.Args[0]) - fmt.Println("Commands:") - fmt.Println(" index - build, update, or modify an index") - fmt.Println(" query - search against an index") - fmt.Println(" shell - start a debug shell") - fmt.Println(" server - start an http query server (EXPERIMENTAL)") - fmt.Println(" help - print this help then exit") -} - func main() { - home, _ := os.UserHomeDir() - dataHome := xdg.DataHome - if dataHome == "" { - dataHome = strings.Join([]string{home, ".local", "share"}, string(os.PathSeparator)) - } - dataHome += string(os.PathSeparator) + "atlas" - if err := os.Mkdir(dataHome, 0755); errors.Is(err, fs.ErrExist) { - } else if err != nil { - panic(err) - } - - globalFlags := GlobalFlags{} - flag.StringVar(&globalFlags.IndexRoot, "root", xdg.UserDirs.Documents, "root `directory` for indexing") - flag.StringVar(&globalFlags.DBPath, "db", dataHome+string(os.PathSeparator)+"default.db", "`path` to document database") - flag.StringVar(&globalFlags.LogLevel, "logLevel", "error", "set log `level` (debug, info, warn, error)") - flag.BoolVar(&globalFlags.LogJson, "logJson", false, "log to json") - flag.UintVar(&globalFlags.NumWorkers, "numWorkers", uint(runtime.NumCPU()), "number of worker threads to use (defaults to core count)") - flag.StringVar(&globalFlags.DateFormat, "dateFormat", time.RFC3339, "format for dates (see https://pkg.go.dev/time#Layout for more details)") + globalFlags := cmd.GlobalFlags{} + cmd.SetupGlobalFlags(flag.CommandLine, &globalFlags) indexFs := flag.NewFlagSet("index", flag.ExitOnError) queryFs := flag.NewFlagSet("query", flag.ExitOnError) shellFs := flag.NewFlagSet("debug", flag.ExitOnError) serverFs := flag.NewFlagSet("server", flag.ExitOnError) + completionsFs := flag.NewFlagSet("completions", flag.ContinueOnError) - indexFs.Usage = addGlobalFlagUsage(indexFs) - queryFs.Usage = addGlobalFlagUsage(queryFs) + // set default usage for flagsets without subcommands shellFs.Usage = addGlobalFlagUsage(shellFs) serverFs.Usage = addGlobalFlagUsage(serverFs) flag.Parse() args := flag.Args() - queryFlags := QueryFlags{Outputer: query.DefaultOutput{}} - indexFlags := IndexFlags{} - serverFlags := ServerFlags{Port: 8080} + queryFlags := cmd.QueryFlags{Outputer: query.DefaultOutput{}} + indexFlags := cmd.IndexFlags{} + serverFlags := cmd.ServerFlags{Port: 8080} if len(args) < 1 { fmt.Fprintln(os.Stderr, "No Command provided") - printHelp() + cmd.PrintHelp() fmt.Fprintln(flag.CommandLine.Output(), "\nGlobal Flags:") flag.PrintDefaults() os.Exit(ExitCommand) @@ -99,20 +62,30 @@ func main() { switch command { case "query", "q": - setupQueryFlags(args[1:], queryFs, &queryFlags, globalFlags.DateFormat) - case "index": - setupIndexFlags(args[1:], indexFs, &indexFlags) + cmd.SetupQueryFlags(args[1:], queryFs, &queryFlags, globalFlags.DateFormat) + case "index", "i": + cmd.SetupIndexFlags(args[1:], indexFs, &indexFlags) case "server": - setupServerFlags(args[1:], serverFs, &serverFlags) + cmd.SetupServerFlags(args[1:], serverFs, &serverFlags) + case "completions": + completionsFs.Parse(args[1:]) case "help": - printHelp() + cmd.PrintHelp() flag.PrintDefaults() return case "shell": shellFs.Parse(args[1:]) default: fmt.Fprintln(os.Stderr, "Unrecognized command: ", command) - printHelp() + suggestedCommand, ok := util.Nearest( + command, + slices.Collect(maps.Keys(cmd.CommandHelp)), + util.LevensteinDistance, 3, + ) + if ok { + fmt.Fprintf(os.Stderr, "Did you mean %s?\n\n", suggestedCommand) + } + cmd.PrintHelp() os.Exit(ExitCommand) } @@ -132,9 +105,26 @@ func main() { fmt.Fprintln(os.Stderr, "Unrecognized log level:", globalFlags.LogLevel) os.Exit(ExitCommand) } + + var logFile *os.File + var err error + switch globalFlags.LogFile { + case "": + logFile = os.Stderr + case "-": + logFile = os.Stdout + default: + logFile, err = os.Create(globalFlags.LogFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot use log file `%s`: %s", globalFlags.LogFile, err) + os.Exit(1) + } + defer logFile.Close() + } + var logHandler slog.Handler if globalFlags.LogJson { - logHandler = slog.NewJSONHandler(os.Stderr, loggerOpts) + logHandler = slog.NewJSONHandler(logFile, loggerOpts) } else { // strip time loggerOpts.ReplaceAttr = func(groups []string, a slog.Attr) slog.Attr { @@ -143,7 +133,7 @@ func main() { } return a } - logHandler = slog.NewTextHandler(os.Stderr, loggerOpts) + logHandler = slog.NewTextHandler(logFile, loggerOpts) } logger := slog.New(logHandler) slog.SetDefault(logger) @@ -155,11 +145,22 @@ func main() { switch command { case "query", "q": searchQuery := strings.Join(queryFs.Args(), " ") - exitCode = int(runQuery(globalFlags, queryFlags, querier, searchQuery)) - case "index": - exitCode = int(runIndex(globalFlags, indexFlags, querier)) + exitCode = int(cmd.RunQuery(globalFlags, queryFlags, querier, searchQuery)) + case "index", "i": + exitCode = int(cmd.RunIndex(globalFlags, indexFlags, querier)) case "server": - exitCode = int(runServer(serverFlags, querier)) + exitCode = int(cmd.RunServer(serverFlags, querier)) + case "completions": + lang := completionsFs.Arg(0) + switch lang { + case "zsh": + cmd.ZshCompletions() + default: + fmt.Fprintf(os.Stderr, "Unrecognized completion language `%s`\n", lang) + fmt.Fprintf(os.Stderr, "Usage %s completions <language>\n", os.Args[0]) + fmt.Fprintln(os.Stderr, "Supported languages: zsh") + exitCode = 2 + } case "shell": state := make(shell.State) env := make(map[string]string) @@ -1,5 +1,5 @@ BINS := atlas -SRC := $(wildcard cmd/*.go) $(wildcard pkg/*/*.go) +SRC := main.go $(wildcard cmd/*.go) $(wildcard pkg/*/*.go) INSTALL_PATH := ~/.local/bin .PHONY: all install uninstall test info clean @@ -7,7 +7,7 @@ INSTALL_PATH := ~/.local/bin all: $(BINS) atlas: $(SRC) - go build -o $@ $(wildcard ./cmd/*.go) + go build -o $@ $< test: go test ./... diff --git a/pkg/data/update.go b/pkg/data/update.go index a8a4b3d..1a50563 100644 --- a/pkg/data/update.go +++ b/pkg/data/update.go @@ -201,6 +201,7 @@ func (u *UpdateMany) documents() (bool, error) { SELECT path FROM temp.updateDocs )`) if err != nil { + slog.Debug("Failed to remove missing files from index") return false, err } @@ -215,6 +216,7 @@ func (u *UpdateMany) documents() (bool, error) { WHERE excluded.fileTime > Documents.fileTime `) if err != nil { + slog.Debug("Failed document upsert") return false, err } diff --git a/pkg/index/filters.go b/pkg/index/filters.go index 920d5df..bd9df54 100644 --- a/pkg/index/filters.go +++ b/pkg/index/filters.go @@ -50,10 +50,10 @@ func ParseFilter(s string) (DocFilter, error) { } return NewMaxFilesizeFilter(size), nil case "ExcludeName", "ExcludeFilename": - // FIXME: support escaped commas + // TODO: support escaped commas return NewExcludeFilenameFilter(strings.Split(param, ",")), nil case "IncludeName", "IncludeFilename": - // FIXME: support escaped commas + // TODO: support escaped commas return NewIncludeFilenameFilter(strings.Split(param, ",")), nil case "ExcludeParent": return NewExcludeParentFilter(param), nil diff --git a/pkg/index/index.go b/pkg/index/index.go index cfa4138..50f642e 100644 --- a/pkg/index/index.go +++ b/pkg/index/index.go @@ -12,6 +12,7 @@ import ( "slices" "strings" "sync" + "sync/atomic" "time" "github.com/goccy/go-yaml" @@ -383,21 +384,23 @@ func ParseDoc(path string, opts ParseOpts) (*Document, error) { return doc, nil } -func ParseDocs(paths []string, numWorkers uint, opts ParseOpts) map[string]*Document { +func ParseDocs(paths []string, numWorkers uint, opts ParseOpts) (map[string]*Document, uint64) { jobs := make(chan string, numWorkers) results := make(chan *Document, numWorkers) docs := make(map[string]*Document, len(paths)) wg := &sync.WaitGroup{} + errCnt := &atomic.Uint64{} wg.Add(int(numWorkers)) for range numWorkers { go func(jobs <-chan string, results chan<- *Document, wg *sync.WaitGroup) { for path := range jobs { doc, err := ParseDoc(path, opts) if err != nil { - slog.Error("Error occured while parsing file", + slog.Warn("Error occured while parsing file", slog.String("path", path), slog.String("err", err.Error()), ) + errCnt.Add(1) continue } @@ -423,7 +426,7 @@ func ParseDocs(paths []string, numWorkers uint, opts ParseOpts) map[string]*Docu docs[doc.Path] = doc } - return docs + return docs, errCnt.Load() } func init() { diff --git a/pkg/query/errors.go b/pkg/query/errors.go index 35f8c19..889d40d 100644 --- a/pkg/query/errors.go +++ b/pkg/query/errors.go @@ -6,7 +6,7 @@ import ( ) var ErrQueryFormat = errors.New("Incorrect query format") -var ErrDatetimeTokenParse = errors.New("Unrecognized format for datetime token") +var ErrDatetimeTokenParse = errors.New("Unrecognized format for datetime") // output errors var ErrUnrecognizedOutputToken = errors.New("Unrecognized output token") diff --git a/pkg/query/parser.go b/pkg/query/parser.go index e22b4c3..53681f6 100644 --- a/pkg/query/parser.go +++ b/pkg/query/parser.go @@ -549,7 +549,10 @@ func Parse(tokens []Token) (*Clause, error) { var t time.Time var err error if t, err = util.ParseDateTime(token.Value); err != nil { - return nil, ErrDatetimeTokenParse + return nil, fmt.Errorf("Cannot parse time `%s`, %v", + token.Value, + ErrDatetimeTokenParse, + ) } clause.Statements[len(clause.Statements)-1].Value = DatetimeValue{t} diff --git a/pkg/server/server.go b/pkg/server/server.go index c08d5a4..a7a5395 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -2,6 +2,7 @@ package server import ( "bytes" + "context" "io" "log/slog" "net/http" @@ -14,6 +15,11 @@ import ( "github.com/jpappel/atlas/pkg/query" ) +type Server interface { + ListenAndServe() error + Shutdown(context.Context) error +} + func info(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` <h1>Atlas Server</h1> @@ -22,7 +28,7 @@ func info(w http.ResponseWriter, r *http.Request) { `)) } -func New(db *data.Query) *http.ServeMux { +func NewMux(db *data.Query) *http.ServeMux { mux := http.NewServeMux() outputBufPool := &sync.Pool{} diff --git a/pkg/server/unix.go b/pkg/server/unix.go new file mode 100644 index 0000000..87b425e --- /dev/null +++ b/pkg/server/unix.go @@ -0,0 +1,89 @@ +package server + +import ( + "bytes" + "context" + "fmt" + "log/slog" + "net" + "os" + "sync" + + "github.com/jpappel/atlas/pkg/data" +) + +// datagram based unix server +type UnixServer struct { + Addr string + Db *data.Query + shouldClose chan struct{} + bufPool *sync.Pool +} + +func (s *UnixServer) ListenAndServe() error { + slog.Info("Listening on", slog.String("addr", s.Addr)) + conn, err := net.ListenUnixgram( + "unixgram", + &net.UnixAddr{Name: s.Addr, Net: "Unix"}, + ) + if err != nil { + return err + } + defer conn.Close() + defer os.RemoveAll(s.Addr) + slog.Debug("Accepted connection") + + // slog.Info("New Connection", + // slog.String("addr", conn.RemoteAddr().String()), + // ) + + s.bufPool = &sync.Pool{} + s.bufPool.New = func() any { + return &bytes.Buffer{} + } + s.handleConn(conn) + + return nil +} + +func (s UnixServer) handleConn(conn *net.UnixConn) { + // buf, ok := s.bufPool.Get().(*bytes.Buffer) + // if !ok { + // panic("Expected *bytes.Buffer in pool") + // } + buf := make([]byte, 1024) + + n, err := conn.Read(buf) + if err != nil { + panic(err) + } + buf = buf[:n] + + fmt.Println(string(buf)) + + // if _, err := io.Copy(buf, conn); err != nil { + // panic(err) + // } + // defer buf.Reset() + // + // io.Copy(os.Stdout, buf) + + // artifact, err := query.Compile(buf.String(), 0, 1) + // if err != nil { + // panic(err) + // } + // + // docs, err := s.Db.Execute(artifact) + // if err != nil { + // panic(err) + // } + // + // _, err = query.DefaultOutput{}.OutputTo(conn, slices.Collect(maps.Values(docs))) + // if err != nil { + // panic(err) + // } +} + +func (s *UnixServer) Shutdown(ctx context.Context) error { + return nil +} |
