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 (C) 2023 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 <core/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
89int UPDATE_MANAGER::PostRequest( const wxString& aUrl, std::string aRequestBody,
90 std::ostream* aOutput, PROGRESS_REPORTER* aReporter,
91 const size_t aSizeLimit )
92{
93 bool size_exceeded = false;
94
95 TRANSFER_CALLBACK callback = [&]( size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow )
96 {
97 if( aSizeLimit > 0 && ( dltotal > aSizeLimit || dlnow > aSizeLimit ) )
98 {
99 size_exceeded = true;
100
101 // Non zero return means abort.
102 return true;
103 }
104
105 if( aReporter )
106 {
107 if( dltotal > 1000 )
108 {
109 aReporter->SetCurrentProgress( dlnow / (double) dltotal );
110 aReporter->Report( wxString::Format( _( "Downloading %lld/%lld kB" ), dlnow / 1000,
111 dltotal / 1000 ) );
112 }
113 else
114 {
115 if( aReporter )
116 aReporter->SetCurrentProgress( 0.0 );
117 }
118
119 return !aReporter->KeepRefreshing();
120 }
121 else
122 return false;
123 };
124
125 KICAD_CURL_EASY curl;
126 curl.SetHeader( "Accept", "application/json" );
127 curl.SetHeader( "Content-Type", "application/json" );
128 curl.SetHeader( "charset", "utf-8" );
129 curl.SetOutputStream( aOutput );
130 curl.SetURL( aUrl.ToUTF8().data() );
131 curl.SetPostFields( aRequestBody );
132 curl.SetFollowRedirects( true );
133 curl.SetTransferCallback( callback, 250000L );
134
135 int code = curl.Perform();
136
137 if( aReporter && !aReporter->IsCancelled() )
138 aReporter->SetCurrentProgress( 1.0 );
139
140 if( code != CURLE_OK )
141 {
142 if( aReporter )
143 {
144 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
145 aReporter->Report( _( "Download is too large." ) );
146 else if( code != CURLE_ABORTED_BY_CALLBACK )
147 aReporter->Report( wxString( curl.GetErrorText( code ) ) );
148 }
149
150 return 0;
151 }
152
153 return curl.GetResponseStatusCode();
154}
155
156
157void UPDATE_MANAGER::CheckForUpdate( wxWindow* aNoticeParent )
158{
159 if( m_working )
160 return;
161
162 m_working = false;
163
164 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "Update Check" ) );
165
166 auto update_check = [aNoticeParent, this]() -> void
167 {
168 std::stringstream update_json_stream;
169 std::stringstream request_json_stream;
170
171 wxString aUrl = UPDATE_QUERY_ENDPOINT;
172 m_updateBackgroundJob->m_reporter->SetNumPhases( 1 );
173 m_updateBackgroundJob->m_reporter->Report( _( "Requesting update info" ) );
174
175 UPDATE_REQUEST requestContent;
176
177
178 // These platform keys are specific to the downloads site
179#if defined( __WXMSW__ )
180 requestContent.platform = "windows";
181
182 #if defined( KICAD_BUILD_ARCH_X64 )
183 requestContent.arch = "amd64";
184 #elif defined( KICAD_BUILD_ARCH_X86 )
185 requestContent.arch = "i686";
186 #elif defined( KICAD_BUILD_ARCH_ARM )
187 requestContent.arch = "arm";
188 #elif defined( KICAD_BUILD_ARCH_ARM64 )
189 requestContent.arch = "arm64";
190 #endif
191#elif defined( __WXOSX__ )
192 requestContent.platform = "macos";
193 requestContent.arch = "unified";
194#else
195 //everything else gets lumped as linux
196 requestContent.platform = "linux";
197 requestContent.arch = "";
198#endif
199 wxString verString = GetSemanticVersion();
200 verString.Replace( "~", "-" ); // make the string valid for semver
201 requestContent.current_version = verString;
202 requestContent.lang = Pgm().GetLanguageTag();
203
205 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
206
207 requestContent.last_check = settings->m_lastUpdateCheckTime;
208
209 nlohmann::json requestJson = nlohmann::json( requestContent );
210 request_json_stream << requestJson;
211
212 int responseCode =
213 PostRequest( aUrl, request_json_stream.str(), &update_json_stream, NULL, 20480 );
214
215 // Check that the response is 200 (content provided)
216 // We can also return 204 for no update
217 if( responseCode == 200 )
218 {
219 nlohmann::json update_json;
220 UPDATE_RESPONSE response;
221
222 try
223 {
224 update_json_stream >> update_json;
225 response = update_json.get<UPDATE_RESPONSE>();
226
227 if( response.version != settings->m_lastReceivedUpdate )
228 {
229 aNoticeParent->CallAfter(
230 [aNoticeParent, response]()
231 {
232 auto notice = new DIALOG_UPDATE_NOTICE( aNoticeParent,
233 response.version,
234 response.details_url,
235 response.downloads_url );
236
237 int retCode = notice->ShowModal();
238
239 if( retCode != wxID_RETRY )
240 {
241 // basically saving the last received update prevents us from
242 // prompting again
244 KICAD_SETTINGS* cfg = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
245 cfg->m_lastReceivedUpdate = response.version;
246 }
247 } );
248 }
249 }
250 catch( const std::exception& e )
251 {
252 wxLogError( wxString::Format( _( "Unable to parse update response: %s" ),
253 e.what() ) );
254 }
255 }
256
257 settings->m_lastUpdateCheckTime = wxDateTime::Now().FormatISOCombined();
258
260 m_updateBackgroundJob = nullptr;
261 m_working = false;
262 };
263
265 tp.push_task( update_check );
266}
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:146
virtual wxString GetLanguageTag()
Definition: pgm_base.cpp:843
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition: pgm_base.h:142
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)
Returns a handle to the a given settings by type If the settings have already been loaded,...
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
#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:1060
see class PGM_BASE
wxString current_version
static thread_pool * tp
Definition: thread_pool.cpp:30
BS::thread_pool thread_pool
Definition: thread_pool.h:30
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:32
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UPDATE_REQUEST, platform, arch, current_version, lang, last_check) struct UPDATE_RESPONSE
#define UPDATE_QUERY_ENDPOINT