rssguard/src/librssguard/miscellaneous/systemfactory.cpp
Martin Rotter e0c3f6f19b fix #657
2022-03-08 07:39:55 +01:00

286 lines
9.3 KiB
C++

// For license of this file, see <project-root-folder>/LICENSE.md.
#include "miscellaneous/systemfactory.h"
#include "gui/dialogs/formmain.h"
#include "gui/dialogs/formupdate.h"
#include "miscellaneous/application.h"
#include "miscellaneous/systemfactory.h"
#include "network-web/networkfactory.h"
#if defined(Q_OS_WIN)
#include <QSettings>
#endif
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QFuture>
#include <QFutureWatcher>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QString>
#include <QVersionNumber>
using UpdateCheck = QPair<UpdateInfo, QNetworkReply::NetworkError>;
SystemFactory::SystemFactory(QObject* parent) : QObject(parent) {}
SystemFactory::~SystemFactory() = default;
QRegularExpression SystemFactory::supportedUpdateFiles() {
#if defined(Q_OS_WIN)
return QRegularExpression(QSL(".+win.+\\.(exe|7z)"));
#elif defined(Q_OS_MACOS)
return QRegularExpression(QSL(".dmg"));
#elif defined(Q_OS_LINUX)
return QRegularExpression(QSL(".AppImage"));
#else
return QRegularExpression(QSL(".*"));
#endif
}
SystemFactory::AutoStartStatus SystemFactory::autoStartStatus() const {
// User registry way to auto-start the application on Windows.
#if defined(Q_OS_WIN)
QSettings registry_key(QSL("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"),
QSettings::Format::NativeFormat);
const bool autostart_enabled = registry_key.value(QSL(APP_LOW_NAME),
QString()).toString().replace(QL1C('\\'),
QL1C('/')) ==
Application::applicationFilePath();
if (autostart_enabled) {
return AutoStartStatus::Enabled;
}
else {
return AutoStartStatus::Disabled;
}
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
// Use proper freedesktop.org way to auto-start the application.
// INFO: http://standards.freedesktop.org/autostart-spec/latest/
const QString desktop_file_location = autostartDesktopFileLocation();
// No correct path was found.
if (desktop_file_location.isEmpty()) {
qWarningNN << LOGSEC_GUI << "Searching for auto-start function status failed. HOME variable not found.";
return AutoStartStatus::Unavailable;
}
// We found correct path, now check if file exists and return correct status.
if (QFile::exists(desktop_file_location)) {
// File exists, we must read it and check if "Hidden" attribute is defined and what is its value.
QSettings desktop_settings(desktop_file_location, QSettings::IniFormat);
bool hidden_value = desktop_settings.value(QSL("Desktop Entry/Hidden"), false).toBool();
return hidden_value ? AutoStartStatus::Disabled : AutoStartStatus::Enabled;
}
else {
return AutoStartStatus::Disabled;
}
#else
// Disable auto-start functionality on unsupported platforms.
return AutoStartStatus::Unavailable;
#endif
}
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
QString SystemFactory::autostartDesktopFileLocation() const {
const QString xdg_config_path(qgetenv("XDG_CONFIG_HOME"));
QString desktop_file_location;
if (!xdg_config_path.isEmpty()) {
// XDG_CONFIG_HOME variable is specified. Look for .desktop file
// in 'autostart' subdirectory.
desktop_file_location = xdg_config_path + QSL("/autostart/") + APP_DESKTOP_ENTRY_FILE;
}
else {
// Desired variable is not set, look for the default 'autostart' subdirectory.
const QString home_directory(qgetenv("HOME"));
if (!home_directory.isEmpty()) {
// Home directory exists. Check if target .desktop file exists and
// return according status.
desktop_file_location = home_directory + QSL("/.config/autostart/") + APP_DESKTOP_ENTRY_FILE;
}
}
return desktop_file_location;
}
#endif
bool SystemFactory::setAutoStartStatus(AutoStartStatus new_status) {
const SystemFactory::AutoStartStatus current_status = SystemFactory::autoStartStatus();
// Auto-start feature is not even available, exit.
if (current_status == AutoStartStatus::Unavailable) {
return false;
}
#if defined(Q_OS_WIN)
QSettings registry_key(QSL("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"), QSettings::NativeFormat);
switch (new_status) {
case AutoStartStatus::Enabled:
registry_key.setValue(QSL(APP_LOW_NAME),
Application::applicationFilePath().replace(QL1C('/'), QL1C('\\')));
return true;
case AutoStartStatus::Disabled:
registry_key.remove(QSL(APP_LOW_NAME));
return true;
default:
return false;
}
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
// Note that we expect here that no other program uses
// "rssguard.desktop" desktop file.
const QString destination_file = autostartDesktopFileLocation();
const QString destination_folder = QFileInfo(destination_file).absolutePath();
switch (new_status) {
case AutoStartStatus::Enabled: {
if (QFile::exists(destination_file)) {
if (!QFile::remove(destination_file)) {
return false;
}
}
if (!QDir().mkpath(destination_folder)) {
return false;
}
const QString source_autostart_desktop_file = QString(APP_DESKTOP_ENTRY_PATH) + QDir::separator() + APP_DESKTOP_SOURCE_ENTRY_FILE;
return QFile::copy(source_autostart_desktop_file, destination_file);
}
case AutoStartStatus::Disabled:
return QFile::remove(destination_file);
default:
return false;
}
#else
return false;
#endif
}
QString SystemFactory::loggedInUser() const {
QString name = qEnvironmentVariable("USER");
if (name.isEmpty()) {
name = qEnvironmentVariable("USERNAME");
}
if (name.isEmpty()) {
name = tr("anonymous");
}
return name;
}
void SystemFactory::checkForUpdates() const {
auto* downloader = new Downloader();
connect(downloader, &Downloader::completed, this, [this, downloader]() {
QPair<QList<UpdateInfo>, QNetworkReply::NetworkError> result;
result.second = downloader->lastOutputError();
if (result.second == QNetworkReply::NoError) {
QByteArray obtained_data = downloader->lastOutputData();
result.first = parseUpdatesFile(obtained_data);
}
emit updatesChecked(result);
downloader->deleteLater();
});
downloader->downloadFile(QSL(RELEASES_LIST));
}
void SystemFactory::checkForUpdatesOnStartup() {
if (qApp->settings()->value(GROUP(General), SETTING(General::UpdateOnStartup)).toBool()) {
QObject::connect(qApp->system(), &SystemFactory::updatesChecked,
this, [&](const QPair<QList<UpdateInfo>, QNetworkReply::NetworkError>& updates) {
QObject::disconnect(qApp->system(), &SystemFactory::updatesChecked, this, nullptr);
if (!updates.first.isEmpty() &&
updates.second == QNetworkReply::NetworkError::NoError &&
SystemFactory::isVersionNewer(updates.first.at(0).m_availableVersion, QSL(APP_VERSION))) {
qApp->showGuiMessage(Notification::Event::NewAppVersionAvailable, {
QObject::tr("New version available"),
QObject::tr("Click the bubble for more information."),
QSystemTrayIcon::Information }, {}, {
tr("See new version info"),
[] {
FormUpdate(qApp->mainForm()).exec();
} });
}
});
qApp->system()->checkForUpdates();
}
}
bool SystemFactory::isVersionNewer(const QString& new_version, const QString& base_version) {
QVersionNumber nw = QVersionNumber::fromString(new_version);
QVersionNumber bs = QVersionNumber::fromString(base_version);
return nw > bs;
}
bool SystemFactory::isVersionEqualOrNewer(const QString& new_version, const QString& base_version) {
return new_version == base_version || isVersionNewer(new_version, base_version);
}
bool SystemFactory::openFolderFile(const QString& file_path) {
#if defined(Q_OS_WIN)
return QProcess::startDetached(QSL("explorer.exe"),
{ "/select,", QDir::toNativeSeparators(file_path) });
#else
const QString folder = QDir::toNativeSeparators(QFileInfo(file_path).absoluteDir().absolutePath());
return QDesktopServices::openUrl(QUrl::fromLocalFile(folder));
#endif
}
QList<UpdateInfo> SystemFactory::parseUpdatesFile(const QByteArray& updates_file) const {
QList<UpdateInfo> updates;
QJsonArray document = QJsonDocument::fromJson(updates_file).array();
for (QJsonValueRef i : document) {
QJsonObject release = i.toObject();
if (release[QSL("tag_name")].toString() == QSL("devbuild")) {
continue;
}
UpdateInfo update;
update.m_availableVersion = release[QSL("tag_name")].toString();
update.m_date = QDateTime::fromString(release[QSL("published_at")].toString(), QSL("yyyy-MM-ddTHH:mm:ssZ"));
update.m_changes = release[QSL("body")].toString();
QJsonArray assets = release[QSL("assets")].toArray();
for (QJsonValueRef j : assets) {
QJsonObject asset = j.toObject();
UpdateUrl url;
url.m_fileUrl = asset[QSL("browser_download_url")].toString();
url.m_name = asset[QSL("name")].toString();
url.m_size = asset[QSL("size")].toVariant().toString() + tr(" bytes");
update.m_urls.append(url);
}
updates.append(update);
}
std::sort(updates.begin(), updates.end(), [](const UpdateInfo& a, const UpdateInfo& b) -> bool {
return a.m_date > b.m_date;
});
return updates;
}