1 // ==========================================================================
2 // SeqAn - The Library for Sequence Analysis
3 // ==========================================================================
4 // Copyright (c) 2006-2018, Knut Reinert, FU Berlin
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 // * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 // * Redistributions in binary form must reproduce the above copyright
13 // notice, this list of conditions and the following disclaimer in the
14 // documentation and/or other materials provided with the distribution.
15 // * Neither the name of Knut Reinert or the FU Berlin nor the names of
16 // its contributors may be used to endorse or promote products derived
17 // from this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 // ARE DISCLAIMED. IN NO EVENT SHALL KNUT REINERT OR THE FU BERLIN BE LIABLE
23 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
29 // DAMAGE.
30 //
31 // ==========================================================================
32 // Author: Svenja Mehringer <svenja.mehringer@fu-berlin.de>
33 // ==========================================================================
34
35 #ifndef SEQAN_INCLUDE_ARG_PARSE_VERSION_CHECK_H_
36 #define SEQAN_INCLUDE_ARG_PARSE_VERSION_CHECK_H_
37
38 #include <sys/stat.h>
39
40 #include <iostream>
41 #include <fstream>
42 #include <regex>
43 #include <future>
44 #include <chrono>
45
46 namespace seqan
47 {
48
49 // ==========================================================================
50 // Forwards
51 // ==========================================================================
52
53 // NOTE(rrahn): In-file forward for function call operator.
54 struct VersionCheck;
55 inline void _checkForNewerVersion(VersionCheck &, std::promise<bool>);
56 inline std::string _getPath();
57 constexpr const char * _getOS();
58 constexpr const char * _getBitSys();
59
60 // ==========================================================================
61 // Tags, Classes, Enums
62 // ==========================================================================
63
64 template <typename TVoidSpec = void>
65 struct VersionControlTags_
66 {
67 static constexpr char const * const SEQAN_NAME = "seqan";
68 static constexpr char const * const UNREGISTERED_APP = "UNREGISTERED_APP";
69
70 static constexpr char const * const MESSAGE_SEQAN_UPDATE =
71 "[SEQAN INFO] :: A new SeqAn version is available online.\n"
72 "[SEQAN INFO] :: Please visit www.seqan.de for an update or inform the developer of this app.\n"
73 "[SEQAN INFO] :: If you don't wish to receive further notifications, set --version-check OFF.\n\n";
74 static constexpr char const * const MESSAGE_APP_UPDATE =
75 "[APP INFO] :: A new version of this application is now available.\n"
76 "[APP INFO] :: Visit www.seqan.de for updates of official SeqAn applications.\n"
77 "[APP INFO] :: If you don't wish to receive further notifications, set --version-check OFF.\n\n";
78 static constexpr char const * const MESSAGE_UNREGISTERED_APP =
79 "[SEQAN INFO] :: Thank you for using SeqAn!\n"
80 "[SEQAN INFO] :: Do you wish to register your app for update notifications?\n"
81 "[SEQAN INFO] :: Just send an email to support@seqan.de with your app name and version number.\n"
82 "[SEQAN INFO] :: If you don't wish to receive further notifications, set --version-check OFF.\n\n";
83 static constexpr char const * const MESSAGE_REGISTERED_APP_UPDATE =
84 "[APP INFO] :: We noticed the app version you use is newer than the one registered with us.\n"
85 "[APP INFO] :: Please send us an email with the new version so we can correct it (support@seqan.de)\n\n";
86 };
87
88 template <typename TVoidSpec>
89 constexpr char const * const VersionControlTags_<TVoidSpec>::SEQAN_NAME;
90 template <typename TVoidSpec>
91 constexpr char const * const VersionControlTags_<TVoidSpec>::UNREGISTERED_APP;
92 template <typename TVoidSpec>
93 constexpr char const * const VersionControlTags_<TVoidSpec>::MESSAGE_SEQAN_UPDATE;
94 template <typename TVoidSpec>
95 constexpr char const * const VersionControlTags_<TVoidSpec>::MESSAGE_APP_UPDATE;
96 template <typename TVoidSpec>
97 constexpr char const * const VersionControlTags_<TVoidSpec>::MESSAGE_UNREGISTERED_APP;
98 template <typename TVoidSpec>
99 constexpr char const * const VersionControlTags_<TVoidSpec>::MESSAGE_REGISTERED_APP_UPDATE;
100
101 struct VersionCheck
102 {
103 // ----------------------------------------------------------------------------
104 // Member Variables
105 // ----------------------------------------------------------------------------
106 std::string _url;
107 std::string _name;
108 std::string _version = "0.0.0";
109 std::string _program;
110 std::string _command;
111 std::string _path = _getPath();
112 std::string _timestamp_filename;
113 std::ostream & errorStream;
114
115 // ----------------------------------------------------------------------------
116 // Constructors
117 // ----------------------------------------------------------------------------
118
VersionCheckVersionCheck119 VersionCheck(std::string name,
120 std::string const & version,
121 std::ostream & errorStream) :
122 _name{std::move(name)},
123 errorStream(errorStream)
124 {
125 std::smatch versionMatch;
126 #if defined(NDEBUG) || defined(SEQAN_TEST_VERSION_CHECK_)
127 _timestamp_filename = _path + "/" + _name + "_usr.timestamp";
128 #else
129 _timestamp_filename = _path + "/" + _name + "_dev.timestamp";
130 #endif
131 if (!version.empty() &&
132 std::regex_search(version, versionMatch, std::regex("^([[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+).*")))
133 {
134 _version = versionMatch.str(1); // in case the git revision number is given take only version number
135 }
136 _url = static_cast<std::string>("http://seqan-update.informatik.uni-tuebingen.de/check/SeqAn_") + _getOS() + _getBitSys() + _name + "_" + _version;
137 _getProgram();
138 _updateCommand();
139 }
140
141 // ----------------------------------------------------------------------------
142 // Member Functions
143 // ----------------------------------------------------------------------------
144
145 #if defined(STDLIB_VS)
_getProgramVersionCheck146 void _getProgram()
147 {
148 _program = "powershell.exe -NoLogo -NonInteractive -Command \"& {Invoke-WebRequest -erroraction 'silentlycontinue' -OutFile";
149 }
150 #else // Unix based platforms.
_getProgramVersionCheck151 void _getProgram()
152 {
153 // ask if system call for version or help is successfull
154 if (!system("wget --version > /dev/null 2>&1"))
155 _program = "wget -q -O";
156 else if (!system("curl --version > /dev/null 2>&1"))
157 _program = "curl -o";
158 #ifndef __linux // ftp call does not work on linux
159 else if (!system("which ftp > /dev/null 2>&1"))
160 _program = "ftp -Vo";
161 #endif
162 else
163 _program.clear();
164 }
165 #endif // defined(STDLIB_VS)
166
_updateCommandVersionCheck167 void _updateCommand()
168 {
169 if (!_program.empty())
170 {
171 _command = _program + " " + _path + "/" + _name + ".version " + _url;
172 #if defined(STDLIB_VS)
173 _command = _command + "; exit [int] -not $?}\" > nul 2>&1";
174 #else
175 _command = _command + " > /dev/null 2>&1";
176 #endif
177 }
178 }
179
operatorVersionCheck180 inline void operator()(std::promise<bool> versionCheckProm)
181 {
182 _checkForNewerVersion(*this, std::move(versionCheckProm));
183 }
184 };
185
186 // ==========================================================================
187 // Metafunctions
188 // ==========================================================================
189
190 // ==========================================================================
191 // Functions
192 // ==========================================================================
193
194 // ----------------------------------------------------------------------------
195 // Function setURL()
196 // ----------------------------------------------------------------------------
197
setURL(VersionCheck & me,std::string url)198 inline void setURL(VersionCheck & me, std::string url)
199 {
200 std::swap(me._url, url);
201 me._updateCommand();
202 }
203
204 // ----------------------------------------------------------------------------
205 // Function _getOS()
206 // ----------------------------------------------------------------------------
207
_getOS()208 constexpr const char * _getOS()
209 {
210 //get system information
211 #ifdef __linux
212 return "Linux";
213 #elif __APPLE__
214 return "MacOS";
215 #elif defined(STDLIB_VS)
216 return "Windows";
217 #elif __FreeBSD__
218 return "FreeBSD";
219 #elif __OpenBSD__
220 return "OpenBSD";
221 #else
222 return "unknown";
223 #endif
224 }
225
226 // ----------------------------------------------------------------------------
227 // Function _getBitSys()
228 // ----------------------------------------------------------------------------
229
_getBitSys()230 constexpr const char * _getBitSys()
231 {
232 #if SEQAN_IS_32_BIT
233 return "_32_";
234 #else
235 return "_64_";
236 #endif
237 }
238
239 // ----------------------------------------------------------------------------
240 // Function _checkWritability()
241 // ----------------------------------------------------------------------------
242
243 #if defined(STDLIB_VS)
_checkWritability(std::string const & path)244 inline bool _checkWritability(std::string const & path)
245 {
246 DWORD ftyp = GetFileAttributesA(path.c_str());
247 if (ftyp == INVALID_FILE_ATTRIBUTES || !(ftyp & FILE_ATTRIBUTE_DIRECTORY))
248 {
249 if (!CreateDirectory(path.c_str(), NULL))
250 return false;
251 }
252
253 HANDLE dummyFile; // check writablity by trying to create a file in GENERIC_WRITE mode
254 std::string fileName(path + "/dummy.txt");
255 dummyFile = CreateFile(fileName.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
256
257 if (dummyFile == INVALID_HANDLE_VALUE)
258 return false;
259
260 CloseHandle(dummyFile);
261 bool successful_deletion = DeleteFile(fileName.c_str());
262 SEQAN_ASSERT(successful_deletion);
263 if (!successful_deletion)
264 return false;
265
266 return true;
267 }
268 #else
_checkWritability(std::string const & path)269 inline bool _checkWritability(std::string const & path)
270 {
271 struct stat d_stat;
272 if (stat(path.c_str(), &d_stat) < 0)
273 {
274 // try to make dir
275 std::string makeDir("mkdir -p " + path);
276 if (system(makeDir.c_str()))
277 return false; // could not create home dir
278
279 if (stat(path.c_str(), &d_stat) < 0) // repeat stat
280 return false;
281 }
282
283 if (!(d_stat.st_mode & S_IWUSR))
284 return false; // dir not writable
285
286 return true;
287 }
288 #endif
289
290 // ----------------------------------------------------------------------------
291 // Function _getPath()
292 // ----------------------------------------------------------------------------
293
_getPath()294 inline std::string _getPath()
295 {
296 std::string path;
297 #if defined(STDLIB_VS)
298 path = std::string(getenv("UserProfile")) + "/.config/seqan";
299 #else
300 path = std::string(getenv("HOME")) + "/.config/seqan";
301 #endif
302
303 // check if user has permission to write to home path
304 if (!_checkWritability(path))
305 {
306 #if defined(STDLIB_VS)
307 TCHAR tmp_path [MAX_PATH];
308 if (GetTempPath(MAX_PATH, tmp_path) != 0)
309 {
310 path = tmp_path;
311 }
312 else
313 { //GetTempPath() returns 0 on failure
314 path.clear();
315 }
316 # else // unix
317 path = "/tmp";
318 #endif
319 }
320 return path;
321 }
322
323 // ----------------------------------------------------------------------------
324 // Function _getFileTimeDiff()
325 // ----------------------------------------------------------------------------
326
_getFileTimeDiff(VersionCheck const & me)327 inline double _getFileTimeDiff(VersionCheck const & me)
328 {
329 double curr = static_cast<double>(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
330 std::ifstream timestamp_file;
331 timestamp_file.open(me._timestamp_filename.c_str());
332
333 if (timestamp_file.is_open())
334 {
335 std::string str_time;
336 std::getline(timestamp_file, str_time);
337 timestamp_file.close();
338 double d_time;
339 lexicalCast(d_time, str_time);
340 return curr - d_time;
341 }
342 return curr;
343 }
344
345 // ----------------------------------------------------------------------------
346 // Function _getNumbersFromString()
347 // ----------------------------------------------------------------------------
348
_getNumbersFromString(std::string const & str)349 inline String<int> _getNumbersFromString(std::string const & str)
350 {
351 String<int> numbers;
352 StringSet<std::string> set;
353 strSplit(set, str, EqualsChar<'.'>(), false);
354 for (auto & num : set)
355 {
356 appendValue(numbers, lexicalCast<int>(num));
357 }
358 return numbers;
359 }
360
361 // ----------------------------------------------------------------------------
362 // Function _readVersionString()
363 // ----------------------------------------------------------------------------
364
_readVersionStrings(std::vector<std::string> & versions,std::string const & version_file)365 inline void _readVersionStrings(std::vector<std::string> & versions, std::string const & version_file)
366 {
367 std::ifstream myfile;
368 myfile.open(version_file.c_str());
369 std::string app_version;
370 std::string seqan_version;
371 if (myfile.is_open())
372 {
373 std::getline(myfile, app_version); // get first line which should only contain the version number of the app
374
375 #if !defined(NDEBUG) || defined(SEQAN_TEST_VERSION_CHECK_)
376 if (app_version == VersionControlTags_<>::UNREGISTERED_APP)
377 versions[0] = app_version;
378 #endif // !defined(NDEBUG) || defined(SEQAN_TEST_VERSION_CHECK_)
379
380 if (std::regex_match(app_version, std::regex("^[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+$")))
381 versions[0] = app_version;
382
383 std::getline(myfile, seqan_version); // get second line which should only contain the version number of seqan
384
385 if (std::regex_match(seqan_version, std::regex("^[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+$")))
386 versions[1] = seqan_version;
387
388 myfile.close();
389 }
390 }
391
392 // ----------------------------------------------------------------------------
393 // Function _callServer()
394 // ----------------------------------------------------------------------------
395
_callServer(VersionCheck const me,std::promise<bool> prom)396 inline void _callServer(VersionCheck const me, std::promise<bool> prom)
397 {
398 // update timestamp
399 std::ofstream timestamp_file(me._timestamp_filename.c_str());
400 if (timestamp_file.is_open())
401 {
402 timestamp_file << std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
403 timestamp_file.close();
404 }
405
406 // system call
407 // http response is stored in a file '.config/seqan/{app_name}_version'
408 if (system(me._command.c_str()))
409 prom.set_value(false);
410 else
411 prom.set_value(true);
412 }
413
414 // ----------------------------------------------------------------------------
415 // Function checkForNewerVersion()
416 // ----------------------------------------------------------------------------
417
_checkForNewerVersion(VersionCheck & me,std::promise<bool> prom)418 inline void _checkForNewerVersion(VersionCheck & me, std::promise<bool> prom)
419 {
420 if (me._path.empty()) // neither home dir nor temp dir are writable
421 {
422 prom.set_value(false);
423 return;
424 }
425
426 std::string version_filename(me._path + "/" + me._name + ".version");
427 double min_time_diff(86400); // one day = 86400 seonds
428 double file_time_diff(_getFileTimeDiff(me)); // time difference in seconds
429
430 if (file_time_diff < min_time_diff)
431 {
432 prom.set_value(false); // only check for newer version once a day
433 return;
434 }
435
436 std::vector<std::string> str_server_versions{"", ""};
437 _readVersionStrings(str_server_versions, version_filename);
438
439 #if !defined(NDEBUG) || defined(SEQAN_TEST_VERSION_CHECK_) // only check seqan version in debug or testing mode
440 if (!str_server_versions[1].empty()) // seqan version
441 {
442 std::string seqan_version = std::to_string(SEQAN_VERSION_MAJOR) + "." +
443 std::to_string(SEQAN_VERSION_MINOR) + "." +
444 std::to_string(SEQAN_VERSION_PATCH);
445 Lexical<> version_comp(_getNumbersFromString(seqan_version), _getNumbersFromString(str_server_versions[1]));
446
447 if (isLess(version_comp))
448 me.errorStream << VersionControlTags_<>::MESSAGE_SEQAN_UPDATE;
449 }
450 if (str_server_versions[0] == VersionControlTags_<>::UNREGISTERED_APP)
451 me.errorStream << VersionControlTags_<>::MESSAGE_UNREGISTERED_APP;
452 #endif
453
454 if (!str_server_versions[0].empty() & !(str_server_versions[0] == VersionControlTags_<>::UNREGISTERED_APP)) // app version
455 {
456 Lexical<> version_comp(_getNumbersFromString(me._version), _getNumbersFromString(str_server_versions[0]));
457
458 #if defined(NDEBUG) || defined(SEQAN_TEST_VERSION_CHECK_) // only check app version in release or testing mode
459 if (isLess(version_comp))
460 me.errorStream << VersionControlTags_<>::MESSAGE_APP_UPDATE;
461 #endif // defined(NDEBUG) || defined(SEQAN_TEST_VERSION_CHECK_)
462
463 #if !defined(NDEBUG) || defined(SEQAN_TEST_VERSION_CHECK_) // only notify developer that app version should be updated on server
464 if (isGreater(version_comp))
465 me.errorStream << VersionControlTags_<>::MESSAGE_REGISTERED_APP_UPDATE;
466 #endif // !defined(NDEBUG) || defined(SEQAN_TEST_VERSION_CHECK_)
467 }
468
469 if (me._program.empty())
470 {
471 prom.set_value(false);
472 return;
473 }
474
475 // launch a seperate thread to not defer runtime.
476 std::thread(_callServer, me, std::move(prom)).detach();
477 }
478
479 } // namespace seqan
480 #endif //SEQAN_INCLUDE_ARG_PARSE_VERSION_CHECK_H_
481