1 | // Aseprite |
2 | // Copyright (C) 2020-2021 Igara Studio S.A. |
3 | // Copyright (C) 2001-2017 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #ifdef ENABLE_UPDATER |
13 | |
14 | #include "app/check_update.h" |
15 | |
16 | #include "app/check_update_delegate.h" |
17 | #include "app/pref/preferences.h" |
18 | #include "base/convert_to.h" |
19 | #include "base/launcher.h" |
20 | #include "base/replace_string.h" |
21 | #include "base/version.h" |
22 | #include "ver/info.h" |
23 | |
24 | #if ENABLE_SENTRY |
25 | #include "app/sentry_wrapper.h" |
26 | #endif |
27 | |
28 | #include <ctime> |
29 | #include <sstream> |
30 | |
31 | static const int kMonitoringPeriod = 100; |
32 | |
33 | namespace app { |
34 | |
35 | class CheckUpdateBackgroundJob : public updater::CheckUpdateDelegate { |
36 | public: |
37 | CheckUpdateBackgroundJob() |
38 | : m_received(false) { } |
39 | |
40 | void abort() { |
41 | m_checker.abort(); |
42 | } |
43 | |
44 | bool isReceived() const { |
45 | return m_received; |
46 | } |
47 | |
48 | void sendRequest(const updater::Uuid& uuid, const std::string& ) { |
49 | m_checker.checkNewVersion(uuid, extraParams, this); |
50 | } |
51 | |
52 | const updater::CheckUpdateResponse& getResponse() const { |
53 | return m_response; |
54 | } |
55 | |
56 | private: |
57 | void onResponse(updater::CheckUpdateResponse& data) override { |
58 | m_response = data; |
59 | m_received = true; |
60 | } |
61 | |
62 | bool m_received; |
63 | updater::CheckUpdate m_checker; |
64 | updater::CheckUpdateResponse m_response; |
65 | }; |
66 | |
67 | CheckUpdateThreadLauncher::CheckUpdateThreadLauncher(CheckUpdateDelegate* delegate) |
68 | : m_delegate(delegate) |
69 | , m_preferences(Preferences::instance()) |
70 | , m_doCheck(true) |
71 | , m_received(false) |
72 | , m_inits(m_preferences.updater.inits()) |
73 | , m_exits(m_preferences.updater.exits()) |
74 | #ifdef _DEBUG |
75 | , m_isDeveloper(true) |
76 | #else |
77 | , m_isDeveloper(m_preferences.updater.isDeveloper()) |
78 | #endif |
79 | , m_timer(kMonitoringPeriod, NULL) |
80 | { |
81 | // Get how many days we have to wait for the next "check for update" |
82 | double waitDays = m_preferences.updater.waitDays(); |
83 | if (waitDays > 0.0) { |
84 | // Get the date of the last "check for updates" |
85 | time_t lastCheck = (time_t)m_preferences.updater.lastCheck(); |
86 | time_t now = std::time(NULL); |
87 | |
88 | // Verify if we are in the "WaitDays" period... |
89 | if (now < lastCheck+int(double(60*60*24*waitDays)) && |
90 | now > lastCheck) { // <- Avoid broken clocks |
91 | // So we do not check for updates. |
92 | m_doCheck = false; |
93 | } |
94 | } |
95 | |
96 | // Minimal stats: number of initializations |
97 | m_preferences.updater.inits(m_inits+1); |
98 | m_preferences.save(); |
99 | } |
100 | |
101 | CheckUpdateThreadLauncher::~CheckUpdateThreadLauncher() |
102 | { |
103 | if (m_timer.isRunning()) |
104 | m_timer.stop(); |
105 | |
106 | if (m_thread) { |
107 | if (m_bgJob) |
108 | m_bgJob->abort(); |
109 | |
110 | m_thread->join(); |
111 | } |
112 | |
113 | // Minimal stats: number of exits |
114 | m_preferences.updater.exits(m_exits+1); |
115 | m_preferences.save(); |
116 | } |
117 | |
118 | void CheckUpdateThreadLauncher::launch() |
119 | { |
120 | if (m_uuid.empty()) |
121 | m_uuid = m_preferences.updater.uuid(); |
122 | |
123 | #if ENABLE_SENTRY |
124 | if (!m_uuid.empty()) |
125 | Sentry::setUserID(m_uuid); |
126 | #endif |
127 | |
128 | // In this case we are in the "wait days" period, so we don't check |
129 | // for updates. |
130 | if (!m_doCheck) { |
131 | showUI(); |
132 | return; |
133 | } |
134 | |
135 | m_delegate->onCheckingUpdates(); |
136 | |
137 | m_bgJob.reset(new CheckUpdateBackgroundJob); |
138 | m_thread.reset(new base::thread([this]{ checkForUpdates(); })); |
139 | |
140 | // Start a timer to monitoring the progress of the background job |
141 | // executed in "m_thread". The "onMonitoringTick" method will be |
142 | // called periodically by the GUI main thread. |
143 | m_timer.Tick.connect(&CheckUpdateThreadLauncher::onMonitoringTick, this); |
144 | m_timer.start(); |
145 | } |
146 | |
147 | bool CheckUpdateThreadLauncher::isReceived() const |
148 | { |
149 | return m_received; |
150 | } |
151 | |
152 | void CheckUpdateThreadLauncher::onMonitoringTick() |
153 | { |
154 | // If we do not receive a response yet... |
155 | if (!m_received) |
156 | return; // Skip and wait the next call. |
157 | |
158 | // Depending on the type of update received |
159 | switch (m_response.getUpdateType()) { |
160 | |
161 | case updater::CheckUpdateResponse::NoUpdate: |
162 | // Clear |
163 | m_preferences.updater.newVersion("" ); |
164 | m_preferences.updater.newUrl("" ); |
165 | break; |
166 | |
167 | case updater::CheckUpdateResponse::Critical: |
168 | case updater::CheckUpdateResponse::Major: |
169 | m_preferences.updater.newVersion(m_response.getLatestVersion()); |
170 | m_preferences.updater.newUrl(m_response.getUrl()); |
171 | break; |
172 | } |
173 | |
174 | showUI(); |
175 | |
176 | // Save the new UUID |
177 | if (!m_response.getUuid().empty()) { |
178 | m_uuid = m_response.getUuid(); |
179 | m_preferences.updater.uuid(m_uuid); |
180 | |
181 | #if ENABLE_SENTRY |
182 | if (!m_uuid.empty()) |
183 | Sentry::setUserID(m_uuid); |
184 | #endif |
185 | } |
186 | |
187 | // Set the date of the last "check for updates" and the "WaitDays" parameter. |
188 | m_preferences.updater.lastCheck((int)std::time(NULL)); |
189 | m_preferences.updater.waitDays(m_response.getWaitDays()); |
190 | |
191 | // Save the config file right now |
192 | m_preferences.save(); |
193 | |
194 | // Stop the monitoring timer. |
195 | m_timer.stop(); |
196 | } |
197 | |
198 | // This method is executed in a special thread to send the HTTP request. |
199 | void CheckUpdateThreadLauncher::checkForUpdates() |
200 | { |
201 | // Add mini-stats in the request |
202 | std::stringstream ; |
203 | extraParams << "inits=" << m_inits |
204 | << "&exits=" << m_exits; |
205 | |
206 | if (m_isDeveloper) |
207 | extraParams << "&dev=1" ; |
208 | |
209 | // Send the HTTP request to check for updates. |
210 | m_bgJob->sendRequest(m_uuid, extraParams.str()); |
211 | |
212 | if (m_bgJob->isReceived()) { |
213 | m_received = true; |
214 | m_response = m_bgJob->getResponse(); |
215 | } |
216 | } |
217 | |
218 | void CheckUpdateThreadLauncher::showUI() |
219 | { |
220 | std::string localVersionStr = get_app_version(); |
221 | base::replace_string(localVersionStr, "-x64" , "" ); |
222 | bool newVer = false; |
223 | |
224 | if (!m_preferences.updater.newVersion().empty()) { |
225 | base::Version serverVersion(m_preferences.updater.newVersion()); |
226 | base::Version localVersion(localVersionStr); |
227 | newVer = (localVersion < serverVersion); |
228 | } |
229 | |
230 | if (newVer) { |
231 | m_delegate->onNewUpdate(m_preferences.updater.newUrl(), |
232 | m_preferences.updater.newVersion()); |
233 | } |
234 | else { |
235 | // If the program was updated, reset the "exits" counter |
236 | if (m_preferences.updater.currentVersion() != localVersionStr) { |
237 | m_preferences.updater.currentVersion(localVersionStr); |
238 | m_exits = m_inits; |
239 | } |
240 | |
241 | m_delegate->onUpToDate(); |
242 | } |
243 | } |
244 | |
245 | } |
246 | |
247 | #endif // ENABLE_UPDATER |
248 | |