X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=src%2FUpdateChecker.cpp;h=734d91b6a1c29ca4d6f587311a53d19ea090b091;hb=ba7fa5aea592ce7e103c3d977b263b73b3944e99;hp=ddb352bcd142cdf9df06526a0fca379a44ed5e86;hpb=4bc32e75c65ad1952c9a254794e70f86f49ab09f;p=mutilities%2FMUtilities.git diff --git a/src/UpdateChecker.cpp b/src/UpdateChecker.cpp index ddb352b..734d91b 100644 --- a/src/UpdateChecker.cpp +++ b/src/UpdateChecker.cpp @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// // MuldeR's Utilities for Qt -// Copyright (C) 2004-2015 LoRd_MuldeR +// Copyright (C) 2004-2018 LoRd_MuldeR // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public @@ -27,12 +27,16 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include -using namespace MUtils; +#include "Mirrors.h" /////////////////////////////////////////////////////////////////////////////// // CONSTANTS @@ -47,159 +51,70 @@ static const char *mirror_url_postfix[] = NULL }; -static const char *update_mirrors_prim[] = -{ - "http://muldersoft.com/", - "http://mulder.bplaced.net/", - "http://mulder.cwsurf.de/", - "http://mulder.6te.net/", - "http://mulder.webuda.com/", //"http://mulder.byethost13.com/", - "http://mulder.pe.hu/", //"http://muldersoft.kilu.de/", - "http://muldersoft.square7.ch/", //"http://muldersoft.zxq.net/", - "http://muldersoft.co.nf/", - "http://muldersoft.eu.pn/", - "http://lamexp.sourceforge.net/", - "http://lordmulder.github.io/LameXP/", - "http://lord_mulder.bitbucket.org/", - "http://www.tricksoft.de/", - NULL -}; +static const int MIN_CONNSCORE = 5; +static const int QUICK_MIRRORS = 3; +static const int MAX_CONN_TIMEOUT = 16000; +static const int DOWNLOAD_TIMEOUT = 30000; -static const char *update_mirrors_back[] = -{ - "http://mplayer.savedonthe.net/", - NULL -}; +static const int VERSION_INFO_EXPIRES_MONTHS = 6; +static char *USER_AGENT_STR = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0"; /*use something innocuous*/ + +#define CHECK_CANCELLED() do \ +{ \ + if(MUTILS_BOOLIFY(m_cancelled)) \ + { \ + m_success.fetchAndStoreOrdered(0); \ + log("", "Update check has been cancelled by user!"); \ + setProgress(m_maxProgress); \ + setStatus(UpdateStatus_CancelledByUser); \ + return; \ + } \ +} \ +while(0) -static const char *known_hosts[] = //Taken form: http://www.alexa.com/topsites !!! +//////////////////////////////////////////////////////////// +// Helper Functions +//////////////////////////////////////////////////////////// + +static int getMaxProgress(void) { - "http://www.163.com/", - "http://www.7-zip.org/", - "http://www.ac3filter.net/", - "http://www.amazon.com/", - "http://antergos.com/", - "http://www.aol.com/", - "http://www.apache.org/", - "http://www.apple.com/", - "http://www.adobe.com/", - "http://web.audacityteam.org/", - "http://automattic.com/", - "http://www.avidemux.org/", - "http://www.babylon.com/", - "http://www.baidu.com/", - "http://bandcamp.com/", - "http://www.bbc.co.uk/", - "http://www.berlios.de/", - "http://www.bing.com/", - "http://www.bucketheadpikes.com/", - "http://www.ccc.de/", - "http://www.citizeninsomniac.com/WMV/", - "http://www.cnet.com/", - "http://cnzz.com/", - "http://www.codeplex.com/", - "http://www.der-postillon.com/", - "http://www.ebay.com/", - "http://www.equation.com/", - "http://fc2.com/", - "http://fedoraproject.org/wiki/Fedora_Project_Wiki", - "http://blog.fefe.de/", - "http://www.ffmpeg.org/", - "http://blog.flickr.net/en", - "http://free-codecs.com/", - "http://git-scm.com/", - "http://status.gitlab.com/", - "http://www.gmx.net/", - "http://news.gnome.org/", - "http://www.gnu.org/", - "http://go.com/", - "http://code.google.com/", - "http://haali.su/mkv/", - "http://www.heise.de/", - "http://www.huffingtonpost.co.uk/", - "http://www.iana.org/", - "http://www.imdb.com/", - "http://www.imgburn.com/", - "http://imgur.com/", - "http://www.jd.com/contact/", - "http://kannmanumdieuhrzeitschonnbierchentrinken.de/", - "http://mirrors.kernel.org/", - "http://komisar.gin.by/", - "http://lame.sourceforge.net/", - "http://www.libav.org/", - "http://blog.linkedin.com/", - "http://www.linuxmint.com/", - "http://www.livedoor.com/", - "http://www.livejournal.com/", - "http://go.mail.ru/", - "http://www.mediafire.com/about/", - "http://ftp.mozilla.org/", - "http://mplayerhq.hu/", - "http://www.msn.com/en-us/", - "http://wiki.multimedia.cx/", - "http://www.nch.com.au/", - "http://oss.netfarm.it/", - "http://www.nytimes.com/", - "http://www.opera.com/", - "http://www.partha.com/", - "http://pastie.org/", - "http://www.portablefreeware.com/", - "http://www.qt.io/", - "http://www.quakelive.com/", - "http://www.seamonkey-project.org/", - "http://www.shspvr.com/", - "http://www.sina.com.cn/", - "http://www.sohu.com/", - "http://www.sogou.com/", - "http://sourceforge.net/", - "http://www.spiegel.de/", - "http://stackoverflow.com/", - "http://tdm-gcc.tdragon.net/", - "http://www.tdrsmusic.com/", - "http://www.ubuntu.com/", - "http://www.uol.com.br/", - "http://www.videohelp.com/", - "http://www.videolan.org/", - "http://virtualdub.org/", - "http://blog.virustotal.com/", - "http://www.warr.org/buckethead.html", - "http://www.weibo.com/login.php", - "http://status.wikimedia.org/", - "http://www.winamp.com/", - "http://wpde.org/", - "http://x265.org/", - "http://xhmikosr.1f0.de/", - "http://xiph.org/", - "http://us.mail.yahoo.com/", - "http://www.yandex.ru/", - "http://www.youtube.com/yt/about/", - "http://www.zedo.com/", - "http://ffmpeg.zeranoe.com/", - NULL -}; + int counter = 0; + while (update_mirrors[counter]) + { + counter++; + } + counter += MIN_CONNSCORE + QUICK_MIRRORS + 2; + return counter; ; +} -static const int MIN_CONNSCORE = 8; -static const int VERSION_INFO_EXPIRES_MONTHS = 6; -static char *USER_AGENT_STR = "Mozilla/5.0 (X11; Linux i686; rv:7.0.1) Gecko/20111106 IceCat/7.0.1"; +static QStringList buildRandomList(const char *const values[]) +{ + QStringList list; + for (int index = 0; values[index]; index++) + { + const int pos = MUtils::next_rand_u32() % (index + 1); + list.insert(pos, QString::fromLatin1(values[index])); + } + return list; +} -//Helper function -static int getMaxProgress(void) +static const QHash *initEnvVars(void) { - int counter = MIN_CONNSCORE + 2; - for(int i = 0; update_mirrors_prim[i]; i++) counter++; - for(int i = 0; update_mirrors_back[i]; i++) counter++; - return counter; + QHash *const environment = new QHash(); + environment->insert(QLatin1String("CURL_HOME"), QDir::toNativeSeparators(MUtils::temp_folder())); + return environment; } //////////////////////////////////////////////////////////// // Update Info Class //////////////////////////////////////////////////////////// -UpdateCheckerInfo::UpdateCheckerInfo(void) +MUtils::UpdateCheckerInfo::UpdateCheckerInfo(void) { resetInfo(); } -void UpdateCheckerInfo::resetInfo(void) +void MUtils::UpdateCheckerInfo::resetInfo(void) { m_buildNo = 0; m_buildDate.setDate(1900, 1, 1); @@ -207,71 +122,90 @@ void UpdateCheckerInfo::resetInfo(void) m_downloadAddress.clear(); m_downloadFilename.clear(); m_downloadFilecode.clear(); + m_downloadChecksum.clear(); +} + +bool MUtils::UpdateCheckerInfo::isComplete(void) +{ + if(this->m_buildNo < 1) return false; + if(this->m_buildDate.year() < 2010) return false; + if(this->m_downloadSite.isEmpty()) return false; + if(this->m_downloadAddress.isEmpty()) return false; + if(this->m_downloadFilename.isEmpty()) return false; + if(this->m_downloadFilecode.isEmpty()) return false; + if(this->m_downloadChecksum.isEmpty()) return false; + + return true; } //////////////////////////////////////////////////////////// // Constructor & Destructor //////////////////////////////////////////////////////////// -UpdateChecker::UpdateChecker(const QString &binWGet, const QString &binGnuPG, const QString &binKeys, const QString &applicationId, const quint32 &installedBuildNo, const bool betaUpdates, const bool testMode) +MUtils::UpdateChecker::UpdateChecker(const QString &binCurl, const QString &binGnuPG, const QString &binKeys, const QString &applicationId, const quint32 &installedBuildNo, const bool betaUpdates, const bool testMode) : m_updateInfo(new UpdateCheckerInfo()), - m_binaryWGet(binWGet), + m_binaryCurl(binCurl), m_binaryGnuPG(binGnuPG), m_binaryKeys(binKeys), m_applicationId(applicationId), m_installedBuildNo(installedBuildNo), m_betaUpdates(betaUpdates), m_testMode(testMode), - m_maxProgress(getMaxProgress()) + m_maxProgress(getMaxProgress()), + m_environment(initEnvVars()) { - m_success = false; m_status = UpdateStatus_NotStartedYet; m_progress = 0; - if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty()) + if(m_binaryCurl.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty()) { MUTILS_THROW("Tools not initialized correctly!"); } } -UpdateChecker::~UpdateChecker(void) +MUtils::UpdateChecker::~UpdateChecker(void) +{ +} + +//////////////////////////////////////////////////////////// +// Public slots +//////////////////////////////////////////////////////////// + +void MUtils::UpdateChecker::start(Priority priority) { - delete m_updateInfo; + m_success.fetchAndStoreOrdered(0); + m_cancelled.fetchAndStoreOrdered(0); + QThread::start(priority); } //////////////////////////////////////////////////////////// // Protected functions //////////////////////////////////////////////////////////// -void UpdateChecker::run(void) +void MUtils::UpdateChecker::run(void) { qDebug("Update checker thread started!"); - MUTILS_EXCEPTION_HANDLER(m_testMode ? testKnownHosts() : checkForUpdates()); + MUTILS_EXCEPTION_HANDLER(m_testMode ? testMirrorsList() : checkForUpdates()); qDebug("Update checker thread completed."); } -void UpdateChecker::checkForUpdates(void) +void MUtils::UpdateChecker::checkForUpdates(void) { // ----- Initialization ----- // - m_success = false; m_updateInfo->resetInfo(); - seed_rand(); setProgress(0); // ----- Test Internet Connection ----- // - int connectionScore = 0; - int maxConnectTries = (3 * MIN_CONNSCORE) / 2; - - log("Checking internet connection..."); + log("Checking internet connection...", ""); setStatus(UpdateStatus_CheckingConnection); const int networkStatus = OS::network_status(); if(networkStatus == OS::NETWORK_TYPE_NON) { - log("", "Operating system reports that the computer is currently offline !!!"); + log("Operating system reports that the computer is currently offline !!!"); setProgress(m_maxProgress); setStatus(UpdateStatus_ErrorNoConnection); return; @@ -281,24 +215,37 @@ void UpdateChecker::checkForUpdates(void) // ----- Test Known Hosts Connectivity ----- // - QStringList hostList; - for(int i = 0; known_hosts[i]; i++) - { - hostList << QString::fromLatin1(known_hosts[i]); - } + int connectionScore = 0; + QStringList mirrorList = buildRandomList(known_hosts); - while(!(hostList.isEmpty() || (connectionScore >= MIN_CONNSCORE) || (maxConnectTries < 1))) + for(int connectionTimout = 1000; connectionTimout <= MAX_CONN_TIMEOUT; connectionTimout *= 2) { - switch(tryContactHost(hostList.takeAt(next_rand32() % hostList.count()))) + QElapsedTimer elapsedTimer; + elapsedTimer.start(); + const int globalTimout = 2 * MIN_CONNSCORE * connectionTimout; + while (!elapsedTimer.hasExpired(globalTimout)) { - case 01: connectionScore += 1; break; - case 02: connectionScore += 2; break; - default: maxConnectTries -= 1; break; + const QString hostName = mirrorList.takeFirst(); + if (tryContactHost(hostName, connectionTimout)) + { + connectionScore += 1; + setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1)); + elapsedTimer.restart(); + if (connectionScore >= MIN_CONNSCORE) + { + goto endLoop; /*success*/ + } + } + else + { + mirrorList.append(hostName); /*re-schedule*/ + } + CHECK_CANCELLED(); + msleep(1); } - setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1)); - msleep(64); } +endLoop: if(connectionScore < MIN_CONNSCORE) { log("", "Connectivity test has failed: Internet connection appears to be broken!"); @@ -307,53 +254,42 @@ void UpdateChecker::checkForUpdates(void) return; } - // ----- Build Mirror List ----- // + // ----- Fetch Update Info From Server ----- // - log("", "----", "", "Checking for updates online..."); + log("----", "", "Checking for updates online..."); setStatus(UpdateStatus_FetchingUpdates); - QStringList mirrorList; - for(int index = 0; update_mirrors_prim[index]; index++) - { - mirrorList << QString::fromLatin1(update_mirrors_prim[index]); - } - - if(const int len = mirrorList.count()) - { - const int rounds = len * 1097; - for(int i = 0; i < rounds; i++) - { - mirrorList.swap(i % len, next_rand32() % len); - } - } - - for(int index = 0; update_mirrors_back[index]; index++) - { - mirrorList << QString::fromLatin1(update_mirrors_back[index]); - } - - // ----- Fetch Update Info From Server ----- // + int mirrorCount = 0; + mirrorList = buildRandomList(update_mirrors); while(!mirrorList.isEmpty()) { - QString currentMirror = mirrorList.takeFirst(); setProgress(m_progress + 1); - if(!m_success) + const QString currentMirror = mirrorList.takeFirst(); + const bool isQuick = (mirrorCount++ < QUICK_MIRRORS); + if(tryUpdateMirror(m_updateInfo.data(), currentMirror, isQuick)) { - if(tryUpdateMirror(m_updateInfo, currentMirror)) - { - m_success = true; - } + m_success.ref(); /*success*/ + break; } - else + if (isQuick) { - msleep(64); + mirrorList.append(currentMirror); /*re-schedule*/ } + CHECK_CANCELLED(); + msleep(1); } - setProgress(m_maxProgress); + while (m_progress < m_maxProgress) + { + msleep(16); + setProgress(m_progress + 1); + CHECK_CANCELLED(); + } + + // ----- Generate final result ----- // - if(m_success) + if(MUTILS_BOOLIFY(m_success)) { if(m_updateInfo->m_buildNo > m_installedBuildNo) { @@ -374,38 +310,63 @@ void UpdateChecker::checkForUpdates(void) } } -void UpdateChecker::testKnownHosts(void) +void MUtils::UpdateChecker::testMirrorsList(void) { - QStringList hostList; - for(int i = 0; known_hosts[i]; i++) + // ----- Test update mirrors ----- // + + QStringList mirrorList; + for(int i = 0; update_mirrors[i]; i++) { - hostList << QString::fromLatin1(known_hosts[i]); + mirrorList << QString::fromLatin1(update_mirrors[i]); } - qDebug("\n[Known Hosts]"); - log("Testing all known hosts...", "", "---"); + qDebug("\n[Mirror Sites]"); + log("Testing all known mirror sites...", "", "---"); - int hostCount = hostList.count(); - while(!hostList.isEmpty()) + UpdateCheckerInfo updateInfo; + while (!mirrorList.isEmpty()) { - QString currentHost = hostList.takeFirst(); - qDebug("Testing: %s", currentHost.toLatin1().constData()); - log("", "Testing:", currentHost, ""); - QString outFile = QString("%1/%2.htm").arg(temp_folder(), rand_str()); - bool httpOk = false; - if(!getFile(currentHost, outFile, 0, &httpOk)) + const QString currentMirror = mirrorList.takeFirst(); + bool success = false; + qDebug("Testing: %s", MUTILS_L1STR(currentMirror)); + log("", "Testing:", currentMirror, ""); + for (quint8 attempt = 0; attempt < 3; ++attempt) { - if(httpOk) - { - qWarning("\nConnectivity test was SLOW on the following site:\n%s\n", currentHost.toLatin1().constData()); - } - else + updateInfo.resetInfo(); + if (tryUpdateMirror(&updateInfo, currentMirror, (!attempt))) { - qWarning("\nConnectivity test FAILED on the following site:\n%s\n", currentHost.toLatin1().constData()); + success = true; + break; } } + if (!success) + { + qWarning("\nUpdate mirror seems to be unavailable:\n%s\n", MUTILS_L1STR(currentMirror)); + } + log("", "---"); + } + + // ----- Test known hosts ----- // + + QStringList knownHostList; + for (int i = 0; known_hosts[i]; i++) + { + knownHostList << QString::fromLatin1(known_hosts[i]); + } + + qDebug("\n[Known Hosts]"); + log("Testing all known hosts...", "", "---"); + + while(!knownHostList.isEmpty()) + { + const QString currentHost = knownHostList.takeFirst(); + qDebug("Testing: %s", MUTILS_L1STR(currentHost)); + log(QLatin1String(""), "Testing:", currentHost, ""); + if (!tryContactHost(currentHost, DOWNLOAD_TIMEOUT)) + { + qWarning("\nConnectivity test FAILED on the following host:\n%s\n", MUTILS_L1STR(currentHost)); + } log("", "---"); - QFile::remove(outFile); } } @@ -413,7 +374,7 @@ void UpdateChecker::testKnownHosts(void) // PRIVATE FUNCTIONS //////////////////////////////////////////////////////////// -void UpdateChecker::setStatus(const int status) +void MUtils::UpdateChecker::setStatus(const int status) { if(m_status != status) { @@ -422,7 +383,7 @@ void UpdateChecker::setStatus(const int status) } } -void UpdateChecker::setProgress(const int progress) +void MUtils::UpdateChecker::setProgress(const int progress) { if(m_progress != progress) { @@ -431,7 +392,7 @@ void UpdateChecker::setProgress(const int progress) } } -void UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4) +void MUtils::UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4) { if(!str1.isNull()) emit messageLogged(str1); if(!str2.isNull()) emit messageLogged(str2); @@ -439,50 +400,30 @@ void UpdateChecker::log(const QString &str1, const QString &str2, const QString if(!str4.isNull()) emit messageLogged(str4); } -int UpdateChecker::tryContactHost(const QString &url) +bool MUtils::UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url, const bool &quick) { - int result = -1; bool httpOkay = false; - const QString outFile = QString("%1/%2.htm").arg(temp_folder(), rand_str()); - log("", "Testing host:", url); + bool success = false; + log("", "Trying mirror:", url, ""); - if(getFile(url, outFile, 0, &httpOkay)) - { - log("Connection to host was established successfully."); - result = 2; - } - else + if (quick) + { + if (!tryContactHost(QUrl(url).host(), (MAX_CONN_TIMEOUT / 10))) { - if(httpOkay) - { - log("Connection to host timed out after HTTP OK was received."); - result = 1; - } - else - { - log("Connection failed: The host could not be reached!"); - result = 0; - } + log("", "Mirror is too slow, skipping!"); + return false; } + } - QFile::remove(outFile); - return result; -} - -bool UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url) -{ - bool success = false; - log("", "Trying mirror:", url); - - const QString randPart = rand_str(); + const QString randPart = next_rand_str(); const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart); const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart); - if(getUpdateInfo(url, outFileVers, outFileSign)) + if (getUpdateInfo(url, outFileVers, outFileSign)) { log("", "Download okay, checking signature:"); - if(checkSignature(outFileVers, outFileSign)) + if (checkSignature(outFileVers, outFileSign)) { - log("", "Signature okay, parsing info:"); + log("", "Signature okay, parsing info:", ""); success = parseVersionInfo(outFileVers, updateInfo); } else @@ -501,141 +442,24 @@ bool UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString return success; } -bool UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign) -{ - log("", "Downloading update info:"); - if(!getFile(QString("%1%2" ).arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers)) - { - return false; - } - - log("", "Downloading signature:"); - if(!getFile(QString("%1%2.sig").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign)) - { - return false; - } - - return true; -} - -bool UpdateChecker::getFile(const QString &url, const QString &outFile, unsigned int maxRedir, bool *httpOk) +bool MUtils::UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign) { - QFileInfo output(outFile); - output.setCaching(false); - if(httpOk) *httpOk = false; - - if(output.exists()) + log("Downloading update info:", ""); + if(getFile(QString("%1%2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers)) { - QFile::remove(output.canonicalFilePath()); - if(output.exists()) + if (!m_cancelled) { - return false; - } - } - - QProcess process; - init_process(process, output.absolutePath()); - - QStringList args; - args << "-T" << "15" << "--no-cache" << "--no-dns-cache" << QString().sprintf("--max-redirect=%u", maxRedir); - args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR; - args << "-O" << output.fileName() << url; - - QEventLoop loop; - connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); - connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit())); - connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); - - QTimer timer; - timer.setSingleShot(true); - timer.setInterval(25000); - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - - const QRegExp httpResponseOK("200 OK$"); - - process.start(m_binaryWGet, args); - - if(!process.waitForStarted()) - { - return false; - } - - timer.start(); - - while(process.state() != QProcess::NotRunning) - { - loop.exec(); - const bool bTimeOut = (!timer.isActive()); - while(process.canReadLine()) - { - QString line = QString::fromLatin1(process.readLine()).simplified(); - if(line.contains(httpResponseOK)) + log("", "Downloading signature:", ""); + if (getFile(QString("%1%2.sig2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign)) { - line.append(" [OK]"); - if(httpOk) *httpOk = true; + return true; } - log(line); - } - if(bTimeOut) - { - qWarning("WGet process timed out <-- killing!"); - process.kill(); - process.waitForFinished(); - log("!!! TIMEOUT !!!"); - return false; - } - } - - timer.stop(); - timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - - log(QString().sprintf("Exited with code %d", process.exitCode())); - return (process.exitCode() == 0) && output.exists() && output.isFile(); -} - -bool UpdateChecker::checkSignature(const QString &file, const QString &signature) -{ - if(QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0) - { - qWarning("CheckSignature: File and signature should be in same folder!"); - return false; - } - - if(QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0) - { - qWarning("CheckSignature: File and keyring should be in same folder!"); - return false; - } - - QProcess process; - init_process(process, QFileInfo(file).absolutePath()); - - QEventLoop loop; - connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); - connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit())); - connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); - - process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(m_binaryKeys).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName()); - - if(!process.waitForStarted()) - { - return false; - } - - while(process.state() == QProcess::Running) - { - loop.exec(); - while(process.canReadLine()) - { - log(QString::fromLatin1(process.readLine()).simplified()); } } - - log(QString().sprintf("Exited with code %d", process.exitCode())); - return (process.exitCode() == 0); + return false; } -bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo) +bool MUtils::UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo) { QRegExp value("^(\\w+)=(.+)$"); QRegExp section("^\\[(.+)\\]$"); @@ -669,12 +493,12 @@ bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *upd if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0) { bool ok = false; - unsigned int temp = value.cap(2).toUInt(&ok); + const unsigned int temp = value.cap(2).toUInt(&ok); if(ok) updateInfo->m_buildNo = temp; } else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0) { - QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate); + const QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate); if(temp.isValid()) updateInfo->m_buildDate = temp; } else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0) @@ -693,6 +517,10 @@ bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *upd { updateInfo->m_downloadFilecode = value.cap(2).trimmed(); } + else if(value.cap(1).compare("DownloadChecksum", Qt::CaseInsensitive) == 0) + { + updateInfo->m_downloadChecksum = value.cap(2).trimmed(); + } } if(inHdr && (value.indexIn(line) >= 0)) { @@ -724,22 +552,212 @@ bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *upd log("Version info is from the future, take care!"); qWarning("Version info is from the future, take care!"); } + + if(!updateInfo->isComplete()) + { + log("WARNING: Version info is incomplete!"); + return false; + } + + return true; +} + +//---------------------------------------------------------- +// EXTERNAL TOOLS +//---------------------------------------------------------- + +bool MUtils::UpdateChecker::getFile(const QString &url, const QString &outFile, const unsigned int maxRedir) +{ + QFileInfo output(outFile); + output.setCaching(false); + + if (output.exists()) + { + QFile::remove(output.canonicalFilePath()); + if (output.exists()) + { + return false; + } + } + + QProcess process; + init_process(process, output.absolutePath(), true, NULL, m_environment.data()); + + QStringList args(QLatin1String("-vsSqkfL")); + args << "-m" << QString::number(DOWNLOAD_TIMEOUT / 1000); + args << "--max-redirs" << QString::number(maxRedir); + args << "-U" << USER_AGENT_STR; + args << "-o" << output.fileName() << url; + + QEventLoop loop; + connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); + connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit())); + connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); + + QTimer timer; + timer.setSingleShot(true); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + + process.start(m_binaryCurl, args); + if (!process.waitForStarted()) + { + return false; + } + + timer.start(2 * DOWNLOAD_TIMEOUT); + + while (process.state() != QProcess::NotRunning) + { + loop.exec(); + const bool bTimeOut = (!timer.isActive()); + while (process.canReadLine()) + { + const QString line = QString::fromLatin1(process.readLine()).simplified(); + if (!line.isEmpty()) + { + log(line); + } + } + if (bTimeOut || MUTILS_BOOLIFY(m_cancelled)) + { + qWarning("cURL process timed out <-- killing!"); + process.kill(); + process.waitForFinished(); + log(bTimeOut ? "PROCESS TIMEOUT !!!" : "CANCELLED BY USER !!!", ""); + return false; + } + } + + timer.stop(); + timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + + log(QString().sprintf("Exited with code %d", process.exitCode())); + return (process.exitCode() == 0) && output.exists() && output.isFile(); +} - bool complete = true; +bool MUtils::UpdateChecker::tryContactHost(const QString &hostname, const int &timeoutMsec) +{ + log(QString("Connecting to host: %1").arg(hostname), ""); - if(!(updateInfo->m_buildNo > 0)) complete = false; - if(!(updateInfo->m_buildDate.year() >= 2010)) complete = false; - if(updateInfo->m_downloadSite.isEmpty()) complete = false; - if(updateInfo->m_downloadAddress.isEmpty()) complete = false; - if(updateInfo->m_downloadFilename.isEmpty()) complete = false; - if(updateInfo->m_downloadFilecode.isEmpty()) complete = false; + QProcess process; + init_process(process, temp_folder(), true, NULL, m_environment.data()); + + QStringList args(QLatin1String("-vsSqkI")); + args << "-m" << QString::number(qMax(timeoutMsec, 1000) / 1000); + args << "-U" << USER_AGENT_STR; + args << "-o" << OS::null_device() << QString("http://%1/").arg(hostname); - if(!complete) + QEventLoop loop; + connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); + connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit())); + connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); + + QTimer timer; + timer.setSingleShot(true); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + + process.start(m_binaryCurl, args); + if (!process.waitForStarted()) { - log("WARNING: Version info is incomplete!"); + return false; } - return complete; + timer.start(2 * qMax(timeoutMsec, 1000)); + + while (process.state() != QProcess::NotRunning) + { + loop.exec(); + const bool bTimeOut = (!timer.isActive()); + while (process.canReadLine()) + { + const QString line = QString::fromLatin1(process.readLine()).simplified(); + if (!line.isEmpty()) + { + log(line); + } + } + if (bTimeOut || MUTILS_BOOLIFY(m_cancelled)) + { + qWarning("cURL process timed out <-- killing!"); + process.kill(); + process.waitForFinished(); + log(bTimeOut ? "PROCESS TIMEOUT !!!" : "CANCELLED BY USER !!!", ""); + return false; + } + } + + timer.stop(); + timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + + if (process.exitCode() != 0) + { + log("Connection has failed!"); + } + + log(QString().sprintf("Exited with code %d", process.exitCode()), ""); + return (process.exitCode() == 0); +} + +bool MUtils::UpdateChecker::checkSignature(const QString &file, const QString &signature) +{ + if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0) + { + qWarning("CheckSignature: File and signature should be in same folder!"); + return false; + } + + QString keyRingPath(m_binaryKeys); + bool removeKeyring = false; + if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0) + { + keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg"); + removeKeyring = true; + if (!QFile::copy(m_binaryKeys, keyRingPath)) + { + qWarning("CheckSignature: Failed to copy the key-ring file!"); + return false; + } + } + + QProcess process; + init_process(process, QFileInfo(file).absolutePath()); + + QEventLoop loop; + connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); + connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit())); + connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); + + process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(keyRingPath).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName()); + + if (!process.waitForStarted()) + { + if (removeKeyring) + { + remove_file(keyRingPath); + } + return false; + } + + while (process.state() == QProcess::Running) + { + loop.exec(); + while (process.canReadLine()) + { + const QString line = QString::fromLatin1(process.readLine()).simplified(); + if (!line.isEmpty()) + { + log(line); + } + } + } + + if (removeKeyring) + { + remove_file(keyRingPath); + } + + log(QString().sprintf("Exited with code %d", process.exitCode())); + return (process.exitCode() == 0); } ////////////////////////////////////////////////////////////