@@ -29,7 +29,10 @@ func NewUpdateChecker(versionClient VersionClient) (UpdateChecker, error) {
29
29
return nil , fmt .Errorf ("unable to access update file path %w" , err )
30
30
}
31
31
32
- // Check to see if the file already exists. Read the instance ID from the
32
+ // Get component name for component-specific data
33
+ component := getComponentFromVersionClient (versionClient )
34
+
35
+ // Check to see if the file already exists. Read the instance ID and component-specific data from the
33
36
// file if it does. If it doesn't exist, create a new instance ID.
34
37
var instanceID , previousVersion string
35
38
// #nosec G304: File path is not configurable at this time.
@@ -56,6 +59,7 @@ func NewUpdateChecker(versionClient VersionClient) (UpdateChecker, error) {
56
59
updateFilePath : path ,
57
60
versionClient : versionClient ,
58
61
previousAPIResponse : previousVersion ,
62
+ component : component ,
59
63
}, nil
60
64
}
61
65
@@ -64,10 +68,16 @@ const (
64
68
updateInterval = 4 * time .Hour
65
69
)
66
70
71
+ // componentInfo represents component-specific update timing information.
72
+ type componentInfo struct {
73
+ LastCheck time.Time `json:"last_check"`
74
+ }
75
+
67
76
// updateFile represents the structure of the update file.
68
77
type updateFile struct {
69
- InstanceID string `json:"instance_id"`
70
- LatestVersion string `json:"latest_version"`
78
+ InstanceID string `json:"instance_id"`
79
+ LatestVersion string `json:"latest_version"`
80
+ Components map [string ]componentInfo `json:"components"`
71
81
}
72
82
73
83
type defaultUpdateChecker struct {
@@ -76,43 +86,67 @@ type defaultUpdateChecker struct {
76
86
previousAPIResponse string
77
87
updateFilePath string
78
88
versionClient VersionClient
89
+ component string
79
90
}
80
91
81
92
func (d * defaultUpdateChecker ) CheckLatestVersion () error {
82
- // Check if the update file exists.
83
- // Ignore the error if the file doesn't exist - we'll create it later.
84
- fileInfo , err := os .Stat (d .updateFilePath )
93
+ // Read the current update file to get component-specific data
94
+ var currentFile updateFile
95
+ // #nosec G304: File path is not configurable at this time.
96
+ rawContents , err := os .ReadFile (d .updateFilePath )
85
97
if err != nil && ! os .IsNotExist (err ) {
86
- return fmt .Errorf ("failed to stat update file: %w" , err )
98
+ return fmt .Errorf ("failed to read update file: %w" , err )
99
+ }
100
+
101
+ // Initialize file structure if it doesn't exist or is empty
102
+ if os .IsNotExist (err ) || len (rawContents ) == 0 {
103
+ currentFile = updateFile {
104
+ InstanceID : d .instanceID ,
105
+ Components : make (map [string ]componentInfo ),
106
+ }
107
+ } else {
108
+ if err := json .Unmarshal (rawContents , & currentFile ); err != nil {
109
+ return fmt .Errorf ("failed to deserialize update file: %w" , err )
110
+ }
111
+
112
+ // Initialize components map if it doesn't exist (for backward compatibility)
113
+ if currentFile .Components == nil {
114
+ currentFile .Components = make (map [string ]componentInfo )
115
+ }
116
+
117
+ // Use the instance ID from file, but fallback to the one we generated
118
+ if currentFile .InstanceID == "" {
119
+ currentFile .InstanceID = d .instanceID
120
+ }
87
121
}
88
122
89
- // Check if we need to make an API request based on file modification time.
90
- if fileInfo != nil && time .Since (fileInfo .ModTime ()) < updateInterval {
91
- // If it is too soon - notify the user if we already know there is
92
- // an update, then exit.
93
- notifyIfUpdateAvailable (d .currentVersion , d .previousAPIResponse )
94
- return nil
123
+ // Check component-specific timing
124
+ if componentData , exists := currentFile .Components [d .component ]; exists {
125
+ if time .Since (componentData .LastCheck ) < updateInterval {
126
+ // If it is too soon - notify the user if we already know there is
127
+ // an update, then exit.
128
+ notifyIfUpdateAvailable (d .currentVersion , currentFile .LatestVersion )
129
+ return nil
130
+ }
95
131
}
96
132
97
- // If the update file is stale or does not exist - get the latest version
133
+ // If the component data is stale or does not exist - get the latest version
98
134
// from the API.
99
- latestVersion , err := d .versionClient .GetLatestVersion (d . instanceID , d .currentVersion )
135
+ latestVersion , err := d .versionClient .GetLatestVersion (currentFile . InstanceID , d .currentVersion )
100
136
if err != nil {
101
137
return fmt .Errorf ("failed to check for updates: %w" , err )
102
138
}
103
139
104
140
notifyIfUpdateAvailable (d .currentVersion , latestVersion )
105
141
106
- // Rewrite the update file with the latest result.
107
- // If we really wanted to optimize this, we could skip updating the file
108
- // if the version is the same as the current version, but update the
109
- // modification time.
110
- newFileContents := updateFile {
111
- InstanceID : d .instanceID ,
112
- LatestVersion : latestVersion ,
142
+ // Update shared latest version and component-specific timing
143
+ currentFile .LatestVersion = latestVersion
144
+ currentFile .Components [d .component ] = componentInfo {
145
+ LastCheck : time .Now ().UTC (),
113
146
}
114
147
115
- updatedData , err := json .Marshal (newFileContents )
148
+ // Write the updated file
149
+ updatedData , err := json .Marshal (currentFile )
116
150
if err != nil {
117
151
return fmt .Errorf ("failed to marshal updated data: %w" , err )
118
152
}
@@ -124,6 +158,11 @@ func (d *defaultUpdateChecker) CheckLatestVersion() error {
124
158
return nil
125
159
}
126
160
161
+ // getComponentFromVersionClient extracts the component name from a VersionClient.
162
+ func getComponentFromVersionClient (versionClient VersionClient ) string {
163
+ return versionClient .GetComponent ()
164
+ }
165
+
127
166
func notifyIfUpdateAvailable (current , latest string ) {
128
167
// Print a meaningful message for people running local builds.
129
168
if strings .HasPrefix (current , "build-" ) {
0 commit comments