1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
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?
all.AddGeminiComment("I like astronomy")
log.Info(errors.New("chatpb.ConfigLoad() gemini.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 "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
}
|