summaryrefslogtreecommitdiff
path: root/loadRaw.go
diff options
context:
space:
mode:
authorJeff Carr <[email protected]>2025-10-20 05:50:38 -0500
committerJeff Carr <[email protected]>2025-10-20 05:50:38 -0500
commit8a24584262a90956e012cee7b03c8c2b9f4b794c (patch)
tree8b7d0afd04e717d7c07da5eb2894debd74ec2307 /loadRaw.go
parentb6e93c08d601a7a6c27a0fdcdf98f6cb7dc9ccd8 (diff)
reworking this to make it more sane. hopefully.
Diffstat (limited to 'loadRaw.go')
-rw-r--r--loadRaw.go351
1 files changed, 351 insertions, 0 deletions
diff --git a/loadRaw.go b/loadRaw.go
new file mode 100644
index 0000000..c09b3f5
--- /dev/null
+++ b/loadRaw.go
@@ -0,0 +1,351 @@
+package config
+
+// functions to import and export the protobuf
+// data to and from config files
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "go.wit.com/log"
+ "google.golang.org/protobuf/encoding/protojson"
+ "google.golang.org/protobuf/encoding/prototext"
+ "google.golang.org/protobuf/proto"
+)
+
+/*
+// loads a file from ~/.config/<appname>/
+func Load(appname string) ([]byte, string) {
+}
+*/
+
+var ErrEmpty error = fmt.Errorf("config file was empty")
+var ErrMarshal error = fmt.Errorf("protobuf parse error")
+
+// returns:
+// - Full path to the config file. usually: ~/.config/<appname>
+// - []byte : the contents of the file
+// - error on read
+func ConfigLoadRaw(pb proto.Message, appname string, protoname string) error {
+ // Get ~/.config/appname/protoname.text
+ fullname := GetConfigFilename(appname, protoname)
+
+ var pbFilenameSupport bool
+ var err error
+ curfilename, err := GetFilename(pb)
+ if err == nil {
+ pbFilenameSupport = true
+ } else {
+ // this .proto doesn't have the Filename variable/message
+ }
+
+ if err = loadTEXT(pb, fullname); err == nil {
+ if pbFilenameSupport {
+ // If the config is old or broken, this sets the filename
+ if curfilename != fullname {
+ _, err := SetFilename(pb, fullname)
+ if err != nil {
+ log.Info("FILENAME COULD NOT BE SET old=", curfilename)
+ log.Info("FILENAME COULD NOT BE SET new=", fullname)
+ panic("something is wrong in lib/config")
+ }
+ }
+ }
+ return nil
+ } else {
+ if strings.HasSuffix(fullname, ".text") {
+ fulljson := fullname + ".json"
+ // If the config is old or broken, this sets the filename
+ if err := loadJSON(pb, fulljson); err == nil {
+ if pbFilenameSupport {
+ if curfilename != fullname {
+ _, err := SetFilename(pb, fullname)
+ if err != nil {
+ log.Info("FILENAME COULD NOT BE SET old=", curfilename)
+ log.Info("FILENAME COULD NOT BE SET new=", fullname)
+ panic("something is wrong in lib/config")
+ }
+ }
+ }
+ return nil
+ }
+ }
+ }
+ return ErrMarshal
+}
+
+// returns the default constructed filename:
+// ~/.config/appname/protoname.text
+func GetConfigFilename(appname string, protoname string) string {
+ var err error
+ configDir, err := os.UserConfigDir()
+ if err != nil {
+ // todo: get something better than /tmp/ if anyone cares
+ return filepath.Join("/tmp", appname, protoname+".text")
+ }
+ return filepath.Join(configDir, appname, protoname+".text")
+}
+
+// loads from the users .cache dir
+// if the .proto file version changes, automatically delete the .pb
+// file. This is important to avoid marshalling garbage data
+// .cache files are treated as such, a "cache" file. don't keep important
+// things in here. argv stores the information here for autodelete
+func LoadCache(pb proto.Message, appname string, protoname string) error {
+ cacheDir, _ := os.UserCacheDir()
+ fullpath := filepath.Join(cacheDir, appname)
+ os.MkdirAll(fullpath, os.ModePerm)
+ fullname := filepath.Join(fullpath, protoname+".pb")
+ _, err := SetFilename(pb, fullname)
+ if err != nil {
+ pb = nil
+ os.Remove(fullname)
+ return err
+ }
+ newver, curver, err := LoadVersionCheckPB(pb)
+ if err != nil {
+ pb = nil
+ os.Remove(fullname)
+ return err
+ }
+ _, _ = newver, curver
+ return nil
+}
+
+func LoadVersionCheckPB(pb proto.Message) (string, string, error) {
+ var newver string
+ var pbver string
+ var err error
+
+ fullname, err := GetFilename(pb)
+ if err != nil {
+ return newver, pbver, err
+ }
+ // text is supposed to be "easy". Don't verify 'version'
+ if strings.HasSuffix(fullname, ".text") {
+ err = loadTEXT(pb, fullname)
+ return newver, pbver, err
+ }
+
+ // verify 'version' for .pb files
+ // application should die if they don't match
+ var worked bool
+ newver, err = GetString(pb, "version")
+ if err != nil {
+ return newver, pbver, err
+ }
+ // maybe don't really verify .json files (?)
+ // doing it for now anyway. maybe just return an error
+ if strings.HasSuffix(fullname, ".json") {
+ if err = loadJSON(pb, fullname); err != nil {
+ return newver, pbver, err
+ }
+ worked = true
+ }
+ if strings.HasSuffix(fullname, ".pb") {
+ if err = loadPB(pb, fullname); err != nil {
+ return newver, pbver, err
+ }
+ _, err = SetFilename(pb, fullname)
+ if err != nil {
+ return newver, pbver, err
+ }
+ worked = true
+ }
+ if !worked {
+ return newver, pbver, fmt.Errorf("unknown filetype '%s'", fullname)
+ }
+ // get the version from the current PB saved on disk
+ pbver, _ = GetString(pb, "version")
+ if newver != pbver {
+ return newver, pbver, VersionMismatch
+ }
+ return newver, pbver, nil
+}
+
+// uses the version to die. This is needed because loading binary
+// protobuf files with rearranged messages is indeterminate
+func LoadPB(pb proto.Message) error {
+ fullname, err := GetFilename(pb)
+ if fullname == "" {
+ panic("config.LoadPB() got blank filename = ''")
+ }
+ if err != nil {
+ return err
+ }
+ // this code needs work
+ newver, pbver, err := LoadVersionCheckPB(pb)
+ if errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ if errors.Is(err, VersionMismatch) || (newver != pbver) {
+ fmt.Println("")
+ fmt.Printf("VERSION new '%s' != cur PB '%s'\n", newver, pbver)
+ fmt.Println("")
+ fmt.Println("Your protobuf file is old and can not be loaded")
+ fmt.Println("your application must decide how to handle this (delete or fix)")
+ fmt.Println("always die here. application is broken")
+ fmt.Println("You must delete or convert the file", fullname)
+ fmt.Println("")
+ // probably should ALWAYS PANIC HERE
+ // upon further study, always die here is better than not
+ s := fmt.Sprintf("protobuf version wrong. delete or fix %s", fullname)
+ panic(s)
+ }
+ if err != nil {
+ // return to let the application figure this out
+ return err
+ }
+ return nil
+}
+
+func LoadFromFilename(pb proto.Message, fullname string) error {
+ return LoadFile(pb, fullname)
+}
+
+func LoadFile(pb proto.Message, fullname string) error {
+ if strings.HasSuffix(fullname, ".text") {
+ return loadTEXT(pb, fullname)
+ }
+ if strings.HasSuffix(fullname, ".json") {
+ return loadJSON(pb, fullname)
+ }
+ if strings.HasSuffix(fullname, ".pb") {
+ return loadPB(pb, fullname)
+ }
+
+ return fmt.Errorf("unknown filetype '%s'", fullname)
+}
+
+func loadPB(pb proto.Message, fullname string) error {
+ data, err := loadFile(fullname)
+ if err != nil {
+ // set pb.Filename that was attempted
+ return err
+ }
+
+ if err = proto.Unmarshal(data, pb); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func LoadConfigPB(pb proto.Message, appname string, protoname string) (string, error) {
+ var fullname string
+ if strings.HasPrefix(appname, "/") {
+ fullname = filepath.Join(appname, protoname+".pb")
+ } else {
+ configDir, err := os.UserConfigDir()
+ if err != nil {
+ return "", err
+ }
+
+ fullname = filepath.Join(configDir, appname, protoname+".pb")
+ }
+
+ data, err := loadFile(fullname)
+ if err != nil {
+ return fullname, err
+ }
+
+ // Unmarshal()
+ if err = proto.Unmarshal(data, pb); err != nil {
+ return fullname, err
+ }
+
+ return fullname, nil
+}
+
+func loadTEXT(pb proto.Message, fullname string) error {
+ var data []byte
+ var err error
+ SetFilename(pb, fullname)
+ if data, err = loadFile(fullname); err != nil {
+ return err
+ }
+
+ // don't even bother with Marshal()
+ if data == nil {
+ return ErrEmpty // file is empty
+ }
+
+ // Unmarshal()
+ if err = prototext.Unmarshal(data, pb); err != nil {
+ return ErrMarshal
+ }
+
+ if fn, err := GetFilename(pb); err != nil {
+ if fn != fullname {
+ SetFilename(pb, fullname)
+ }
+ }
+ return nil
+}
+
+// json files are backup Marshal() data in case .text Unmarshal() fails
+// they always should have the ".text" filename in them
+func loadJSON(pb proto.Message, fullname string) error {
+ var data []byte
+ var err error
+ if data, err = loadFile(fullname); err != nil {
+ return err
+ }
+
+ // don't even bother with Marshal()
+ if data == nil {
+ return ErrEmpty // file is empty
+ }
+
+ // Unmarshal()
+ if err = protojson.Unmarshal(data, pb); err != nil {
+ return ErrMarshal
+ }
+
+ if fn, err := GetFilename(pb); err != nil {
+ if fn != fullname {
+ SetFilename(pb, fullname)
+ }
+ }
+ return nil
+}
+
+/* left this here to remind myself just how dumb I can be
+// dumb but simple to read logic
+func missingConfig(fullname string) error {
+ data1, err1 := os.ReadFile(fullname)
+ if !errors.Is(err1, os.ErrNotExist) {
+ return err1
+ }
+
+ data2, err2 := os.ReadFile(fullname + ".json")
+ if !errors.Is(err2, os.ErrNotExist) {
+ return err2
+ }
+ if errors.Is(err1, os.ErrNotExist) && errors.Is(err2, os.ErrNotExist) {
+ return os.ErrNotExist
+ }
+ if (len(data1) == 0) && (len(data2) == 0) {
+ return ErrEmpty
+ }
+ return nil
+}
+*/
+
+func loadFile(fullname string) ([]byte, error) {
+ data, err := os.ReadFile(fullname)
+ if errors.Is(err, os.ErrNotExist) {
+ // if file does not exist, just return nil. this
+ return nil, err
+ }
+ if err != nil {
+ return nil, err
+ }
+ if len(data) == 0 {
+ return data, ErrEmpty
+ }
+ return data, nil
+}