summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Carr <[email protected]>2025-08-21 14:18:55 -0500
committerJeff Carr <[email protected]>2025-08-21 14:18:55 -0500
commitd7dec7865ad74d4ffe6f56aaf27906ea1a52171d (patch)
tree950e650eb3151ecd798b51dccf627b47fc7de080
parent6e40b1bab429c042d4310ad8d945e97a64957d1b (diff)
make ConfigSave() ConfigLoad()
-rw-r--r--config.go150
-rw-r--r--example.go22
2 files changed, 172 insertions, 0 deletions
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..0db21b1
--- /dev/null
+++ b/config.go
@@ -0,0 +1,150 @@
+package chatpb
+
+// functions to import and export the protobuf
+// data to and from config files
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+
+ "go.wit.com/lib/protobuf/bugpb"
+ "go.wit.com/log"
+)
+
+// write to ~/.config/gemini/ unless ENV{GEMINI_HOME} is set
+func (all *Chats) ConfigSave() error {
+ if os.Getenv("GEMINI_HOME") == "" {
+ homeDir, _ := os.UserHomeDir()
+ fullpath := filepath.Join(homeDir, ".config/gemini")
+ os.Setenv("GEMINI_HOME", fullpath)
+ }
+ if all == nil {
+ log.Warn("chatpb all == nil")
+ return errors.New("chatpb.ConfigSave() all == nil")
+ }
+
+ data, err := all.Marshal()
+ if err != nil {
+ log.Info("chatpb proto.Marshal() failed len", len(data), err)
+ // often this is because strings have invalid UTF-8. This should probably be fixed in the protobuf code
+ if err := all.tryValidate(); err != nil {
+ return err
+ } else {
+ // re-attempt Marshal() here
+ data, err = all.Marshal()
+ if err == nil {
+ // validate & sanitize strings worked
+ log.Info("chatpb.ConfigSave() pb.Marshal() worked len", len(all.Chats), "chats")
+ configWrite("gemini.pb", data)
+ return nil
+ }
+ }
+ return err
+ }
+ if err := configWrite("gemini.pb", data); err != nil {
+ log.Infof("chatpb.ConfigSave() failed len(Chats)=%d bytes=%d", len(all.Chats), len(data))
+ return err
+ }
+ configWrite("gemini.text", []byte(all.FormatTEXT()))
+ log.Infof("chatpb.ConfigSave() worked len(Chats)=%d bytes=%d", len(all.Chats), len(data))
+ return nil
+}
+
+func (all *Chats) tryValidate() error {
+
+ err := bugpb.ValidateProtoUTF8(all)
+ if err != nil {
+ log.Printf("Protobuf UTF-8 validation failed: %v\n", err)
+ }
+ if err := bugpb.SanitizeProtoUTF8(all); err != nil {
+ log.Warn("Sanitation failed:", err)
+ // log.Fatalf("Sanitization failed: %v", err)
+ return err
+ }
+ return nil
+}
+
+// load the gemini.pb file. I shouldn't really matter if this
+// fails. the file should be autogenerated. This is used
+// locally just for speed
+func (all *Chats) ConfigLoad() error {
+ if os.Getenv("GEMINI_HOME") == "" {
+ homeDir, _ := os.UserHomeDir()
+ fullpath := filepath.Join(homeDir, ".config/gemini")
+ os.Setenv("GEMINI_HOME", fullpath)
+ }
+ var data []byte
+ var err error
+
+ cfgname := filepath.Join(os.Getenv("GEMINI_HOME"), "gemini.pb")
+ if data, err = loadFile(cfgname); err != nil {
+ // something went wrong loading the file
+ // all.sampleConfig() // causes nil panic
+ return err
+ }
+ // this means the gemini.pb file exists and was read
+ if len(data) == 0 {
+ // todo: add default data here since it's blank? // might cause nil panic?
+ return errors.New("chatpb.ConfigLoad() gemini.pb is empty")
+ }
+ err = all.Unmarshal(data)
+ test := NewChats()
+ if test.Uuid != all.Uuid {
+ log.Warn("uuids do not match", test.Uuid, all.Uuid)
+ deleteProtobufFile(cfgname)
+ }
+ if test.Version != all.Version {
+ log.Warn("versions do not match", test.Version, all.Version)
+ deleteProtobufFile(cfgname)
+ }
+ log.Info(cfgname, "protobuf versions and uuid match", all.Uuid, all.Version)
+ return err
+}
+
+func deleteProtobufFile(filename string) {
+ log.Warn("The protobuf file format has changed for", filename)
+ log.Warn("Deleting old file:", filename)
+ log.Warn("This file will be recreated on the next run.")
+ err := os.Remove(filename)
+ if err != nil {
+ log.Warn("failed to remove old protobuf file", "err", err)
+ }
+}
+
+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
+ // will cause ConfigLoad() to try the next config file like "gemini.text"
+ // because the user might want to edit the .config by hand
+ return nil, nil
+ }
+ if err != nil {
+ // log.Info("open config file :", err)
+ return nil, err
+ }
+ return data, nil
+}
+
+func configWrite(filename string, data []byte) error {
+ fullname := filepath.Join(os.Getenv("GEMINI_HOME"), filename)
+
+ cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+ defer cfgfile.Close()
+ if err != nil {
+ log.Warn("open config file :", err)
+ return err
+ }
+ if filename == "gemini.text" {
+ // add header
+ cfgfile.Write([]byte("# this file is automatically re-generated from gemini.pb, however,\n"))
+ cfgfile.Write([]byte("# if you want to edit it by hand, you can:\n"))
+ cfgfile.Write([]byte("# stop gemini; remove gemini.pb; edit gemini.text; start gemini\n"))
+ cfgfile.Write([]byte("# this will cause the default behavior to fallback to parsing this file for the config\n"))
+ cfgfile.Write([]byte("\n"))
+ cfgfile.Write([]byte("# this file is intended to be used to customize settings on what\n"))
+ }
+ cfgfile.Write(data)
+ return nil
+}
diff --git a/example.go b/example.go
new file mode 100644
index 0000000..d22f39c
--- /dev/null
+++ b/example.go
@@ -0,0 +1,22 @@
+package chatpb
+
+import (
+ "go.wit.com/log"
+)
+
+func ExampleChat() *Chats {
+ conversation := NewChats()
+
+ t := conversation.AddTable()
+ t.AddRow([]string{"apple", "pear"})
+
+ conversation.AddGeminiComment("funny")
+ conversation.AddUserComment("yes")
+
+ conversation.AddGeminiComment("I like astronomy")
+
+ dump := conversation.FormatTEXT()
+
+ log.Println(dump)
+ return conversation
+}