diff options
| author | Jeff Carr <[email protected]> | 2025-10-20 05:50:38 -0500 |
|---|---|---|
| committer | Jeff Carr <[email protected]> | 2025-10-20 05:50:38 -0500 |
| commit | 8a24584262a90956e012cee7b03c8c2b9f4b794c (patch) | |
| tree | 8b7d0afd04e717d7c07da5eb2894debd74ec2307 /loadRaw.go | |
| parent | b6e93c08d601a7a6c27a0fdcdf98f6cb7dc9ccd8 (diff) | |
reworking this to make it more sane. hopefully.
Diffstat (limited to 'loadRaw.go')
| -rw-r--r-- | loadRaw.go | 351 |
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 +} |
