Commit of mostly working code.
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user