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