184 lines
5.6 KiB
Go
184 lines
5.6 KiB
Go
package chatpb
|
|
|
|
// functions to import and export the protobuf
|
|
// data to and from config files
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"go.wit.com/lib/protobuf/bugpb"
|
|
"go.wit.com/log"
|
|
)
|
|
|
|
// write to ~/.config/regex/ unless ENV{REGEX_HOME} is set
|
|
func (all *Chats) ConfigSave() error {
|
|
if os.Getenv("REGEX_HOME") == "" {
|
|
homeDir, _ := os.UserHomeDir()
|
|
fullpath := filepath.Join(homeDir, ".config/regex")
|
|
os.Setenv("REGEX_HOME", fullpath)
|
|
}
|
|
if all == nil {
|
|
log.Warn("chatpb all == nil")
|
|
return errors.New("chatpb.ConfigSave() all == nil")
|
|
}
|
|
|
|
// --- Start of Fix ---
|
|
// Create a new, clean Chats object to avoid marshaling a slice with nil entries.
|
|
cleanChats := NewChats() // Assuming NewChats() initializes the struct correctly.
|
|
cleanChats.Uuid = all.Uuid
|
|
cleanChats.Version = all.Version
|
|
|
|
// Loop through the original chats and append only the non-nil ones.
|
|
for _, chat := range all.GetChats() {
|
|
if chat != nil {
|
|
cleanChats.Chats = append(cleanChats.Chats, chat)
|
|
} else {
|
|
log.Warn("Found and skipped a nil chat entry during ConfigSave")
|
|
}
|
|
}
|
|
// --- End of Fix ---
|
|
|
|
data, err := cleanChats.Marshal() // Marshal the clean object, not 'all'
|
|
if err != nil {
|
|
log.Info("chatpb proto.Marshal() failed len", len(data), err)
|
|
// The tryValidate logic might be less necessary now but kept for safety.
|
|
if err := all.tryValidate(); err != nil {
|
|
return err
|
|
} else {
|
|
data, err = cleanChats.Marshal() // Retry with the clean object
|
|
if err == nil {
|
|
log.Info("chatpb.ConfigSave() pb.Marshal() worked after validation len", len(cleanChats.Chats), "chats")
|
|
configWrite("regex.pb", data)
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// --- Backup Logic ---
|
|
filename := filepath.Join(os.Getenv("REGEX_HOME"), "regex.pb")
|
|
if _, err := os.Stat(filename); err == nil {
|
|
// File exists, so back it up.
|
|
dir := filepath.Dir(filename)
|
|
timestamp := time.Now().Format("20060102-150405")
|
|
backupFilename := fmt.Sprintf("regex.%s.pb", timestamp)
|
|
backupPath := filepath.Join(dir, backupFilename)
|
|
if err := os.Rename(filename, backupPath); err != nil {
|
|
log.Warn("Could not backup config file:", err)
|
|
}
|
|
}
|
|
// --- End Backup Logic ---
|
|
|
|
if err := configWrite("regex.pb", data); err != nil {
|
|
log.Infof("chatpb.ConfigSave() failed len(Chats)=%d bytes=%d", len(cleanChats.Chats), len(data))
|
|
return err
|
|
}
|
|
configWrite("regex.text", []byte(cleanChats.FormatTEXT()))
|
|
// log.Infof("chatpb.ConfigSave() worked len(Chats)=%d bytes=%d", len(cleanChats.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 regex.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("REGEX_HOME") == "" {
|
|
homeDir, _ := os.UserHomeDir()
|
|
fullpath := filepath.Join(homeDir, ".config/regex")
|
|
os.Setenv("REGEX_HOME", fullpath)
|
|
}
|
|
var data []byte
|
|
var err error
|
|
|
|
cfgname := filepath.Join(os.Getenv("REGEX_HOME"), "regex.pb")
|
|
if data, err = loadFile(cfgname); err != nil {
|
|
// something went wrong loading the file
|
|
// all.sampleConfig() // causes nil panic
|
|
return err
|
|
}
|
|
// this means the regex.pb file exists and was read
|
|
if len(data) == 0 {
|
|
// todo: add default data here since it's blank? // might cause nil panic?
|
|
all.AddRegexComment("I like astronomy")
|
|
log.Info(errors.New("chatpb.ConfigLoad() regex.pb is empty"))
|
|
return nil
|
|
}
|
|
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 "regex.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("REGEX_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 == "regex.text" {
|
|
// add header
|
|
cfgfile.Write([]byte("# this file is automatically re-generated from regex.pb, however,\n"))
|
|
cfgfile.Write([]byte("# if you want to edit it by hand, you can:\n"))
|
|
cfgfile.Write([]byte("# stop regex; remove regex.pb; edit regex.text; start regex\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
|
|
}
|