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