From 60ecc4fe8f47298e5a59c268b0e5439088e2272c Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 6 Sep 2018 14:03:07 +0800 Subject: [PATCH 1/3] Move python related functions Also the functions are slightly changed to return full path of the found python executable. --- src/base/preferences.cpp | 148 ------------------------------ src/base/preferences.h | 1 - src/base/utils/foreignapps.cpp | 159 +++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 149 deletions(-) diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index b86f94e9b..b0b8b5db6 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -43,7 +43,6 @@ #ifdef Q_OS_WIN #include -#include #include #endif @@ -878,153 +877,6 @@ void Preferences::disableRecursiveDownload(bool disable) } #ifdef Q_OS_WIN -namespace -{ - enum REG_SEARCH_TYPE - { - USER, - SYSTEM_32BIT, - SYSTEM_64BIT - }; - - QStringList getRegSubkeys(HKEY handle) - { - QStringList keys; - - DWORD cSubKeys = 0; - DWORD cMaxSubKeyLen = 0; - LONG res = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL); - - if (res == ERROR_SUCCESS) { - ++cMaxSubKeyLen; // For null character - LPWSTR lpName = new WCHAR[cMaxSubKeyLen]; - DWORD cName; - - for (DWORD i = 0; i < cSubKeys; ++i) { - cName = cMaxSubKeyLen; - res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL); - if (res == ERROR_SUCCESS) - keys.push_back(QString::fromWCharArray(lpName)); - } - - delete[] lpName; - } - - return keys; - } - - QString getRegValue(HKEY handle, const QString &name = QString()) - { - QString result; - - DWORD type = 0; - DWORD cbData = 0; - LPWSTR lpValueName = NULL; - if (!name.isEmpty()) { - lpValueName = new WCHAR[name.size() + 1]; - name.toWCharArray(lpValueName); - lpValueName[name.size()] = 0; - } - - // Discover the size of the value - ::RegQueryValueExW(handle, lpValueName, NULL, &type, NULL, &cbData); - DWORD cBuffer = (cbData / sizeof(WCHAR)) + 1; - LPWSTR lpData = new WCHAR[cBuffer]; - LONG res = ::RegQueryValueExW(handle, lpValueName, NULL, &type, (LPBYTE)lpData, &cbData); - if (lpValueName) - delete[] lpValueName; - - if (res == ERROR_SUCCESS) { - lpData[cBuffer - 1] = 0; - result = QString::fromWCharArray(lpData); - } - delete[] lpData; - - return result; - } - - QString pythonSearchReg(const REG_SEARCH_TYPE type) - { - HKEY hkRoot; - if (type == USER) - hkRoot = HKEY_CURRENT_USER; - else - hkRoot = HKEY_LOCAL_MACHINE; - - REGSAM samDesired = KEY_READ; - if (type == SYSTEM_32BIT) - samDesired |= KEY_WOW64_32KEY; - else if (type == SYSTEM_64BIT) - samDesired |= KEY_WOW64_64KEY; - - QString path; - LONG res = 0; - HKEY hkPythonCore; - res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore); - - if (res == ERROR_SUCCESS) { - QStringList versions = getRegSubkeys(hkPythonCore); - qDebug("Python versions nb: %d", versions.size()); - versions.sort(); - - bool found = false; - while (!found && !versions.empty()) { - const QString version = versions.takeLast() + "\\InstallPath"; - LPWSTR lpSubkey = new WCHAR[version.size() + 1]; - version.toWCharArray(lpSubkey); - lpSubkey[version.size()] = 0; - - HKEY hkInstallPath; - res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath); - delete[] lpSubkey; - - if (res == ERROR_SUCCESS) { - qDebug("Detected possible Python v%s location", qUtf8Printable(version)); - path = getRegValue(hkInstallPath); - ::RegCloseKey(hkInstallPath); - - if (!path.isEmpty() && QDir(path).exists("python.exe")) { - qDebug("Found python.exe at %s", qUtf8Printable(path)); - found = true; - } - } - } - - if (!found) - path = QString(); - - ::RegCloseKey(hkPythonCore); - } - - return path; - } -} - -QString Preferences::getPythonPath() -{ - QString path = pythonSearchReg(USER); - if (!path.isEmpty()) - return path; - - path = pythonSearchReg(SYSTEM_32BIT); - if (!path.isEmpty()) - return path; - - path = pythonSearchReg(SYSTEM_64BIT); - if (!path.isEmpty()) - return path; - - // Fallback: Detect python from default locations - const QStringList dirs = QDir("C:/").entryList(QStringList("Python*"), QDir::Dirs, QDir::Name | QDir::Reversed); - foreach (const QString &dir, dirs) { - const QString path("C:/" + dir + '/'); - if (QFile::exists(path + "python.exe")) - return path; - } - - return QString(); -} - bool Preferences::neverCheckFileAssoc() const { return value("Preferences/Win32/NeverCheckFileAssocation", false).toBool(); diff --git a/src/base/preferences.h b/src/base/preferences.h index 466305bb2..2c66ef04c 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -259,7 +259,6 @@ public: bool recursiveDownloadDisabled() const; void disableRecursiveDownload(bool disable = true); #ifdef Q_OS_WIN - static QString getPythonPath(); bool neverCheckFileAssoc() const; void setNeverCheckFileAssoc(bool check = true); static bool isTorrentFileAssocSet(); diff --git a/src/base/utils/foreignapps.cpp b/src/base/utils/foreignapps.cpp index 958b3186b..41ccf270e 100644 --- a/src/base/utils/foreignapps.cpp +++ b/src/base/utils/foreignapps.cpp @@ -29,11 +29,19 @@ #include "foreignapps.h" +#if defined(Q_OS_WIN) +#include +#endif + #include #include #include #include +#if defined(Q_OS_WIN) +#include +#endif + #include "base/logger.h" using namespace Utils::ForeignApps; @@ -75,6 +83,152 @@ namespace return false; } + +#if defined(Q_OS_WIN) + enum REG_SEARCH_TYPE + { + USER, + SYSTEM_32BIT, + SYSTEM_64BIT + }; + + QStringList getRegSubkeys(const HKEY handle) + { + QStringList keys; + + DWORD cSubKeys = 0; + DWORD cMaxSubKeyLen = 0; + LONG res = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL); + + if (res == ERROR_SUCCESS) { + ++cMaxSubKeyLen; // For null character + LPWSTR lpName = new WCHAR[cMaxSubKeyLen]; + DWORD cName; + + for (DWORD i = 0; i < cSubKeys; ++i) { + cName = cMaxSubKeyLen; + res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL); + if (res == ERROR_SUCCESS) + keys.push_back(QString::fromWCharArray(lpName)); + } + + delete[] lpName; + } + + return keys; + } + + QString getRegValue(const HKEY handle, const QString &name = QString()) + { + QString result; + + DWORD type = 0; + DWORD cbData = 0; + LPWSTR lpValueName = NULL; + if (!name.isEmpty()) { + lpValueName = new WCHAR[name.size() + 1]; + name.toWCharArray(lpValueName); + lpValueName[name.size()] = 0; + } + + // Discover the size of the value + ::RegQueryValueExW(handle, lpValueName, NULL, &type, NULL, &cbData); + DWORD cBuffer = (cbData / sizeof(WCHAR)) + 1; + LPWSTR lpData = new WCHAR[cBuffer]; + LONG res = ::RegQueryValueExW(handle, lpValueName, NULL, &type, (LPBYTE)lpData, &cbData); + if (lpValueName) + delete[] lpValueName; + + if (res == ERROR_SUCCESS) { + lpData[cBuffer - 1] = 0; + result = QString::fromWCharArray(lpData); + } + delete[] lpData; + + return result; + } + + QString pythonSearchReg(const REG_SEARCH_TYPE type) + { + HKEY hkRoot; + if (type == USER) + hkRoot = HKEY_CURRENT_USER; + else + hkRoot = HKEY_LOCAL_MACHINE; + + REGSAM samDesired = KEY_READ; + if (type == SYSTEM_32BIT) + samDesired |= KEY_WOW64_32KEY; + else if (type == SYSTEM_64BIT) + samDesired |= KEY_WOW64_64KEY; + + QString path; + LONG res = 0; + HKEY hkPythonCore; + res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore); + + if (res == ERROR_SUCCESS) { + QStringList versions = getRegSubkeys(hkPythonCore); + qDebug("Python versions nb: %d", versions.size()); + versions.sort(); + + bool found = false; + while (!found && !versions.empty()) { + const QString version = versions.takeLast() + "\\InstallPath"; + LPWSTR lpSubkey = new WCHAR[version.size() + 1]; + version.toWCharArray(lpSubkey); + lpSubkey[version.size()] = 0; + + HKEY hkInstallPath; + res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath); + delete[] lpSubkey; + + if (res == ERROR_SUCCESS) { + qDebug("Detected possible Python v%s location", qUtf8Printable(version)); + path = getRegValue(hkInstallPath); + ::RegCloseKey(hkInstallPath); + + if (!path.isEmpty() && QDir(path).exists("python.exe")) { + found = true; + path = QDir(path).filePath("python.exe"); + } + } + } + + if (!found) + path = QString(); + + ::RegCloseKey(hkPythonCore); + } + + return path; + } + + QString findPythonPath() + { + QString path = pythonSearchReg(USER); + if (!path.isEmpty()) + return path; + + path = pythonSearchReg(SYSTEM_32BIT); + if (!path.isEmpty()) + return path; + + path = pythonSearchReg(SYSTEM_64BIT); + if (!path.isEmpty()) + return path; + + // Fallback: Detect python from default locations + const QFileInfoList dirs = QDir("C:/").entryInfoList({"Python*"}, QDir::Dirs, (QDir::Name | QDir::Reversed)); + for (const QFileInfo &info : dirs) { + const QString path {info.absolutePath() + "/python.exe"}; + if (QFile::exists(path)) + return path; + } + + return QString(); + } +#endif } bool Utils::ForeignApps::PythonInfo::isValid() const @@ -99,6 +253,11 @@ PythonInfo Utils::ForeignApps::pythonInfo() if (testPythonInstallation("python", pyInfo)) return pyInfo; +#if defined(Q_OS_WIN) + if (testPythonInstallation(findPythonPath(), pyInfo)) + return pyInfo; +#endif + LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python not detected"), Log::INFO); } From 7d808cfc991a59e17b1c8124a21088c59988c08d Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 6 Sep 2018 15:31:14 +0800 Subject: [PATCH 2/3] Fix asking to install Python The dialog asking users to install python is borked since the last refactor, this commit fixes it. --- src/base/utils/foreignapps.cpp | 11 +++- src/base/utils/foreignapps.h | 1 + src/gui/mainwindow.cpp | 105 +++++++++++---------------------- src/gui/mainwindow.h | 1 - 4 files changed, 44 insertions(+), 74 deletions(-) diff --git a/src/base/utils/foreignapps.cpp b/src/base/utils/foreignapps.cpp index 41ccf270e..e31243e30 100644 --- a/src/base/utils/foreignapps.cpp +++ b/src/base/utils/foreignapps.cpp @@ -77,7 +77,8 @@ namespace return false; } - LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python detected, version: %1").arg(info.version), Log::INFO); + LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python detected, executable name: '%1', version: %2") + .arg(info.executableName, info.version), Log::INFO); return true; } @@ -236,6 +237,14 @@ bool Utils::ForeignApps::PythonInfo::isValid() const return (!executableName.isEmpty() && version.isValid()); } +bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const +{ + const int majorVer = version.majorNumber(); + return ((majorVer > 3) + || ((majorVer == 3) && (version >= Version {3, 3, 0})) + || ((majorVer == 2) && (version >= Version {2, 7, 9}))); +} + PythonInfo Utils::ForeignApps::pythonInfo() { static PythonInfo pyInfo; diff --git a/src/base/utils/foreignapps.h b/src/base/utils/foreignapps.h index ca427c4a6..2ec5d1378 100644 --- a/src/base/utils/foreignapps.h +++ b/src/base/utils/foreignapps.h @@ -42,6 +42,7 @@ namespace Utils using Version = Utils::Version; bool isValid() const; + bool isSupportedVersion() const; QString executableName; Version version; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index f1b8bc6ab..acac9ef9f 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -1784,67 +1784,50 @@ void MainWindow::on_actionRSSReader_triggered() void MainWindow::on_actionSearchWidget_triggered() { if (!m_hasPython && m_ui->actionSearchWidget->isChecked()) { - int majorVersion = Utils::ForeignApps::pythonInfo().version.majorNumber(); + const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo(); - // Check if python is already in PATH - if (majorVersion > 0) { - // Prevent translators from messing with PATH - Logger::instance()->addMessage(tr("Python found in %1: %2", "Python found in PATH: /usr/local/bin:/usr/bin:/etc/bin") - .arg("PATH", qgetenv("PATH").constData()), Log::INFO); - } -#ifdef Q_OS_WIN - else if (addPythonPathToEnv()) { - majorVersion = Utils::ForeignApps::pythonInfo().version.majorNumber(); - } -#endif - else { - QMessageBox::information(this, tr("Undetermined Python version"), tr("Couldn't determine your Python version. Search engine disabled.")); + // Not installed + if (!pyInfo.isValid()) { m_ui->actionSearchWidget->setChecked(false); Preferences::instance()->setSearchEnabled(false); - return; - } - - bool res = false; - - if ((majorVersion == 2) || (majorVersion == 3)) { - // Check Python minimum requirement: 2.7.9 / 3.3.0 - using Version = Utils::ForeignApps::PythonInfo::Version; - const Version pyVersion = Utils::ForeignApps::pythonInfo().version; - if (((majorVersion == 2) && (pyVersion < Version {2, 7, 9})) - || ((majorVersion == 3) && (pyVersion < Version {3, 3, 0}))) { - QMessageBox::information(this, tr("Old Python Interpreter"), tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: 2.7.9 / 3.3.0.").arg(pyVersion)); - m_ui->actionSearchWidget->setChecked(false); - Preferences::instance()->setSearchEnabled(false); - return; - } - - res = true; - } - - if (res) { - m_hasPython = true; - } #ifdef Q_OS_WIN - else if (QMessageBox::question(this, tr("Missing Python Interpreter"), - tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - // Download and Install Python - installPython(); - m_ui->actionSearchWidget->setChecked(false); - Preferences::instance()->setSearchEnabled(false); + const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Missing Python Runtime") + , tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?") + , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes); + if (buttonPressed == QMessageBox::Yes) + installPython(); +#else + QMessageBox::information(this, tr("Missing Python Runtime") + , tr("Python is required to use the search engine but it does not seem to be installed.")); +#endif return; } -#endif - else { -#ifndef Q_OS_WIN - QMessageBox::information(this, tr("Missing Python Interpreter"), tr("Python is required to use the search engine but it does not seem to be installed.")); -#endif + + // Check version requirement + if (!pyInfo.isSupportedVersion()) { m_ui->actionSearchWidget->setChecked(false); Preferences::instance()->setSearchEnabled(false); + +#ifdef Q_OS_WIN + const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Old Python Runtime") + , tr("Your Python version (%1) is outdated. Minimum requirement: 2.7.9 / 3.3.0.\nDo you want to install a newer version now?") + , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes); + if (buttonPressed == QMessageBox::Yes) + installPython(); +#else + QMessageBox::information(this, tr("Old Python Runtime") + , tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: 2.7.9 / 3.3.0.") + .arg(pyInfo.version)); +#endif return; } + + m_hasPython = true; + m_ui->actionSearchWidget->setChecked(true); + Preferences::instance()->setSearchEnabled(true); } + displaySearchTab(m_ui->actionSearchWidget->isChecked()); } @@ -2061,25 +2044,6 @@ void MainWindow::checkProgramUpdate() #endif #ifdef Q_OS_WIN -bool MainWindow::addPythonPathToEnv() -{ - if (m_hasPython) return true; - - QString pythonPath = Preferences::getPythonPath(); - if (!pythonPath.isEmpty()) { - Logger::instance()->addMessage(tr("Python found in '%1'").arg(Utils::Fs::toNativePath(pythonPath)), Log::INFO); - // Add it to PATH envvar - QString pathEnvar = QString::fromLocal8Bit(qgetenv("PATH").constData()); - if (pathEnvar.isNull()) - pathEnvar = ""; - pathEnvar = pythonPath + ';' + pathEnvar; - qDebug("New PATH envvar is: %s", qUtf8Printable(pathEnvar)); - qputenv("PATH", Utils::Fs::toNativePath(pathEnvar).toLocal8Bit()); - return true; - } - return false; -} - void MainWindow::installPython() { setCursor(QCursor(Qt::WaitCursor)); @@ -2123,10 +2087,7 @@ void MainWindow::pythonDownloadSuccess(const QString &url, const QString &filePa else Utils::Fs::forceRemove(filePath + ".msi"); // Reload search engine - m_hasPython = addPythonPathToEnv(); - if (m_hasPython) { - // Make it print the version to Log - Utils::ForeignApps::pythonInfo(); + if (Utils::ForeignApps::pythonInfo().isSupportedVersion()) { m_ui->actionSearchWidget->setChecked(true); displaySearchTab(true); } diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index 87428eefc..3d80066b7 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -201,7 +201,6 @@ private: QIcon getSystrayIcon() const; #endif #ifdef Q_OS_WIN - bool addPythonPathToEnv(); void installPython(); #endif From f26a5c358150194549be0e3aa07ea71081a14f90 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 6 Sep 2018 16:31:16 +0800 Subject: [PATCH 3/3] Update Python URLs --- src/base/utils/foreignapps.cpp | 2 +- src/gui/mainwindow.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/utils/foreignapps.cpp b/src/base/utils/foreignapps.cpp index e31243e30..6f27ab3d7 100644 --- a/src/base/utils/foreignapps.cpp +++ b/src/base/utils/foreignapps.cpp @@ -251,7 +251,7 @@ PythonInfo Utils::ForeignApps::pythonInfo() if (!pyInfo.isValid()) { #if defined(Q_OS_UNIX) // On Unix-Like Systems python2 and python3 should always exist - // https://legacy.python.org/dev/peps/pep-0394/ + // https://www.python.org/dev/peps/pep-0394/ if (testPythonInstallation("python3", pyInfo)) return pyInfo; if (testPythonInstallation("python2", pyInfo)) diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index acac9ef9f..9de5170e5 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -2049,7 +2049,7 @@ void MainWindow::installPython() setCursor(QCursor(Qt::WaitCursor)); // Download python const QString installerURL = ((QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) - ? "https://www.python.org/ftp/python/3.5.2/python-3.5.2.exe" + ? "https://www.python.org/ftp/python/3.6.6/python-3.6.6.exe" : "https://www.python.org/ftp/python/3.4.4/python-3.4.4.msi"); Net::DownloadHandler *handler = Net::DownloadManager::instance()->download( Net::DownloadRequest(installerURL).saveToFile(true));