KiCad PCB EDA Suite
Loading...
Searching...
No Matches
update_manager.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2023 Mark Roszko <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <update_manager.h>
22#include <pgm_base.h>
23
24#include <string>
25#include <sstream>
26
30
33#include <progress_reporter.h>
34
36
37#include <json_common.h>
39
40#include <wx/log.h>
41#include <wx/event.h>
42#include <wx/filefn.h>
43#include <wx/translation.h>
44#include <wx/notifmsg.h>
45
47
48#include <thread_pool.h>
49
50#include <build_version.h>
51
52
54{
55 wxString platform;
56 wxString arch;
58 wxString lang;
59 wxString last_check;
60};
61
62
63NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( UPDATE_REQUEST, platform, arch, current_version, lang,
64 last_check )
65
66struct UPDATE_RESPONSE
67{
68 wxString version;
69 wxString release_date;
70 wxString details_url;
71 wxString downloads_url;
72};
73
74NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( UPDATE_RESPONSE, version, release_date, details_url,
75 downloads_url )
76
77#define UPDATE_QUERY_ENDPOINT wxS( "https://downloads.kicad.org/api/v1/update" )
78
79
80UPDATE_MANAGER::UPDATE_MANAGER() : m_working( false )
81{
82}
83
84
86{
88 {
89 if( m_updateBackgroundJob->m_reporter )
90 m_updateBackgroundJob->m_reporter->Cancel();
91
92 if( m_updateTask.valid() )
93 m_updateTask.wait();
94 }
95}
96
97
98int UPDATE_MANAGER::PostRequest( const wxString& aUrl, std::string aRequestBody,
99 std::ostream* aOutput, PROGRESS_REPORTER* aReporter,
100 const size_t aSizeLimit )
101{
102 bool size_exceeded = false;
103
104 TRANSFER_CALLBACK callback = [&]( size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow )
105 {
106 if( aSizeLimit > 0 && ( dltotal > aSizeLimit || dlnow > aSizeLimit ) )
107 {
108 size_exceeded = true;
109
110 // Non zero return means abort.
111 return true;
112 }
113
114 if( aReporter )
115 {
116 if( dltotal > 1000 )
117 {
118 aReporter->SetCurrentProgress( dlnow / (double) dltotal );
119 aReporter->Report( wxString::Format( _( "Downloading %lld/%lld kB" ), dlnow / 1000,
120 dltotal / 1000 ) );
121 }
122 else
123 {
124 if( aReporter )
125 aReporter->SetCurrentProgress( 0.0 );
126 }
127
128 return !aReporter->KeepRefreshing();
129 }
130 else
131 return false;
132 };
133
134 KICAD_CURL_EASY curl;
135 curl.SetHeader( "Accept", "application/json" );
136 curl.SetHeader( "Content-Type", "application/json" );
137 curl.SetHeader( "charset", "utf-8" );
138 curl.SetOutputStream( aOutput );
139 curl.SetURL( aUrl.ToUTF8().data() );
140 curl.SetPostFields( aRequestBody );
141 curl.SetFollowRedirects( true );
142 curl.SetTransferCallback( callback, 250000L );
143 curl.SetTimeout( 30L );
144
145 int code = curl.Perform();
146
147 if( aReporter && !aReporter->IsCancelled() )
148 aReporter->SetCurrentProgress( 1.0 );
149
150 if( code != CURLE_OK )
151 {
152 if( aReporter )
153 {
154 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
155 aReporter->Report( _( "Download is too large." ) );
156 else if( code != CURLE_ABORTED_BY_CALLBACK )
157 aReporter->Report( wxString( curl.GetErrorText( code ) ) );
158 }
159
160 return 0;
161 }
162
163 return curl.GetResponseStatusCode();
164}
165
166
167void UPDATE_MANAGER::CheckForUpdate( wxWindow* aNoticeParent )
168{
169 if( m_working.exchange( true ) )
170 return;
171
172 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "Update Check" ) );
173
174 auto update_check = [aNoticeParent, this]() -> void
175 {
176 std::shared_ptr<BACKGROUND_JOB_REPORTER> reporter = m_updateBackgroundJob->m_reporter;
177
178 std::stringstream update_json_stream;
179 std::stringstream request_json_stream;
180
181 wxString aUrl = UPDATE_QUERY_ENDPOINT;
182 reporter->SetNumPhases( 1 );
183 reporter->Report( _( "Requesting update info" ) );
184
185 UPDATE_REQUEST requestContent;
186
187
188 // These platform keys are specific to the downloads site
189#if defined( __WXMSW__ )
190 requestContent.platform = "windows";
191
192 #if defined( KICAD_BUILD_ARCH_X64 )
193 requestContent.arch = "amd64";
194 #elif defined( KICAD_BUILD_ARCH_X86 )
195 requestContent.arch = "i686";
196 #elif defined( KICAD_BUILD_ARCH_ARM )
197 requestContent.arch = "arm";
198 #elif defined( KICAD_BUILD_ARCH_ARM64 )
199 requestContent.arch = "arm64";
200 #endif
201#elif defined( __WXOSX__ )
202 requestContent.platform = "macos";
203 requestContent.arch = "unified";
204#else
205 //everything else gets lumped as linux
206 requestContent.platform = "linux";
207 requestContent.arch = "";
208#endif
209 wxString verString = GetSemanticVersion();
210 verString.Replace( "~", "-" ); // make the string valid for semver
211 requestContent.current_version = verString;
212 requestContent.lang = Pgm().GetLanguageTag();
213
214 KICAD_SETTINGS* settings = GetAppSettings<KICAD_SETTINGS>( "kicad" );
215
216 requestContent.last_check = settings->m_lastUpdateCheckTime;
217
218 nlohmann::json requestJson = nlohmann::json( requestContent );
219 request_json_stream << requestJson;
220
221 int responseCode = PostRequest( aUrl, request_json_stream.str(), &update_json_stream,
222 reporter.get(), 20480 );
223
224 // Check that the response is 200 (content provided)
225 // We can also return 204 for no update
226 if( responseCode == 200 && !Pgm().m_Quitting )
227 {
228 nlohmann::json update_json;
229 UPDATE_RESPONSE response;
230
231 try
232 {
233 update_json_stream >> update_json;
234 response = update_json.get<UPDATE_RESPONSE>();
235
236 if( response.version != settings->m_lastReceivedUpdate && !Pgm().m_Quitting )
237 {
238 aNoticeParent->CallAfter(
239 [aNoticeParent, response]()
240 {
241 if( Pgm().m_Quitting )
242 return;
243
244 auto notice = new DIALOG_UPDATE_NOTICE( aNoticeParent,
245 response.version,
246 response.details_url,
247 response.downloads_url );
248
249 int retCode = notice->ShowModal();
250
251 if( retCode != wxID_RETRY )
252 {
253 // basically saving the last received update prevents us from
254 // prompting again
255 if( KICAD_SETTINGS* cfg = GetAppSettings<KICAD_SETTINGS>( "kicad" ) )
256 cfg->m_lastReceivedUpdate = response.version;
257 }
258 } );
259 }
260 }
261 catch( const std::exception& e )
262 {
263 wxLogError( wxString::Format( _( "Unable to parse update response: %s" ), e.what() ) );
264 }
265 }
266
267 settings->m_lastUpdateCheckTime = wxDateTime::Now().FormatISOCombined();
268
270 m_updateBackgroundJob = nullptr;
271 m_working = false;
272 };
273
275 m_updateTask = tp.submit_task( update_check );
276}
wxString GetSemanticVersion()
Get the semantic version string for KiCad defined inside the KiCadVersion.cmake file in the variable ...
std::shared_ptr< BACKGROUND_JOB > Create(const wxString &aName)
Creates a background job with the given name.
void Remove(std::shared_ptr< BACKGROUND_JOB > job)
Removes the given background job from any lists and frees it.
int Perform()
Equivalent to curl_easy_perform.
bool SetPostFields(const std::vector< std::pair< std::string, std::string > > &aFields)
Set fields for application/x-www-form-urlencoded POST request.
void SetHeader(const std::string &aName, const std::string &aValue)
Set an arbitrary header for the HTTP(s) request.
bool SetTransferCallback(const TRANSFER_CALLBACK &aCallback, size_t aInterval)
bool SetURL(const std::string &aURL)
Set the request URL.
bool SetFollowRedirects(bool aFollow)
Enable the following of HTTP(s) and other redirects, by default curl does not follow redirects.
bool SetTimeout(long aTimeoutSecs)
Set the total operation timeout in seconds.
bool SetOutputStream(const std::ostream *aOutput)
const std::string GetErrorText(int aCode)
Fetch CURL's "friendly" error string for a given error code.
wxString m_lastUpdateCheckTime
wxString m_lastReceivedUpdate
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
Definition pgm_base.h:130
virtual wxString GetLanguageTag()
Definition pgm_base.cpp:726
A progress reporter interface for use in multi-threaded environments.
virtual bool IsCancelled() const =0
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
std::shared_ptr< BACKGROUND_JOB > m_updateBackgroundJob
int PostRequest(const wxString &aUrl, std::string aRequestBody, std::ostream *aOutput, PROGRESS_REPORTER *aReporter, const size_t aSizeLimit)
void CheckForUpdate(wxWindow *aNoticeParent)
std::atomic< bool > m_working
std::future< void > m_updateTask
#define _(s)
std::function< int(size_t, size_t, size_t, size_t)> TRANSFER_CALLBACK
Wrapper interface around the curl_easy API/.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
T * GetAppSettings(const char *aFilename)
wxString current_version
IbisParser parser & reporter
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UPDATE_REQUEST, platform, arch, current_version, lang, last_check) struct UPDATE_RESPONSE
#define UPDATE_QUERY_ENDPOINT