allagan tools inventory data format CSV conversion
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
6afa17fdaf
commit
9553f9c0a3
|
@ -748,12 +748,15 @@ func inventoryPageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
for _, retainer := range inventories.Retainers {
|
||||
if retainer.RetainerMarket != nil {
|
||||
|
||||
for _, item := range retainer.RetainerMarket {
|
||||
fmt.Printf("Item ID: %d\n", item.ID)
|
||||
if item.ID == 0 {
|
||||
itemsRequired++
|
||||
}
|
||||
}
|
||||
itemsRequired += 20 - len(retainer.RetainerMarket)
|
||||
// Update since conversion to CSV - no longer do we have nil entries
|
||||
/*
|
||||
for _, item := range retainer.RetainerMarket {
|
||||
fmt.Printf("Item ID: %d\n", item.ID)
|
||||
if item.ID == 0 {
|
||||
itemsRequired++
|
||||
}
|
||||
}*/
|
||||
|
||||
} else {
|
||||
log.Printf("retainer '%s' market inventory is nil, assuming no items posted?", retainer.Name)
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
// Watches the downloads directory for autohotkey files and makes sure that only the newest one downloaded exists at a time
|
||||
func downloadsFileWatcher() {
|
||||
downloadsWatcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal("NewWatcher failed: ", err)
|
||||
}
|
||||
defer downloadsWatcher.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer log.Printf("file watcher go routine shutting down!") // could be from returns below?
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
case event, ok := <-downloadsWatcher.Events:
|
||||
if !ok {
|
||||
log.Printf("downloads watcher _event_ NOT OK TERMINATING ARRRGGHGHH: %v", err)
|
||||
return
|
||||
}
|
||||
if event.Op == fsnotify.Create {
|
||||
if strings.Contains(event.Name, ".ahk") {
|
||||
err = cleanDownloadsAHK()
|
||||
if err != nil {
|
||||
log.Printf("failed to clean downloads: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case err, ok := <-downloadsWatcher.Errors:
|
||||
if !ok {
|
||||
log.Printf("downloads watcher _error_ NOT OK TERMINATING ARRRGGHGHH: %v", err)
|
||||
return
|
||||
}
|
||||
log.Printf("downloads watcher error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
err = downloadsWatcher.Add(downloadsFolderPath)
|
||||
if err != nil {
|
||||
log.Fatal("downloads watcher add down failed:", err)
|
||||
}
|
||||
|
||||
<-done
|
||||
}
|
||||
|
||||
func cleanDownloadsAHK() error {
|
||||
err := filepath.Walk(downloadsFolderPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to walk file: %v", err)
|
||||
}
|
||||
|
||||
// ignore files that aren't AHK
|
||||
if !strings.Contains(info.Name(), ".ahk") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ignore files downloaded in the past 5 minutes
|
||||
if info.ModTime().After(time.Now().Add(time.Minute * -1)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("removing old .ahk file - %s", info.Name())
|
||||
err = os.Remove(fmt.Sprintf("%s/%s", downloadsFolderPath, info.Name()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete old .ahk '%s': %v", info.Name(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to walk downloads directory: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -2,14 +2,13 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.mashffxiv.deadbeef.codes/MashPotato/gilgetter/inventorytools"
|
||||
|
@ -28,7 +27,7 @@ func init() {
|
|||
if err != nil {
|
||||
log.Fatal("Getting appdata directory failed:", err)
|
||||
}
|
||||
inventoriesFilePath = fmt.Sprintf("%s/XIVLauncher/pluginConfigs/GilGetterPlugin/inventories.json", appDataDir)
|
||||
inventoriesFilePath = fmt.Sprintf("%s/XIVLauncher/pluginConfigs/GilGetterPlugin/inventories.csv", appDataDir)
|
||||
|
||||
setRetainers() // Create your own definitions that satisfy the retainer struct - see README.md
|
||||
}
|
||||
|
@ -113,16 +112,17 @@ func refreshFile() error {
|
|||
}
|
||||
lastUpdated = time.Now()
|
||||
|
||||
// read and parse the InventoryTools state file
|
||||
fileBytes, err := os.ReadFile(inventoriesFilePath)
|
||||
f, err := os.Open(inventoriesFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file '%s': %v", inventoriesFilePath, err)
|
||||
}
|
||||
|
||||
err = unmarshalJSON(fileBytes) // update global variables directly, no assignment here
|
||||
parseInventoryCSV(f)
|
||||
|
||||
/*err = unmarshalJSON(fileBytes) // update global variables directly, no assignment here
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON in file '%s': %v", inventoriesFilePath, err)
|
||||
}
|
||||
}*/
|
||||
|
||||
// marshall the transformed data and upload to gilgetter
|
||||
jsonBytes, err := json.Marshal(inventories)
|
||||
|
@ -145,6 +145,102 @@ func refreshFile() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func parseInventoryCSV(f *os.File) error {
|
||||
setRetainers() // reinitialize retainers from configuration
|
||||
inventories = inventorytools.Inventories{CharacterBags: make([]inventorytools.Item, 0), Crystals: make([]inventorytools.Item, 0), Retainers: retainers}
|
||||
|
||||
r := csv.NewReader(f)
|
||||
data, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file with CSV reader: %v", err)
|
||||
}
|
||||
|
||||
for i, row := range data {
|
||||
/*
|
||||
0 = container
|
||||
1 = slot
|
||||
2 = item id
|
||||
3 = quantity
|
||||
20 = sorted container
|
||||
21 = sorted slot index
|
||||
22 = retainer id
|
||||
*/
|
||||
item := inventorytools.Item{}
|
||||
item.ID, err = strconv.Atoi(row[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert item ID '%s' to int on csv row '%d': %v", row[2], i, err)
|
||||
}
|
||||
item.SortedContainer, err = strconv.Atoi(row[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert sorted container '%s' to int on csv row '%d': %v", row[20], i, err)
|
||||
}
|
||||
item.SortedSlotIndex, err = strconv.Atoi(row[21])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert sorted slot index '%s' to int on csv row '%d': %v", row[21], i, err)
|
||||
}
|
||||
item.Quantity, err = strconv.Atoi(row[3])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert item quantity '%s' to int on csv row '%d': %v", row[3], i, err)
|
||||
}
|
||||
retID, err := strconv.Atoi(row[22])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert retainerID '%s' to int on csv row '%d': %v", row[22], i, err)
|
||||
}
|
||||
|
||||
// 0 - 4 = player bags (pages)
|
||||
// 1000 = player equipped
|
||||
// 2001 = player crystal bag
|
||||
// 3200 - 3209 = armoury chest
|
||||
// 10000 = retainer inventory page 1
|
||||
// 10001 = retainer inventory page 2
|
||||
// 10002 = retainer inventory page 3, etc
|
||||
// 11000 = retainer equipped
|
||||
// 12001 = retainer crystal bag
|
||||
// 12002 = retainer market item
|
||||
containerType, err := strconv.Atoi(row[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert containerType '%s' to int on csv row '%d': %v", row[0], i, err)
|
||||
}
|
||||
|
||||
if retID == characterID { // Handle Character items
|
||||
if containerType < 4 { // player bags
|
||||
inventories.CharacterBags = append(inventories.CharacterBags, item)
|
||||
continue
|
||||
}
|
||||
if containerType == 2001 { // player crystals
|
||||
inventories.Crystals = append(inventories.Crystals, item)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
retMatched := false
|
||||
for i, retainer := range retainers {
|
||||
if retID == retainer.ID {
|
||||
retMatched = true
|
||||
if containerType >= 10000 && containerType <= 10006 { // retainer inventory
|
||||
inventories.Retainers[i].RetainerBags = append(inventories.Retainers[i].RetainerBags, item)
|
||||
break
|
||||
}
|
||||
if containerType == 12002 { // market item
|
||||
inventories.Retainers[i].RetainerMarket = append(inventories.Retainers[i].RetainerMarket, item)
|
||||
break
|
||||
}
|
||||
fmt.Printf("Retainer matched but unhandled container: Container: %s | Slot: %s | Item ID: %s | Quantity: %s | SC: %s | SSI: %s | RET ID: %s\n", row[0], row[1], row[2], row[3], row[20], row[21], row[22])
|
||||
}
|
||||
}
|
||||
if retMatched {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("UNHANDLED ITEM: Container: %s | Slot: %s | Item ID: %s | Quantity: %s | SC: %s | SSI: %s | RET ID: %s\n", row[0], row[1], row[2], row[3], row[20], row[21], row[22])
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
// custom unmarshaller to make sense of the inventories.json file
|
||||
// why the fuck do people use dynamic keys in json instead of structuring their data with static definitions
|
||||
func unmarshalJSON(d []byte) error {
|
||||
|
@ -158,7 +254,7 @@ func unmarshalJSON(d []byte) error {
|
|||
}
|
||||
|
||||
// Unmarshal the character bags and crystals for the set character
|
||||
if _, ok := tmp[strconv.Itoa(characterID)]; !ok {
|
||||
if _, ok := tmp[strconv.Itoa(characterID)]; !ok {x
|
||||
return fmt.Errorf("unmarshaled json map has no key matching character ID '%d'", characterID)
|
||||
}
|
||||
|
||||
|
@ -187,83 +283,4 @@ func unmarshalJSON(d []byte) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watches the downloads directory for autohotkey files and makes sure that only the newest one downloaded exists at a time
|
||||
func downloadsFileWatcher() {
|
||||
downloadsWatcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal("NewWatcher failed: ", err)
|
||||
}
|
||||
defer downloadsWatcher.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer log.Printf("file watcher go routine shutting down!") // could be from returns below?
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
case event, ok := <-downloadsWatcher.Events:
|
||||
if !ok {
|
||||
log.Printf("downloads watcher _event_ NOT OK TERMINATING ARRRGGHGHH: %v", err)
|
||||
return
|
||||
}
|
||||
if event.Op == fsnotify.Create {
|
||||
if strings.Contains(event.Name, ".ahk") {
|
||||
err = cleanDownloadsAHK()
|
||||
if err != nil {
|
||||
log.Printf("failed to clean downloads: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case err, ok := <-downloadsWatcher.Errors:
|
||||
if !ok {
|
||||
log.Printf("downloads watcher _error_ NOT OK TERMINATING ARRRGGHGHH: %v", err)
|
||||
return
|
||||
}
|
||||
log.Printf("downloads watcher error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
err = downloadsWatcher.Add(downloadsFolderPath)
|
||||
if err != nil {
|
||||
log.Fatal("downloads watcher add down failed:", err)
|
||||
}
|
||||
|
||||
<-done
|
||||
}
|
||||
|
||||
func cleanDownloadsAHK() error {
|
||||
err := filepath.Walk(downloadsFolderPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to walk file: %v", err)
|
||||
}
|
||||
|
||||
// ignore files that aren't AHK
|
||||
if !strings.Contains(info.Name(), ".ahk") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ignore files downloaded in the past 5 minutes
|
||||
if info.ModTime().After(time.Now().Add(time.Minute * -1)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("removing old .ahk file - %s", info.Name())
|
||||
err = os.Remove(fmt.Sprintf("%s/%s", downloadsFolderPath, info.Name()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete old .ahk '%s': %v", info.Name(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to walk downloads directory: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue