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, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <update_manager.h>
26#include <pgm_base.h>
27
28#include <string>
29#include <sstream>
30
34
37#include <progress_reporter.h>
38
40
41#include <nlohmann/json.hpp>
43
44#include <wx/log.h>
45#include <wx/event.h>
46#include <wx/filefn.h>
47#include <wx/translation.h>
48#include <wx/notifmsg.h>
49
51
52#include <thread_pool.h>
53
54#include <build_version.h>
55
56
58{
59 wxString platform;
60 wxString arch;
62 wxString lang;
63 wxString last_check;
64};
65
66
67NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( UPDATE_REQUEST, platform, arch, current_version, lang,
68 last_check )
69
70struct UPDATE_RESPONSE
71{
72 wxString version;
73 wxString release_date;
74 wxString details_url;
75 wxString downloads_url;
76};
77
78NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE( UPDATE_RESPONSE, version, release_date, details_url,
79 downloads_url )
80
81#define UPDATE_QUERY_ENDPOINT wxS( "https://downloads.kicad.org/api/v1/update" )
82
83
84UPDATE_MANAGER::UPDATE_MANAGER() : m_working( false )
85{
86}
87
88
90{
92 {
93 if( m_updateBackgroundJob->m_reporter )
94 m_updateBackgroundJob->m_reporter->Cancel();
95
96 if( m_updateTask.valid() )
97 m_updateTask.wait();
98 }
99}
100
101
102int UPDATE_MANAGER::PostRequest( const wxString& aUrl, std::string aRequestBody,
103 std::ostream* aOutput, PROGRESS_REPORTER* aReporter,
104 const size_t aSizeLimit )
105{
106 bool size_exceeded = false;
107
108 TRANSFER_CALLBACK callback = [&]( size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow )
109 {
110 if( aSizeLimit > 0 && ( dltotal > aSizeLimit || dlnow > aSizeLimit ) )
111 {
112 size_exceeded = true;
113
114 // Non zero return means abort.
115 return true;
116 }
117
118 if( aReporter )
119 {
120 if( dltotal > 1000 )
121 {
122 aReporter->SetCurrentProgress( dlnow / (double) dltotal );
123 aReporter->Report( wxString::Format( _( "Downloading %lld/%lld kB" ), dlnow / 1000,
124 dltotal / 1000 ) );
125 }
126 else
127 {
128 if( aReporter )
129 aReporter->SetCurrentProgress( 0.0 );
130 }
131
132 return !aReporter->KeepRefreshing();
133 }
134 else
135 return false;
136 };
137
138 KICAD_CURL_EASY curl;
139 curl.SetHeader( "Accept", "application/json" );
140 curl.SetHeader( "Content-Type", "application/json" );
141 curl.SetHeader( "charset", "utf-8" );
142 curl.SetOutputStream( aOutput );
143 curl.SetURL( aUrl.ToUTF8().data() );
144 curl.SetPostFields( aRequestBody );
145 curl.SetFollowRedirects( true );
146 curl.SetTransferCallback( callback, 250000L );
147
148 int code = curl.Perform();
149
150 if( aReporter && !aReporter->IsCancelled() )
151 aReporter->SetCurrentProgress( 1.0 );
152
153 if( code != CURLE_OK )
154 {
155 if( aReporter )
156 {
157 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
158 aReporter->Report( _( "Download is too large." ) );
159 else if( code != CURLE_ABORTED_BY_CALLBACK )
160 aReporter->Report( wxString( curl.GetErrorText( code ) ) );
161 }
162
163 return 0;
164 }
165
166 return curl.GetResponseStatusCode();
167}
168
169
170void UPDATE_MANAGER::CheckForUpdate( wxWindow* aNoticeParent )
171{
172 if( m_working )
173 return;
174
175 m_working = false;
176
177 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "Update Check" ) );
178
179 auto update_check = [aNoticeParent, this]() -> void
180 {
181 std::shared_ptr<BACKGROUND_JOB_REPORTER> reporter = m_updateBackgroundJob->m_reporter;
182
183 std::stringstream update_json_stream;
184 std::stringstream request_json_stream;
185
186 wxString aUrl = UPDATE_QUERY_ENDPOINT;
187 reporter->SetNumPhases( 1 );
188 reporter->Report( _( "Requesting update info" ) );
189
190 UPDATE_REQUEST requestContent;
191
192
193 // These platform keys are specific to the downloads site
194#if defined( __WXMSW__ )
195 requestContent.platform = "windows";
196
197 #if defined( KICAD_BUILD_ARCH_X64 )
198 requestContent.arch = "amd64";
199 #elif defined( KICAD_BUILD_ARCH_X86 )
200 requestContent.arch = "i686";
201 #elif defined( KICAD_BUILD_ARCH_ARM )
202 requestContent.arch = "arm";
203 #elif defined( KICAD_BUILD_ARCH_ARM64 )
204 requestContent.arch = "arm64";
205 #endif
206#elif defined( __WXOSX__ )
207 requestContent.platform = "macos";
208 requestContent.arch = "unified";
209#else
210 //everything else gets lumped as linux
211 requestContent.platform = "linux";
212 requestContent.arch = "";
213#endif
214 wxString verString = GetSemanticVersion();
215 verString.Replace( "~", "-" ); // make the string valid for semver
216 requestContent.current_version = verString;
217 requestContent.lang = Pgm().GetLanguageTag();
218
220 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
221
222 requestContent.last_check = settings->m_lastUpdateCheckTime;
223
224 nlohmann::json requestJson = nlohmann::json( requestContent );
225 request_json_stream << requestJson;
226
227 int responseCode = PostRequest( aUrl, request_json_stream.str(), &update_json_stream,
228 reporter.get(), 20480 );
229
230 // Check that the response is 200 (content provided)
231 // We can also return 204 for no update
232 if( responseCode == 200 )
233 {
234 nlohmann::json update_json;
235 UPDATE_RESPONSE response;
236
237 try
238 {
239 update_json_stream >> update_json;
240 response = update_json.get<UPDATE_RESPONSE>();
241
242 if( response.version != settings->m_lastReceivedUpdate )
243 {
244 aNoticeParent->CallAfter(
245 [aNoticeParent, response]()
246 {
247 auto notice = new DIALOG_UPDATE_NOTICE( aNoticeParent,
248 response.version,
249 response.details_url,
250 response.downloads_url );
251
252 int retCode = notice->ShowModal();
253
254 if( retCode != wxID_RETRY )
255 {
256 // basically saving the last received update prevents us from
257 // prompting again
259 KICAD_SETTINGS* cfg = set_mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
260 cfg->m_lastReceivedUpdate = response.version;
261 }
262 } );
263 }
264 }
265 catch( const std::exception& e )
266 {
267 wxLogError( wxString::Format( _( "Unable to parse update response: %s" ),
268 e.what() ) );
269 }
270 }
271
272 settings->m_lastUpdateCheckTime = wxDateTime::Now().FormatISOCombined();
273
275 m_updateBackgroundJob = nullptr;
276 m_working = false;
277 };
278
280 m_updateTask = tp.submit( update_check );
281}
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 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:129
virtual wxString GetLanguageTag()
Definition: pgm_base.cpp:853
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition: pgm_base.h:125
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).
T * GetAppSettings(const wxString &aFilename)
Return a handle to the a given settings by type.
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.
Definition: pgm_base.cpp:1073
see class PGM_BASE
wxString current_version
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:30
static thread_pool * tp
Definition: thread_pool.cpp:28
BS::thread_pool thread_pool
Definition: thread_pool.h:31
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UPDATE_REQUEST, platform, arch, current_version, lang, last_check) struct UPDATE_RESPONSE
#define UPDATE_QUERY_ENDPOINT