summaryrefslogtreecommitdiff
path: root/config.go
blob: b5533a0c25dd8dc8a7b5e627d2abf6b7c95e4b00 (plain)
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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/gui/shell"
	"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")
	}
	log.Info("DOING ConfigSave()")

	// --- 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 := cleanChats.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")
	filebackup := filepath.Join(os.Getenv("REGEX_HOME"), "regex.backup.pb")
	if s, err := os.Stat(filebackup); err == nil {
		log.Info("STAT OF CONFIG BACKUP WORKED", filebackup)
		age := time.Since(s.ModTime())
		if age > 10*time.Second {
			log.Info(filebackup, "is greater than 10 minutes")
			shell.RunVerbose([]string{"cp", filename, filebackup})
		} else {
			log.Info(filebackup, "is less than 10 minutes")
		}
	} else {
		log.Info("STAT OF CONFIG BACKUP FAILED", filebackup)
	}
	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
	}
	cfgfile.Write(data)
	return nil
}