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 )
170 return;
171
172 m_working = false;
173
174 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "Update Check" ) );
175
176 auto update_check = [aNoticeParent, this]() -> void
177 {
178 std::shared_ptr<BACKGROUND_JOB_REPORTER> reporter = m_updateBackgroundJob->m_reporter;
179
180 std::stringstream update_json_stream;
181 std::stringstream request_json_stream;
182
183 wxString aUrl = UPDATE_QUERY_ENDPOINT;
184 reporter->SetNumPhases( 1 );
185 reporter->Report( _( "Requesting update info" ) );
186
187 UPDATE_REQUEST requestContent;
188
189
190 // These platform keys are specific to the downloads site
191#if defined( __WXMSW__ )
192 requestContent.platform = "windows";
193
194 #if defined( KICAD_BUILD_ARCH_X64 )
195 requestContent.arch = "amd64";
196 #elif defined( KICAD_BUILD_ARCH_X86 )
197 requestContent.arch = "i686";
198 #elif defined( KICAD_BUILD_ARCH_ARM )
199 requestContent.arch = "arm";
200 #elif defined( KICAD_BUILD_ARCH_ARM64 )
201 requestContent.arch = "arm64";
202 #endif
203#elif defined( __WXOSX__ )
204 requestContent.platform = "macos";
205 requestContent.arch = "unified";
206#else
207 //everything else gets lumped as linux
208 requestContent.platform = "linux";
209 requestContent.arch = "";
210#endif
211 wxString verString = GetSemanticVersion();
212 verString.Replace( "~", "-" ); // make the string valid for semver
213 requestContent.current_version = verString;
214 requestContent.lang = Pgm().GetLanguageTag();
215
216 KICAD_SETTINGS* settings = GetAppSettings<KICAD_SETTINGS>( "kicad" );
217
218 requestContent.last_check = settings->m_lastUpdateCheckTime;
219
220 nlohmann::json requestJson = nlohmann::json( requestContent );
221 request_json_stream << requestJson;
222
223 int responseCode = PostRequest( aUrl, request_json_stream.str(), &update_json_stream,
224 reporter.get(), 20480 );
225
226 // Check that the response is 200 (content provided)
227 // We can also return 204 for no update
228 if( responseCode == 200 && !Pgm().m_Quitting )
229 {
230 nlohmann::json update_json;
231 UPDATE_RESPONSE response;
232
233 try
234 {
235 update_json_stream >> update_json;
236 response = update_json.get<UPDATE_RESPONSE>();
237
238 if( response.version != settings->m_lastReceivedUpdate && !Pgm().m_Quitting )
239 {
240 aNoticeParent->CallAfter(
241 [aNoticeParent, response]()
242 {
243 if( Pgm().m_Quitting )
244 return;
245
246 auto notice = new DIALOG_UPDATE_NOTICE( aNoticeParent,
247 response.version,
248 response.details_url,
249 response.downloads_url );
250
251 int retCode = notice->ShowModal();
252
253 if( retCode != wxID_RETRY )
254 {
255 // basically saving the last received update prevents us from
256 // prompting again
257 if( KICAD_SETTINGS* cfg = GetAppSettings<KICAD_SETTINGS>( "kicad" ) )
258 cfg->m_lastReceivedUpdate = response.version;
259 }
260 } );
261 }
262 }
263 catch( const std::exception& e )
264 {
265 wxLogError( wxString::Format( _( "Unable to parse update response: %s" ), e.what() ) );
266 }
267 }
268
269 settings->m_lastUpdateCheckTime = wxDateTime::Now().FormatISOCombined();
270
272 m_updateBackgroundJob = nullptr;
273 m_working = false;
274 };
275
277 m_updateTask = tp.submit_task( update_check );
278}
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:692
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