44 "encoding/json"
55 "fmt"
66 "os"
7+ "time"
8+
9+ "github.com/reugn/gemini-cli/gemini"
710)
811
912// Configuration contains the details of the application configuration.
@@ -14,9 +17,12 @@ type Configuration struct {
1417 // Data is the application data. This data is loaded from the configuration
1518 // file and is used to configure the application.
1619 Data * ApplicationData
20+ // lastModTime is the time the configuration file was last modified.
21+ lastModTime time.Time
1722}
1823
1924// NewConfiguration returns a new Configuration from a JSON file.
25+ // If the file does not exist, it is created with default values.
2026func NewConfiguration (filePath string ) (* Configuration , error ) {
2127 configuration := & Configuration {
2228 filePath : filePath ,
@@ -34,28 +40,108 @@ func NewConfiguration(filePath string) (*Configuration, error) {
3440 defer file .Close ()
3541
3642 decoder := json .NewDecoder (file )
37- err = decoder .Decode (configuration .Data )
38- if err != nil {
43+ if err := decoder .Decode (configuration .Data ); err != nil {
3944 return nil , fmt .Errorf ("error decoding JSON: %w" , err )
4045 }
4146
4247 return configuration , nil
4348}
4449
4550// Flush serializes and writes the configuration to the file.
51+ //
52+ // If the file is modified since the last load/flush, the configuration is re-read
53+ // and merged with the on-disk data.
4654func (c * Configuration ) Flush () error {
55+ // Reload the configuration if the file was modified since the last load/flush.
56+ if err := c .reloadIfStale (); err != nil {
57+ return err
58+ }
59+
60+ // Create the file if it does not exist.
4761 file , err := os .Create (c .filePath )
4862 if err != nil {
4963 return fmt .Errorf ("error opening file: %w" , err )
5064 }
5165 defer file .Close ()
5266
67+ // Serialize the configuration data to the file.
5368 encoder := json .NewEncoder (file )
5469 encoder .SetIndent ("" , " " )
55- err = encoder .Encode (c .Data )
56- if err != nil {
70+ if err := encoder .Encode (c .Data ); err != nil {
5771 return fmt .Errorf ("error encoding JSON: %w" , err )
5872 }
5973
60- return file .Sync ()
74+ // Sync the file to disk.
75+ if err := file .Sync (); err != nil {
76+ return fmt .Errorf ("error syncing file: %w" , err )
77+ }
78+
79+ // Get the file information.
80+ info , err := os .Stat (c .filePath )
81+ if err != nil {
82+ return fmt .Errorf ("error stating file: %w" , err )
83+ }
84+
85+ // Update the last modified time.
86+ c .lastModTime = info .ModTime ()
87+
88+ return nil
89+ }
90+
91+ // reloadIfStale re-reads and merges the on-disk configuration if the file was
92+ // modified since the last load/flush.
93+ func (c * Configuration ) reloadIfStale () error {
94+ info , err := os .Stat (c .filePath )
95+ if err != nil {
96+ if os .IsNotExist (err ) { // ignore error if file does not exist
97+ return nil
98+ }
99+ return fmt .Errorf ("error stating config file: %w" , err )
100+ }
101+
102+ // If the file was not modified since the last load/flush, do nothing.
103+ if ! c .lastModTime .IsZero () && ! info .ModTime ().After (c .lastModTime ) {
104+ return nil
105+ }
106+
107+ // Re-read the configuration file.
108+ file , err := os .Open (c .filePath )
109+ if err != nil {
110+ return fmt .Errorf ("error reopening config file: %w" , err )
111+ }
112+ defer file .Close ()
113+
114+ onDisk := newDefaultApplicationData ()
115+ if err := json .NewDecoder (file ).Decode (onDisk ); err != nil {
116+ return fmt .Errorf ("error decoding updated config: %w" , err )
117+ }
118+
119+ // Merge the on-disk data into the current configuration.
120+ c .mergeApplicationData (onDisk )
121+
122+ return nil
123+ }
124+
125+ // mergeApplicationData merges on-disk data into the current configuration.
126+ func (c * Configuration ) mergeApplicationData (onDisk * ApplicationData ) {
127+ if onDisk == nil || c .Data == nil {
128+ return
129+ }
130+
131+ // The CLI never modifies these fields; always overwrite with the on-disk values.
132+ c .Data .SystemPrompts = onDisk .SystemPrompts
133+ c .Data .SafetySettings = onDisk .SafetySettings
134+ c .Data .Tools = onDisk .Tools
135+
136+ // Merge history records.
137+ if onDisk .History != nil {
138+ if c .Data .History == nil {
139+ c .Data .History = make (map [string ][]* gemini.SerializableContent , len (onDisk .History ))
140+ }
141+ for label , records := range onDisk .History {
142+ if _ , exists := c .Data .History [label ]; ! exists {
143+ c .Data .History [label ] = records
144+ }
145+ }
146+ }
61147}
0 commit comments