234 lines
6.2 KiB
Go
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
|
|
}
|