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 <json_common.h>
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 curl.SetTimeout( 30L );
148
149 int code = curl.Perform();
150
151 if( aReporter && !aReporter->IsCancelled() )
152 aReporter->SetCurrentProgress( 1.0 );
153
154 if( code != CURLE_OK )
155 {
156 if( aReporter )
157 {
158 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
159 aReporter->Report( _( "Download is too large." ) );
160 else if( code != CURLE_ABORTED_BY_CALLBACK )
161 aReporter->Report( wxString( curl.GetErrorText( code ) ) );
162 }
163
164 return 0;
165 }
166
167 return curl.GetResponseStatusCode();
168}
169
170
171void UPDATE_MANAGER::CheckForUpdate( wxWindow* aNoticeParent )
172{
173 if( m_working )
174 return;
175
176 m_working = false;
177
178 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "Update Check" ) );
179
180 auto update_check = [aNoticeParent, this]() -> void
181 {
182 std::shared_ptr<BACKGROUND_JOB_REPORTER> reporter = m_updateBackgroundJob->m_reporter;
183
184 std::stringstream update_json_stream;
185 std::stringstream request_json_stream;
186
187 wxString aUrl = UPDATE_QUERY_ENDPOINT;
188 reporter->SetNumPhases( 1 );
189 reporter->Report( _( "Requesting update info" ) );
190
191 UPDATE_REQUEST requestContent;
192
193
194 // These platform keys are specific to the downloads site
195#if defined( __WXMSW__ )
196 requestContent.platform = "windows";
197
198 #if defined( KICAD_BUILD_ARCH_X64 )
199 requestContent.arch = "amd64";
200 #elif defined( KICAD_BUILD_ARCH_X86 )
201 requestContent.arch = "i686";
202 #elif defined( KICAD_BUILD_ARCH_ARM )
203 requestContent.arch = "arm";
204 #elif defined( KICAD_BUILD_ARCH_ARM64 )
205 requestContent.arch = "arm64";
206 #endif
207#elif defined( __WXOSX__ )
208 requestContent.platform = "macos";
209 requestContent.arch = "unified";
210#else
211 //everything else gets lumped as linux
212 requestContent.platform = "linux";
213 requestContent.arch = "";
214#endif
215 wxString verString = GetSemanticVersion();
216 verString.Replace( "~", "-" ); // make the string valid for semver
217 requestContent.current_version = verString;
218 requestContent.lang = Pgm().GetLanguageTag();
219
220 KICAD_SETTINGS* settings = 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 && !Pgm().m_Quitting )
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 && !Pgm().m_Quitting )
243 {
244 aNoticeParent->CallAfter(
245 [aNoticeParent, response]()
246 {
247 if( Pgm().m_Quitting )
248 return;
249
250 auto notice = new DIALOG_UPDATE_NOTICE( aNoticeParent,
251 response.version,
252 response.details_url,
253 response.downloads_url );
254
255 int retCode = notice->ShowModal();
256
257 if( retCode != wxID_RETRY )
258 {
259 // basically saving the last received update prevents us from
260 // prompting again
261 if( KICAD_SETTINGS* cfg = GetAppSettings<KICAD_SETTINGS>( "kicad" ) )
262 cfg->m_lastReceivedUpdate = response.version;
263 }
264 } );
265 }
266 }
267 catch( const std::exception& e )
268 {
269 wxLogError( wxString::Format( _( "Unable to parse update response: %s" ), e.what() ) );
270 }
271 }
272
273 settings->m_lastUpdateCheckTime = wxDateTime::Now().FormatISOCombined();
274
276 m_updateBackgroundJob = nullptr;
277 m_working = false;
278 };
279
281 m_updateTask = tp.submit_task( update_check );
282}
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:136
virtual wxString GetLanguageTag()
Definition pgm_base.cpp:705
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
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:31
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UPDATE_REQUEST, platform, arch, current_version, lang, last_check) struct UPDATE_RESPONSE
#define UPDATE_QUERY_ENDPOINT