Files
edas/main.go
T

234 lines
6.2 KiB
Go

package main
import (
"context"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/urfave/cli/v3"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
)
var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
var previousLine = make(map[string]int)
func main() {
cmd := &cli.Command{
Name: "edas",
Usage: "an api for reading data from elite dangerous",
Flags: []cli.Flag{
&cli.StringFlag{Name: "path", Value: "%USERPROFILE%\\Saved Games\\Frontier Developments\\Elite Dangerous"},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
r := gin.Default()
/* These are all the routes that just serve json data straight from the files. */
r.GET("/backpack", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "Backpack.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/cargo", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "Cargo.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/fcmaterials", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "FCMaterials.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/market", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "Market.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/modulesinfo", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "ModulesInfo.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/navroute", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "NavRoute.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/outfitting", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "Outfitting.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/shiplocker", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "ShipLocker.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/shipyard", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "Shipyard.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
r.GET("/status", func(c *gin.Context) {
b, err := os.ReadFile(ExpandPath(filepath.Join(cmd.String("path"), "Status.json")))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.Data(200, "application/json; charset=utf-8", b)
})
/* Shared variables for the watcher and websocket. */
hub := HubNew()
/* Read in all existing Journal files so we know what to ignore. */
files, err := filepath.Glob(ExpandPath(filepath.Join(cmd.String("path"), "Journal.*.log")))
if err != nil {
return err
}
for _, f := range files {
data, err := os.ReadFile(f)
if err != nil {
return err
}
lines := strings.Split(string(data), "\n")
previousLine[filepath.Base(f)] = len(lines)
}
/* This is the watcher that waits for changes to the journal files. */
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op.Has(fsnotify.Write) {
data, err := processChangedFile(event.Name)
if err != nil {
log.Println(err)
continue
}
hub.sendClient(data)
}
}
}
}()
err = watcher.Add(ExpandPath(cmd.String("path")))
if err != nil {
return err
}
/* This is the endpoint for the websocket that publishes events. */
r.GET("/journal", func(c *gin.Context) {
w, r := c.Writer, c.Request
con, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print(err)
return
}
client := &Client{hub: hub, conn: con}
client.hub.registerClient(client)
go client.keepAlive()
})
return r.Run(":3000")
},
}
if err := cmd.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
}
}
func ExpandPath(path string) string {
re := regexp.MustCompile(`(?m)%([^%]+)%`)
return os.ExpandEnv(re.ReplaceAllString(path, "${$1}"))
}
func processChangedFile(path string) ([]byte, error) {
switch filepath.Base(path) {
case "Backpack.json",
"Cargo.json",
"FCMaterials.json",
"Market.json",
"ModulesInfo.json",
"NavRoute.json",
"Outfitting.json",
"ShipLocker.json",
"Shipyard.json",
"Status.json":
return os.ReadFile(path)
default:
if strings.HasPrefix(filepath.Base(path), "Journal") {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
offset, ok := previousLine[filepath.Base(path)]
if !ok {
offset = 0
}
lines := strings.Split(string(data), "\n")
nextLine := lines[offset-1]
previousLine[filepath.Base(path)]++
return []byte(nextLine), nil
}
}
return nil, nil
}