diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/services')
18 files changed, 555 insertions, 1382 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp deleted file mode 100644 index 54b0905..0000000 --- a/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.cpp +++ /dev/null @@ -1,202 +0,0 @@ -#include "bugreporter.h" -#include "services/config.h" -#include "services/notifymanager.h" -#include <QNetworkAccessManager> -#include <QNetworkReply> -#include <QNetworkRequest> -#include <QUrlQuery> - -BugReporter::BugReporter(QObject *parent) : - QObject(parent) -{ - networkManager = new QNetworkAccessManager(this); - connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*))); -} - -QUrl BugReporter::getReporterEmailHelpUrl() const -{ - return QUrl(QString::fromLatin1(reporterEmailHelpUrl)); -} - -QUrl BugReporter::getReporterUserAndPasswordHelpUrl() const -{ - return QUrl(QString::fromLatin1(reporterUserPassHelpUrl)); -} - -void BugReporter::validateBugReportCredentials(const QString& login, const QString& password) -{ - if (credentialsValidationInProgress) - { - credentialsValidationInProgress->abort(); - credentialsValidationInProgress->deleteLater(); - } - - QUrlQuery query; - query.addQueryItem("validateUser", login); - query.addQueryItem("password", password); - - QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + query.query(QUrl::FullyEncoded)); - QNetworkRequest request(url); - credentialsValidationInProgress = networkManager->get(request); - replyToHandler[credentialsValidationInProgress] = [this](bool success, const QString& data) - { - if (success && data.trimmed() != "OK") - { - success = false; - emit credentialsValidationResult(success, tr("Invalid login or password")); - } - else - { - emit credentialsValidationResult(success, success ? QString() : data); - } - }; -} - -void BugReporter::abortCredentialsValidation() -{ - if (credentialsValidationInProgress) - { - credentialsValidationInProgress->abort(); - credentialsValidationInProgress->deleteLater(); - credentialsValidationInProgress = nullptr; - } -} - -void BugReporter::useBugReportCredentials(const QString& login, const QString& password) -{ - CFG_CORE.Internal.BugReportUser.set(login); - CFG_CORE.Internal.BugReportPassword.set(password); -} - -void BugReporter::clearBugReportCredentials() -{ - CFG_CORE.Internal.BugReportUser.set(QString()); - CFG_CORE.Internal.BugReportPassword.set(QString()); -} - -void BugReporter::reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, BugReporter::ResponseHandler responseHandler, const QString& urlSuffix) -{ - static_qstring(contentsTpl, "%1\n\n<b>Plugins loaded:</b>\n%2\n\n<b>Version:</b>\n%3\n\n<b>Operating System:</b>\n%4"); - QString contents = contentsTpl.arg(escapeParam(details), plugins, version, os); - - QUrlQuery query; - query.addQueryItem("brief", escapeParam(title)); - query.addQueryItem("contents", contents); - query.addQueryItem("os", os); - query.addQueryItem("version", version); - query.addQueryItem("featureRequest", "0"); - - QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + escapeUrl(query.query(QUrl::FullyEncoded) + urlSuffix)); - QNetworkRequest request(url); - QNetworkReply* reply = networkManager->get(request); - if (responseHandler) - replyToHandler[reply] = responseHandler; - - replyToTypeAndTitle[reply] = QPair<bool,QString>(false, title); -} - -void BugReporter::requestFeature(const QString& title, const QString& details, BugReporter::ResponseHandler responseHandler, const QString& urlSuffix) -{ - QUrlQuery query; - query.addQueryItem("brief", escapeParam(title)); - query.addQueryItem("contents", escapeParam(details)); - query.addQueryItem("featureRequest", "1"); - - QUrl url = QUrl(QString::fromLatin1(bugReportServiceUrl) + "?" + escapeUrl(query.query(QUrl::FullyEncoded) + urlSuffix)); - QNetworkRequest request(url); - QNetworkReply* reply = networkManager->get(request); - if (responseHandler) - replyToHandler[reply] = responseHandler; - - replyToTypeAndTitle[reply] = QPair<bool,QString>(true, title); -} - -QString BugReporter::escapeParam(const QString &input) -{ - return input.toHtmlEscaped(); -} - -QString BugReporter::escapeUrl(const QString &input) -{ - // For some reason the ";" character is not encodedy by QUrlQuery when using FullEncoded. Pity. We have to do it manually. - QString copy = input; - return copy.replace(";", "%3B"); -} - -void BugReporter::finished(QNetworkReply* reply) -{ - if (reply == credentialsValidationInProgress) - credentialsValidationInProgress = nullptr; - - if (!replyToHandler.contains(reply)) - { - reply->deleteLater(); - return; - } - - bool success = (reply->error() == QNetworkReply::NoError); - QString data; - if (success) - data = QString::fromLatin1(reply->readAll()); - else - data = reply->errorString(); - - replyToHandler[reply](success, data); - replyToHandler.remove(reply); - - if (replyToTypeAndTitle.contains(reply)) - { - if (success) - CFG->addReportHistory(replyToTypeAndTitle[reply].first, replyToTypeAndTitle[reply].second, data); - - replyToTypeAndTitle.remove(reply); - } - - reply->deleteLater(); -} - -void BugReporter::reportBug(const QString& email, const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, - ResponseHandler responseHandler) -{ - QUrlQuery query; - query.addQueryItem("byEmail", email); - QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); - - reportBug(title, details, version, os, plugins, responseHandler, urlSuffix); -} - -void BugReporter::reportBug(const QString& title, const QString& details, const QString& version, const QString& os, - const QString& plugins, ResponseHandler responseHandler) -{ - QString user = CFG_CORE.Internal.BugReportUser.get(); - QString pass = CFG_CORE.Internal.BugReportPassword.get(); - - QUrlQuery query; - query.addQueryItem("byUser", user); - query.addQueryItem("password", pass); - QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); - - reportBug(title, details, version, os, plugins, responseHandler, urlSuffix); -} - -void BugReporter::requestFeature(const QString& email, const QString& title, const QString& details, ResponseHandler responseHandler) -{ - QUrlQuery query; - query.addQueryItem("byEmail", email); - QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); - - requestFeature(title, details, responseHandler, urlSuffix); -} - -void BugReporter::requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler) -{ - QString user = CFG_CORE.Internal.BugReportUser.get(); - QString pass = CFG_CORE.Internal.BugReportPassword.get(); - - QUrlQuery query; - query.addQueryItem("byUser", user); - query.addQueryItem("password", pass); - QString urlSuffix = "&" + query.query(QUrl::FullyEncoded); - - requestFeature(title, details, responseHandler, urlSuffix); -} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h b/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h deleted file mode 100644 index 3e8eb8d..0000000 --- a/SQLiteStudio3/coreSQLiteStudio/services/bugreporter.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef BUGREPORTER_H -#define BUGREPORTER_H - -#include "common/global.h" -#include "sqlitestudio.h" -#include <QObject> -#include <QHash> - -class QNetworkAccessManager; -class QNetworkReply; - -class API_EXPORT BugReporter : public QObject -{ - Q_OBJECT - - public: - typedef std::function<void(bool success, const QString& data)> ResponseHandler; - - explicit BugReporter(QObject *parent = 0); - - QUrl getReporterEmailHelpUrl() const; - QUrl getReporterUserAndPasswordHelpUrl() const; - void validateBugReportCredentials(const QString& login, const QString& password); - void abortCredentialsValidation(); - void useBugReportCredentials(const QString& login, const QString& password); - void clearBugReportCredentials(); - - private: - void reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, - ResponseHandler responseHandler, const QString& urlSuffix); - void requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler, const QString& urlSuffix); - - static QString escapeParam(const QString& input); - static QString escapeUrl(const QString& input); - - QNetworkAccessManager* networkManager = nullptr; - QHash<QNetworkReply*,ResponseHandler> replyToHandler; - QHash<QNetworkReply*,QPair<bool,QString>> replyToTypeAndTitle; - QNetworkReply* credentialsValidationInProgress = nullptr; - - static_char* bugReportServiceUrl = "http://sqlitestudio.pl/report_bug3.rvt"; - static_char* reporterEmailHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Reporter_email_address"; - static_char* reporterUserPassHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Reporter_user_and_password"; - - signals: - void credentialsValidationResult(bool success, const QString& errorMessage); - - private slots: - void finished(QNetworkReply* reply); - - public slots: - void reportBug(const QString& email, const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, - ResponseHandler responseHandler = nullptr); - void reportBug(const QString& title, const QString& details, const QString& version, const QString& os, const QString& plugins, - ResponseHandler responseHandler = nullptr); - void requestFeature(const QString& email, const QString& title, const QString& details, ResponseHandler responseHandler = nullptr); - void requestFeature(const QString& title, const QString& details, ResponseHandler responseHandler = nullptr); -}; - -#define BUGS SQLITESTUDIO->getBugReporter() - -#endif // BUGREPORTER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/config.h b/SQLiteStudio3/coreSQLiteStudio/services/config.h index 1e4c410..202120a 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/config.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/config.h @@ -20,6 +20,8 @@ CFG_CATEGORIES(Core, CFG_CATEGORY(General, CFG_ENTRY(int, SqlHistorySize, 10000) CFG_ENTRY(int, DdlHistorySize, 1000) + CFG_ENTRY(int, BindParamsCacheSize, 1000) + CFG_ENTRY(int, PopulateHistorySize, 100) CFG_ENTRY(QString, LoadedPlugins, "") CFG_ENTRY(QVariantHash, ActiveCodeFormatter, QVariantHash()) CFG_ENTRY(bool, CheckUpdatesOnStartup, true) @@ -31,6 +33,7 @@ CFG_CATEGORIES(Core, CFG_CATEGORY(Internal, CFG_ENTRY(QVariantList, Functions, QVariantList()) CFG_ENTRY(QVariantList, Collations, QVariantList()) + CFG_ENTRY(QVariantList, Extensions, QVariantList()) CFG_ENTRY(QString, BugReportUser, QString()) CFG_ENTRY(QString, BugReportPassword, QString()) CFG_ENTRY(QString, BugReportRecentTitle, QString()) @@ -118,6 +121,7 @@ class API_EXPORT Config : public QObject virtual bool isMassSaving() const = 0; virtual void set(const QString& group, const QString& key, const QVariant& value) = 0; virtual QVariant get(const QString& group, const QString& key) = 0; + virtual QVariant get(const QString& group, const QString& key, const QVariant& defaultValue) = 0; virtual QHash<QString,QVariant> getAll() = 0; virtual bool addDb(const QString& name, const QString& path, const QHash<QString, QVariant> &options) = 0; @@ -144,6 +148,7 @@ class API_EXPORT Config : public QObject virtual qint64 addSqlHistory(const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) = 0; virtual void updateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) = 0; virtual void clearSqlHistory() = 0; + virtual void deleteSqlHistory(const QList<qint64>& ids) = 0; virtual QAbstractItemModel* getSqlHistoryModel() = 0; virtual void addCliHistory(const QString& text) = 0; @@ -151,6 +156,15 @@ class API_EXPORT Config : public QObject virtual void clearCliHistory() = 0; virtual QStringList getCliHistory() const = 0; + virtual void addBindParamHistory(const QVector<QPair<QString, QVariant>>& params) = 0; + virtual void applyBindParamHistoryLimit() = 0; + virtual QVector<QPair<QString, QVariant>> getBindParamHistory(const QStringList& paramNames) const = 0; + + virtual void addPopulateHistory(const QString& database, const QString& table, int rows, const QHash<QString, QPair<QString, QVariant>>& columnsPluginsConfig) = 0; + virtual void applyPopulateHistoryLimit() = 0; + virtual QHash<QString, QPair<QString, QVariant>> getPopulateHistory(const QString& database, const QString& table, int& rows) const = 0; + virtual QVariant getPopulateHistory(const QString& pluginName) const = 0; + virtual void addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) = 0; virtual QList<DdlHistoryEntryPtr> getDdlHistoryFor(const QString& dbName, const QString& dbFile, const QDate& date) = 0; virtual DdlHistoryModel* getDdlHistoryModel() = 0; diff --git a/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h index 52746e4..2b5fa41 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/dbmanager.h @@ -144,12 +144,13 @@ class API_EXPORT DbManager : public QObject /** * @brief Creates in-memory SQLite3 database. + * @param pureInit If true, avoids registering collations/functions/extensions in a database. Skips rich initialization and gives pure database connection. * @return Created database. * * Created database can be used for any purpose. Note that DbManager doesn't own created * database and it's up to the caller to delete the database when it's no longer needed. */ - virtual Db* createInMemDb() = 0; + virtual Db* createInMemDb(bool pureInit = false) = 0; /** * @brief Tells if given database is temporary. diff --git a/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h index 2581b4f..5fb4908 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/functionmanager.h @@ -3,6 +3,7 @@ #include "coreSQLiteStudio_global.h" #include "common/global.h" +#include <QVariant> #include <QList> #include <QSharedPointer> #include <QObject> diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp index 5876021..7d24e47 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/collationmanagerimpl.cpp @@ -27,7 +27,7 @@ QList<CollationManager::CollationPtr> CollationManagerImpl::getAllCollations() c QList<CollationManager::CollationPtr> CollationManagerImpl::getCollationsForDatabase(const QString& dbName) const { QList<CollationPtr> results; - foreach (const CollationPtr& coll, collations) + for (const CollationPtr& coll : collations) { if (coll->allDatabases || coll->databases.contains(dbName, Qt::CaseInsensitive)) results << coll; @@ -120,6 +120,6 @@ void CollationManagerImpl::loadFromConfig() void CollationManagerImpl::refreshCollationsByKey() { collationsByKey.clear(); - foreach (CollationPtr collation, collations) + for (CollationPtr collation : collations) collationsByKey[collation->name] = collation; } diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp index cf8b115..860e828 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.cpp @@ -4,6 +4,7 @@ #include "services/notifymanager.h" #include "sqlitestudio.h" #include "db/dbsqlite3.h" +#include "common/utils.h" #include <QtGlobal> #include <QDebug> #include <QList> @@ -94,11 +95,7 @@ bool ConfigImpl::isMassSaving() const void ConfigImpl::set(const QString &group, const QString &key, const QVariant &value) { - QByteArray bytes; - QDataStream stream(&bytes, QIODevice::WriteOnly); - stream << value; - - db->exec("INSERT OR REPLACE INTO settings VALUES (?, ?, ?)", {group, key, bytes}); + db->exec("INSERT OR REPLACE INTO settings VALUES (?, ?, ?)", {group, key, serializeToBytes(value)}); } QVariant ConfigImpl::get(const QString &group, const QString &key) @@ -107,6 +104,15 @@ QVariant ConfigImpl::get(const QString &group, const QString &key) return deserializeValue(results->getSingleCell()); } +QVariant ConfigImpl::get(const QString &group, const QString &key, const QVariant &defaultValue) +{ + QVariant value = get(group, key); + if (!value.isValid() || value.isNull()) + return defaultValue; + + return value; +} + QHash<QString,QVariant> ConfigImpl::getAll() { SqlQueryPtr results = db->exec("SELECT [group], [key], value FROM settings"); @@ -225,7 +231,7 @@ void ConfigImpl::storeGroups(const QList<DbGroupPtr>& groups) db->begin(); db->exec("DELETE FROM groups"); - foreach (const DbGroupPtr& group, groups) + for (const DbGroupPtr& group : groups) storeGroup(group); db->commit(); @@ -241,7 +247,7 @@ void ConfigImpl::storeGroup(const ConfigImpl::DbGroupPtr &group, qint64 parentId {group->name, group->order, parent, group->open, group->referencedDbName}); qint64 newParentId = results->getRegularInsertRowId(); - foreach (const DbGroupPtr& childGroup, group->childs) + for (const DbGroupPtr& childGroup : group->childs) storeGroup(childGroup, newParentId); } @@ -304,6 +310,11 @@ void ConfigImpl::clearSqlHistory() QtConcurrent::run(this, &ConfigImpl::asyncClearSqlHistory); } +void ConfigImpl::deleteSqlHistory(const QList<qint64>& ids) +{ + QtConcurrent::run(this, &ConfigImpl::asyncDeleteSqlHistory, ids); +} + QAbstractItemModel* ConfigImpl::getSqlHistoryModel() { if (!sqlHistoryModel) @@ -338,6 +349,122 @@ QStringList ConfigImpl::getCliHistory() const return results->columnAsList<QString>("text"); } +void ConfigImpl::addBindParamHistory(const QVector<QPair<QString, QVariant> >& params) +{ + QtConcurrent::run(this, &ConfigImpl::asyncAddBindParamHistory, params); +} + +void ConfigImpl::applyBindParamHistoryLimit() +{ + QtConcurrent::run(this, &ConfigImpl::asyncApplyBindParamHistoryLimit); +} + +QVector<QPair<QString, QVariant>> ConfigImpl::getBindParamHistory(const QStringList& paramNames) const +{ + static_qstring(directQuery, "SELECT id FROM bind_params WHERE pattern = ? ORDER BY id DESC"); + static_qstring(paramsByIdQuery, "SELECT name, value FROM bind_param_values WHERE bind_params_id = ? ORDER BY position"); + static_qstring(singleParamQuery, "SELECT value FROM bind_param_values WHERE %1 = ? ORDER BY id DESC LIMIT 1;"); + static_qstring(singleParamName, "name"); + static_qstring(singleParamPosition, "position"); + + QVector<QPair<QString, QVariant>> bindParams; + bindParams.reserve(paramNames.size()); + + SqlQueryPtr results = db->exec(directQuery, {paramNames.join(",")}); + if (results->isError()) + { + qWarning() << "Error while getting BindParams (1):" << db->getErrorText(); + return bindParams; + } + + // Got an exact match? Extract values and return. + QVariant exactMatch = results->getSingleCell(); + if (!exactMatch.isNull()) + { + results = db->exec(paramsByIdQuery, {exactMatch.toLongLong()}); + if (results->isError()) + { + qWarning() << "Error while getting BindParams (2):" << db->getErrorText(); + } + else + { + for (const SqlResultsRowPtr& row : results->getAll()) + bindParams << QPair<QString, QVariant>(row->value("name").toString(), row->value("value")); + } + return bindParams; + } + + // No exact match. Will look for values one by one using param name and position. + int position = 0; + for (const QString& bindParam : paramNames) + { + if (bindParam == "?") + results = db->exec(singleParamQuery.arg(singleParamPosition), {position}); + else + results = db->exec(singleParamQuery.arg(singleParamName), {bindParam}); + + bindParams << QPair<QString, QVariant>(bindParam, results->getSingleCell()); + position++; + } + return bindParams; +} + +void ConfigImpl::addPopulateHistory(const QString& database, const QString& table, int rows, const QHash<QString, QPair<QString, QVariant> >& columnsPluginsConfig) +{ + QtConcurrent::run(this, &ConfigImpl::asyncAddPopulateHistory, database, table, rows, columnsPluginsConfig); +} + +void ConfigImpl::applyPopulateHistoryLimit() +{ + QtConcurrent::run(this, &ConfigImpl::asyncApplyPopulateHistoryLimit); +} + +QHash<QString, QPair<QString, QVariant>> ConfigImpl::getPopulateHistory(const QString& database, const QString& table, int& rows) const +{ + static_qstring(initialQuery, "SELECT id, rows FROM populate_history WHERE [database] = ? AND [table] = ? ORDER BY id DESC LIMIT 1"); + static_qstring(columnsQuery, "SELECT column_name, plugin_name, plugin_config FROM populate_column_history WHERE populate_history_id = ?"); + + QHash<QString, QPair<QString, QVariant>> historyEntry; + SqlQueryPtr results = db->exec(initialQuery, {database, table}); + if (results->isError()) + { + qWarning() << "Error while getting Populating history entry (1):" << db->getErrorText(); + return historyEntry; + } + + if (!results->hasNext()) + return historyEntry; + + SqlResultsRowPtr row = results->next(); + qint64 historyEntryId = row->value("id").toLongLong(); + rows = row->value("rows").toInt(); + + results = db->exec(columnsQuery, {historyEntryId}); + QVariant value; + while (results->hasNext()) + { + row = results->next(); + value = deserializeValue(row->value("plugin_config")); + historyEntry[row->value("column_name").toString()] = QPair<QString, QVariant>(row->value("plugin_name").toString(), value); + } + + return historyEntry; +} + +QVariant ConfigImpl::getPopulateHistory(const QString& pluginName) const +{ + static_qstring(columnsQuery, "SELECT plugin_config FROM populate_column_history WHERE plugin_name = ? ORDER BY id DESC LiMIT 1"); + + SqlQueryPtr results = db->exec(columnsQuery, {pluginName}); + if (results->isError()) + { + qWarning() << "Error while getting Populating history entry (2):" << db->getErrorText(); + return QVariant(); + } + + return deserializeValue(results->getSingleCell()); +} + void ConfigImpl::addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) { QtConcurrent::run(this, &ConfigImpl::asyncAddDdlHistory, queries, dbName, dbFile); @@ -503,9 +630,9 @@ QString ConfigImpl::getPortableConfigPath() if (!file.isDir() || !file.isReadable() || !file.isWritable()) continue; - foreach (file, dir.entryInfoList()) + for (const QFileInfo& entryFile : dir.entryInfoList()) { - if (!file.isReadable() || !file.isWritable()) + if (!entryFile.isReadable() || !entryFile.isWritable()) continue; } @@ -522,8 +649,7 @@ void ConfigImpl::initTables() if (!tables.contains("version")) { - QString table; - foreach (table, tables) + for (const QString& table : tables) db->exec("DROP TABLE "+table); tables.clear(); @@ -554,6 +680,34 @@ void ConfigImpl::initTables() if (!tables.contains("reports_history")) db->exec("CREATE TABLE reports_history (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, feature_request BOOLEAN, title TEXT, url TEXT)"); + + if (!tables.contains("bind_params")) + { + db->exec("CREATE TABLE bind_params (id INTEGER PRIMARY KEY AUTOINCREMENT, pattern TEXT NOT NULL)"); + db->exec("CREATE INDEX bind_params_patt_idx ON bind_params (pattern);"); + } + + if (!tables.contains("bind_param_values")) + { + db->exec("CREATE TABLE bind_param_values (id INTEGER PRIMARY KEY AUTOINCREMENT, bind_params_id INTEGER REFERENCES bind_params (id) " + "ON DELETE CASCADE ON UPDATE CASCADE NOT NULL, position INTEGER NOT NULL, name TEXT NOT NULL, value)"); + db->exec("CREATE INDEX bind_param_values_fk_idx ON bind_param_values (bind_params_id);"); + } + + if (!tables.contains("populate_history")) + { + db->exec("CREATE TABLE populate_history (id INTEGER PRIMARY KEY AUTOINCREMENT, [database] TEXT NOT NULL, [table] TEXT NOT NULL, rows INTEGER NOT NULL)"); + } + + if (!tables.contains("populate_column_history")) + { + db->exec("CREATE TABLE populate_column_history (id INTEGER PRIMARY KEY AUTOINCREMENT, populate_history_id INTEGER REFERENCES populate_history (id) " + "ON DELETE CASCADE ON UPDATE CASCADE NOT NULL, column_name TEXT NOT NULL, plugin_name TEXT NOT NULL, plugin_config BLOB)"); + db->exec("CREATE INDEX populate_plugin_history_idx ON populate_column_history (plugin_name)"); + } + + if (!tables.contains("reports_history")) + db->exec("CREATE TABLE reports_history (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, feature_request BOOLEAN, title TEXT, url TEXT)"); } void ConfigImpl::initDbFile() @@ -646,19 +800,13 @@ bool ConfigImpl::tryInitDbFile(const QPair<QString, bool> &dbPath) return true; } -QVariant ConfigImpl::deserializeValue(const QVariant &value) +QVariant ConfigImpl::deserializeValue(const QVariant &value) const { if (!value.isValid()) return QVariant(); QByteArray bytes = value.toByteArray(); - if (bytes.isNull()) - return QVariant(); - - QVariant deserializedValue; - QDataStream stream(bytes); - stream >> deserializedValue; - return deserializedValue; + return deserializeFromBytes(bytes); } void ConfigImpl::asyncAddSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected) @@ -709,6 +857,23 @@ void ConfigImpl::asyncClearSqlHistory() emit sqlHistoryRefreshNeeded(); } +void ConfigImpl::asyncDeleteSqlHistory(const QList<qint64>& ids) +{ + if (!db->begin()) { + NOTIFY_MANAGER->warn(tr("Could not start database transaction for deleting SQL history, therefore it's not deleted.")); + return; + } + for (const qint64& id : ids) + db->exec("DELETE FROM sqleditor_history WHERE id = ?", id); + + if (!db->commit()) { + NOTIFY_MANAGER->warn(tr("Could not commit database transaction for deleting SQL history, therefore it's not deleted.")); + db->rollback(); + return; + } + emit sqlHistoryRefreshNeeded(); +} + void ConfigImpl::asyncAddCliHistory(const QString& text) { static_qstring(insertQuery, "INSERT INTO cli_history (text) VALUES (?)"); @@ -722,7 +887,7 @@ void ConfigImpl::asyncAddCliHistory(const QString& text) void ConfigImpl::asyncApplyCliHistoryLimit() { - static_qstring(limitQuery, "DELETE FROM cli_history WHERE id >= (SELECT id FROM cli_history ORDER BY id LIMIT 1 OFFSET %1)"); + static_qstring(limitQuery, "DELETE FROM cli_history WHERE id <= (SELECT id FROM cli_history ORDER BY id DESC LIMIT 1 OFFSET %1)"); SqlQueryPtr results = db->exec(limitQuery.arg(CFG_CORE.Console.HistorySize.get())); if (results->isError()) @@ -738,6 +903,105 @@ void ConfigImpl::asyncClearCliHistory() qWarning() << "Error while clearing CLI history:" << db->getErrorText(); } +void ConfigImpl::asyncAddBindParamHistory(const QVector<QPair<QString, QVariant> >& params) +{ + static_qstring(insertParamsQuery, "INSERT INTO bind_params (pattern) VALUES (?)"); + static_qstring(insertValuesQuery, "INSERT INTO bind_param_values (bind_params_id, position, name, value) VALUES (?, ?, ?, ?)"); + + if (!db->begin()) + { + qWarning() << "Failed to store BindParam cache, because could not begin SQL transaction. Details:" << db->getErrorText(); + return; + } + + QStringList paramNames; + for (const QPair<QString, QVariant>& paramPair : params) + paramNames << paramPair.first; + + SqlQueryPtr results = db->exec(insertParamsQuery, {paramNames.join(",")}); + RowId rowId = results->getInsertRowId(); + qint64 bindParamsId = rowId["ROWID"].toLongLong(); + + int position = 0; + for (const QPair<QString, QVariant>& paramPair : params) + { + results = db->exec(insertValuesQuery, {bindParamsId, position++, paramPair.first, paramPair.second}); + if (results->isError()) + { + qWarning() << "Failed to store BindParam cache, due to SQL error:" << db->getErrorText(); + db->rollback(); + return; + } + } + + if (!db->commit()) + { + qWarning() << "Failed to store BindParam cache, because could not commit SQL transaction. Details:" << db->getErrorText(); + db->rollback(); + } + + asyncApplyBindParamHistoryLimit(); +} + +void ConfigImpl::asyncApplyBindParamHistoryLimit() +{ + static_qstring(findBindParamIdQuery, "SELECT bind_params_id FROM bind_param_values ORDER BY id DESC LIMIT 1 OFFSET %1"); + static_qstring(limitBindParamsQuery, "DELETE FROM bind_params WHERE id <= ?"); // will cascade with FK to bind_param_values + + SqlQueryPtr results = db->exec(findBindParamIdQuery.arg(CFG_CORE.General.BindParamsCacheSize.get())); + if (results->isError()) + qWarning() << "Error while limiting BindParam history (step 1):" << db->getErrorText(); + + qint64 bindParamId = results->getSingleCell().toLongLong(); + results = db->exec(limitBindParamsQuery, {bindParamId}); + if (results->isError()) + qWarning() << "Error while limiting BindParam history (step 2):" << db->getErrorText(); +} + +void ConfigImpl::asyncAddPopulateHistory(const QString& database, const QString& table, int rows, const QHash<QString, QPair<QString, QVariant>>& columnsPluginsConfig) +{ + static_qstring(insertQuery, "INSERT INTO populate_history ([database], [table], rows) VALUES (?, ?, ?)"); + static_qstring(insertColumnQuery, "INSERT INTO populate_column_history (populate_history_id, column_name, plugin_name, plugin_config) VALUES (?, ?, ?, ?)"); + + if (!db->begin()) + { + qWarning() << "Failed to store Populating history entry, because could not begin SQL transaction. Details:" << db->getErrorText(); + return; + } + + SqlQueryPtr results = db->exec(insertQuery, {database, table, rows}); + RowId rowId = results->getInsertRowId(); + qint64 populateHistoryId = rowId["ROWID"].toLongLong(); + + for (QHash<QString, QPair<QString, QVariant>>::const_iterator colIt = columnsPluginsConfig.begin(); colIt != columnsPluginsConfig.end(); colIt++) + { + results = db->exec(insertColumnQuery, {populateHistoryId, colIt.key(), colIt.value().first, serializeToBytes(colIt.value().second)}); + if (results->isError()) + { + qWarning() << "Failed to store Populating history entry, due to SQL error:" << db->getErrorText(); + db->rollback(); + return; + } + } + + if (!db->commit()) + { + qWarning() << "Failed to store Populating history entry, because could not commit SQL transaction. Details:" << db->getErrorText(); + db->rollback(); + } + + asyncApplyPopulateHistoryLimit(); +} + +void ConfigImpl::asyncApplyPopulateHistoryLimit() +{ + static_qstring(limitQuery, "DELETE FROM populate_history WHERE id <= (SELECT id FROM populate_history ORDER BY id DESC LIMIT 1 OFFSET %1)"); + + SqlQueryPtr results = db->exec(limitQuery.arg(CFG_CORE.General.PopulateHistorySize.get())); + if (results->isError()) + qWarning() << "Error while limiting Populating history:" << db->getErrorText(); +} + void ConfigImpl::asyncAddDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile) { static_qstring(insert, "INSERT INTO ddl_history (dbname, file, timestamp, queries) VALUES (?, ?, ?, ?)"); @@ -799,7 +1063,12 @@ void ConfigImpl::mergeMasterConfig() if (masterConfigFile.isEmpty()) return; - qInfo() << "Updating settings from master configuration file: " << masterConfigFile; +#if QT_VERSION >= 0x050500 + qInfo() +#else + qDebug() +#endif + << "Updating settings from master configuration file: " << masterConfigFile; Db* masterDb = new DbSqlite3("SQLiteStudio master settings", masterConfigFile, {{DB_PURE_INIT, true}}); if (!masterDb->open()) diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h index 08bcec7..561aab4 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/configimpl.h @@ -29,6 +29,7 @@ class API_EXPORT ConfigImpl : public Config bool isMassSaving() const; void set(const QString& group, const QString& key, const QVariant& value); QVariant get(const QString& group, const QString& key); + QVariant get(const QString& group, const QString& key, const QVariant& defaultValue); QHash<QString,QVariant> getAll(); bool addDb(const QString& name, const QString& path, const QHash<QString, QVariant> &options); @@ -56,6 +57,7 @@ class API_EXPORT ConfigImpl : public Config qint64 addSqlHistory(const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); void updateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); void clearSqlHistory(); + void deleteSqlHistory(const QList<qint64>& ids); QAbstractItemModel* getSqlHistoryModel(); void addCliHistory(const QString& text); @@ -63,6 +65,15 @@ class API_EXPORT ConfigImpl : public Config void clearCliHistory(); QStringList getCliHistory() const; + void addBindParamHistory(const QVector<QPair<QString, QVariant>>& params); + void applyBindParamHistoryLimit(); + QVector<QPair<QString, QVariant>> getBindParamHistory(const QStringList& paramNames) const; + + void addPopulateHistory(const QString& database, const QString& table, int rows, const QHash<QString, QPair<QString, QVariant>>& columnsPluginsConfig); + void applyPopulateHistoryLimit(); + QHash<QString, QPair<QString, QVariant>> getPopulateHistory(const QString& database, const QString& table, int& rows) const; + QVariant getPopulateHistory(const QString& pluginName) const; + void addDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile); QList<DdlHistoryEntryPtr> getDdlHistoryFor(const QString& dbName, const QString& dbFile, const QDate& date); DdlHistoryModel* getDdlHistoryModel(); @@ -94,16 +105,23 @@ class API_EXPORT ConfigImpl : public Config void initTables(); void initDbFile(); bool tryInitDbFile(const QPair<QString, bool>& dbPath); - QVariant deserializeValue(const QVariant& value); + QVariant deserializeValue(const QVariant& value) const; void asyncAddSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); void asyncUpdateSqlHistory(qint64 id, const QString& sql, const QString& dbName, int timeSpentMillis, int rowsAffected); void asyncClearSqlHistory(); + void asyncDeleteSqlHistory(const QList<qint64> &ids); void asyncAddCliHistory(const QString& text); void asyncApplyCliHistoryLimit(); void asyncClearCliHistory(); + void asyncAddBindParamHistory(const QVector<QPair<QString, QVariant>>& params); + void asyncApplyBindParamHistoryLimit(); + + void asyncAddPopulateHistory(const QString& database, const QString& table, int rows, const QHash<QString, QPair<QString, QVariant>>& columnsPluginsConfig); + void asyncApplyPopulateHistoryLimit(); + void asyncAddDdlHistory(const QString& queries, const QString& dbName, const QString& dbFile); void asyncClearDdlHistory(); diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp index 74f482f..217c2b7 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.cpp @@ -23,7 +23,8 @@ DbManagerImpl::DbManagerImpl(QObject *parent) : DbManagerImpl::~DbManagerImpl() { - foreach (Db* db, dbList) +// qDebug() << "DbManagerImpl::~DbManagerImpl()"; + for (Db* db : dbList) { disconnect(db, SIGNAL(disconnected()), this, SLOT(dbDisconnectedSlot())); disconnect(db, SIGNAL(aboutToDisconnect(bool&)), this, SLOT(dbAboutToDisconnect(bool&))); @@ -261,12 +262,16 @@ Db* DbManagerImpl::getByPath(const QString &path) return pathToDb.value(pathDir.absolutePath()); } -Db* DbManagerImpl::createInMemDb() +Db* DbManagerImpl::createInMemDb(bool pureInit) { if (!inMemDbCreatorPlugin) return nullptr; - return inMemDbCreatorPlugin->getInstance("", ":memory:", {}); + QHash<QString, QVariant> opts; + if (pureInit) + opts[DB_PURE_INIT] = true; + + return inMemDbCreatorPlugin->getInstance("", ":memory:", opts); } bool DbManagerImpl::isTemporary(Db* db) @@ -341,7 +346,7 @@ void DbManagerImpl::loadInitialDbList() { QUrl url; InvalidDb* db = nullptr; - foreach (const Config::CfgDbPtr& cfgDb, CFG->dbList()) + for (const Config::CfgDbPtr& cfgDb : CFG->dbList()) { db = new InvalidDb(cfgDb->name, cfgDb->path, cfgDb->options); diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h index 2e3630a..5f99f86 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/dbmanagerimpl.h @@ -42,7 +42,7 @@ class API_EXPORT DbManagerImpl : public DbManager QStringList getDbNames(); Db* getByName(const QString& name, Qt::CaseSensitivity cs = Qt::CaseInsensitive); Db* getByPath(const QString& path); - Db* createInMemDb(); + Db* createInMemDb(bool pureInit = false); bool isTemporary(Db* db); QString quickAddDb(const QString &path, const QHash<QString, QVariant> &options); DbPlugin* getPluginForDbFile(const QString& filePath); diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp index 826b34b..2fcc689 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/functionmanagerimpl.cpp @@ -40,7 +40,7 @@ QList<FunctionManager::ScriptFunction*> FunctionManagerImpl::getAllScriptFunctio QList<FunctionManager::ScriptFunction*> FunctionManagerImpl::getScriptFunctionsForDatabase(const QString& dbName) const { QList<ScriptFunction*> results; - foreach (ScriptFunction* func, functions) + for (ScriptFunction* func : functions) { if (func->allDatabases || func->databases.contains(dbName, Qt::CaseInsensitive)) results << func; @@ -280,10 +280,10 @@ void FunctionManagerImpl::initNativeFunctions() void FunctionManagerImpl::refreshFunctionsByKey() { functionsByKey.clear(); - foreach (ScriptFunction* func, functions) + for (ScriptFunction* func : functions) functionsByKey[Key(func)] = func; - foreach (NativeFunction* func, nativeFunctions) + for (NativeFunction* func : nativeFunctions) nativeFunctionsByKey[Key(func)] = func; } @@ -291,7 +291,7 @@ void FunctionManagerImpl::storeInConfig() { QVariantList list; QHash<QString,QVariant> fnHash; - foreach (ScriptFunction* func, functions) + for (ScriptFunction* func : functions) { fnHash["name"] = func->name; fnHash["lang"] = func->lang; diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp index 9fe21de..c67156c 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/pluginmanagerimpl.cpp @@ -50,7 +50,7 @@ void PluginManagerImpl::deinit() emit aboutToQuit(); // Plugin containers and their plugins - foreach (PluginContainer* container, pluginContainer.values()) + for (PluginContainer* container : pluginContainer.values()) { if (container->builtIn) { @@ -61,13 +61,13 @@ void PluginManagerImpl::deinit() unload(container->name); } - foreach (PluginContainer* container, pluginContainer.values()) + for (PluginContainer* container : pluginContainer.values()) delete container; pluginContainer.clear(); // Types - foreach (PluginType* type, registeredPluginTypes) + for (PluginType* type : registeredPluginTypes) delete type; registeredPluginTypes.clear(); @@ -113,10 +113,10 @@ void PluginManagerImpl::scanPlugins() nameFilters << "*.so" << "*.dll" << "*.dylib"; QPluginLoader* loader = nullptr; - foreach (QString pluginDirPath, pluginDirs) + for (QString pluginDirPath : pluginDirs) { QDir pluginDir(pluginDirPath); - foreach (QString fileName, pluginDir.entryList(nameFilters, QDir::Files)) + for (QString fileName : pluginDir.entryList(nameFilters, QDir::Files)) { fileName = pluginDir.absoluteFilePath(fileName); loader = new QPluginLoader(fileName); @@ -158,7 +158,7 @@ bool PluginManagerImpl::initPlugin(QPluginLoader* loader, const QString& fileNam QJsonObject pluginMetaData = loader->metaData(); QString pluginTypeName = pluginMetaData.value("MetaData").toObject().value("type").toString(); PluginType* pluginType = nullptr; - foreach (PluginType* type, registeredPluginTypes) + for (PluginType* type : registeredPluginTypes) { if (type->getName() == pluginTypeName) { @@ -169,7 +169,7 @@ bool PluginManagerImpl::initPlugin(QPluginLoader* loader, const QString& fileNam if (!pluginType) { - qWarning() << "Could not load plugin" + fileName + "because its type was not recognized:" << pluginTypeName; + qWarning() << "Could not load plugin" << fileName << "because its type was not recognized:" << pluginTypeName; return false; } @@ -305,7 +305,7 @@ bool PluginManagerImpl::initPlugin(Plugin* plugin) { QString pluginName = plugin->getName(); PluginType* pluginType = nullptr; - foreach (PluginType* type, registeredPluginTypes) + for (PluginType* type : registeredPluginTypes) { if (type->test(plugin)) { @@ -316,7 +316,7 @@ bool PluginManagerImpl::initPlugin(Plugin* plugin) if (!pluginType) { - qWarning() << "Could not load built-in plugin" + pluginName + "because its type was not recognized."; + qWarning() << "Could not load built-in plugin" << pluginName << "because its type was not recognized."; return false; } @@ -363,7 +363,7 @@ QStringList PluginManagerImpl::getAllPluginNames(PluginType* type) const if (!pluginCategories.contains(type)) return names; - foreach (PluginContainer* container, pluginCategories[type]) + for (PluginContainer* container : pluginCategories[type]) names << container->name; return names; @@ -685,7 +685,7 @@ QList<Plugin*> PluginManagerImpl::getLoadedPlugins(PluginType* type) const if (!pluginCategories.contains(type)) return list; - foreach (PluginContainer* container, pluginCategories[type]) + for (PluginContainer* container : pluginCategories[type]) { if (container->loaded) list << container->plugin; @@ -769,7 +769,7 @@ bool PluginManagerImpl::arePluginsInitiallyLoaded() const QList<Plugin*> PluginManagerImpl::getLoadedPlugins() const { QList<Plugin*> plugins; - foreach (PluginContainer* container, pluginContainer.values()) + for (PluginContainer* container : pluginContainer.values()) { if (container->loaded) plugins << container->plugin; @@ -780,7 +780,7 @@ QList<Plugin*> PluginManagerImpl::getLoadedPlugins() const QStringList PluginManagerImpl::getLoadedPluginNames() const { QStringList names; - foreach (PluginContainer* container, pluginContainer.values()) + for (PluginContainer* container : pluginContainer.values()) { if (container->loaded) names << container->name; @@ -792,7 +792,7 @@ QList<PluginManager::PluginDetails> PluginManagerImpl::getAllPluginDetails() con { QList<PluginManager::PluginDetails> results; PluginManager::PluginDetails details; - foreach (PluginContainer* container, pluginContainer.values()) + for (PluginContainer* container : pluginContainer.values()) { details.name = container->name; details.title = container->title; diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/sqliteextensionmanagerimpl.cpp b/SQLiteStudio3/coreSQLiteStudio/services/impl/sqliteextensionmanagerimpl.cpp new file mode 100644 index 0000000..63dbaf6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/sqliteextensionmanagerimpl.cpp @@ -0,0 +1,70 @@ +#include "sqliteextensionmanagerimpl.h" +#include "services/notifymanager.h" +#include "services/dbmanager.h" + +SqliteExtensionManagerImpl::SqliteExtensionManagerImpl() +{ + init(); +} + +void SqliteExtensionManagerImpl::setExtensions(const QList<SqliteExtensionManager::ExtensionPtr>& newExtensions) +{ + extensions = newExtensions; + storeInConfig(); + emit extensionListChanged(); +} + +QList<SqliteExtensionManager::ExtensionPtr> SqliteExtensionManagerImpl::getAllExtensions() const +{ + return extensions; +} + +QList<SqliteExtensionManager::ExtensionPtr> SqliteExtensionManagerImpl::getExtensionForDatabase(const QString& dbName) const +{ + QList<ExtensionPtr> results; + for (const ExtensionPtr& ext : extensions) + { + if (ext->allDatabases || ext->databases.contains(dbName, Qt::CaseInsensitive)) + results << ext; + } + return results; +} + +void SqliteExtensionManagerImpl::init() +{ + loadFromConfig(); +} + +void SqliteExtensionManagerImpl::storeInConfig() +{ + QVariantList list; + QHash<QString,QVariant> extHash; + for (ExtensionPtr ext : extensions) + { + extHash["filePath"] = ext->filePath; + extHash["initFunc"] = ext->initFunc; + extHash["allDatabases"] = ext->allDatabases; + extHash["databases"] =common(DBLIST->getDbNames(), ext->databases); + list << extHash; + } + CFG_CORE.Internal.Extensions.set(list); +} + +void SqliteExtensionManagerImpl::loadFromConfig() +{ + extensions.clear(); + + QVariantList list = CFG_CORE.Internal.Extensions.get(); + QHash<QString,QVariant> extHash; + ExtensionPtr ext; + for (const QVariant& var : list) + { + extHash = var.toHash(); + ext = ExtensionPtr::create(); + ext->filePath = extHash["filePath"].toString(); + ext->initFunc = extHash["initFunc"].toString(); + ext->databases = extHash["databases"].toStringList(); + ext->allDatabases = extHash["allDatabases"].toBool(); + extensions << ext; + } +} diff --git a/SQLiteStudio3/coreSQLiteStudio/services/impl/sqliteextensionmanagerimpl.h b/SQLiteStudio3/coreSQLiteStudio/services/impl/sqliteextensionmanagerimpl.h new file mode 100644 index 0000000..6fd7f46 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/impl/sqliteextensionmanagerimpl.h @@ -0,0 +1,23 @@ +#ifndef SQLITEEXTENSIONMANAGERIMPL_H +#define SQLITEEXTENSIONMANAGERIMPL_H + +#include "services/sqliteextensionmanager.h" + +class SqliteExtensionManagerImpl : public SqliteExtensionManager +{ + public: + SqliteExtensionManagerImpl(); + + void setExtensions(const QList<ExtensionPtr>& newExtensions); + QList<ExtensionPtr> getAllExtensions() const; + QList<ExtensionPtr> getExtensionForDatabase(const QString& dbName) const; + + private: + void init(); + void storeInConfig(); + void loadFromConfig(); + + QList<ExtensionPtr> extensions; +}; + +#endif // SQLITEEXTENSIONMANAGERIMPL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h index 4f822bc..f771c2c 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/pluginmanager.h @@ -380,7 +380,7 @@ class API_EXPORT PluginManager : public QObject template <class T> PluginType* getPluginType() const { - foreach (PluginType* type, getPluginTypes()) + for (PluginType* type : getPluginTypes()) { if (!dynamic_cast<DefinedPluginType<T>*>(type)) continue; @@ -406,7 +406,7 @@ class API_EXPORT PluginManager : public QObject if (!type) return typedPlugins; - foreach (Plugin* plugin, getLoadedPlugins(type)) + for (Plugin* plugin : getLoadedPlugins(type)) typedPlugins << dynamic_cast<T*>(plugin); return typedPlugins; @@ -427,7 +427,7 @@ class API_EXPORT PluginManager : public QObject if (!type) return names; - foreach (Plugin* plugin, getLoadedPlugins(type)) + for (Plugin* plugin : getLoadedPlugins(type)) names << plugin->getName(); return names; diff --git a/SQLiteStudio3/coreSQLiteStudio/services/sqliteextensionmanager.h b/SQLiteStudio3/coreSQLiteStudio/services/sqliteextensionmanager.h new file mode 100644 index 0000000..a135f3b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/services/sqliteextensionmanager.h @@ -0,0 +1,34 @@ +#ifndef SQLITEEXTENSIONMANAGER_H +#define SQLITEEXTENSIONMANAGER_H + +#include "coreSQLiteStudio_global.h" +#include "sqlitestudio.h" +#include <QSharedPointer> +#include <QObject> + +class API_EXPORT SqliteExtensionManager : public QObject +{ + Q_OBJECT + + public: + struct API_EXPORT Extension + { + QString filePath; + QString initFunc; + QStringList databases; + bool allDatabases = true; + }; + + typedef QSharedPointer<Extension> ExtensionPtr; + + virtual void setExtensions(const QList<ExtensionPtr>& newExtensions) = 0; + virtual QList<ExtensionPtr> getAllExtensions() const = 0; + virtual QList<ExtensionPtr> getExtensionForDatabase(const QString& dbName) const = 0; + + signals: + void extensionListChanged(); +}; + +#define SQLITE_EXTENSIONS SQLITESTUDIO->getSqliteExtensionManager() + +#endif // SQLITEEXTENSIONMANAGER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp index 3663a1b..87df73b 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp @@ -1,461 +1,100 @@ #ifdef PORTABLE_CONFIG #include "updatemanager.h" -#include "services/pluginmanager.h" #include "services/notifymanager.h" #include "common/unused.h" -#include <QTemporaryDir> -#include <QNetworkAccessManager> -#include <QNetworkReply> -#include <QNetworkRequest> -#include <QUrl> -#include <QUrlQuery> #include <QDebug> +#include <QRegularExpression> #include <QCoreApplication> -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <QProcess> -#include <QThread> -#include <QtConcurrent/QtConcurrent> - -#ifdef Q_OS_WIN32 -#include "JlCompress.h" -#include <windows.h> -#include <shellapi.h> -#endif - -// Note on creating update packages: -// Packages for Linux and MacOSX should be an archive of _contents_ of SQLiteStudio directory, -// while for Windows it should be an archive of SQLiteStudio directory itself. - -QString UpdateManager::staticErrorMessage; -UpdateManager::RetryFunction UpdateManager::retryFunction = nullptr; +#include <QFileInfo> +#include <QtConcurrent/QtConcurrentRun> UpdateManager::UpdateManager(QObject *parent) : QObject(parent) { - networkManager = new QNetworkAccessManager(this); - connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*))); - connect(this, SIGNAL(updatingError(QString)), NOTIFY_MANAGER, SLOT(error(QString))); -} - -UpdateManager::~UpdateManager() -{ - cleanup(); -} + qRegisterMetaType<QList<UpdateManager::UpdateEntry>>(); -void UpdateManager::checkForUpdates(bool force) -{ - getUpdatesMetadata(updatesCheckReply, force); -} - -void UpdateManager::update() -{ - if (updatesGetUrlsReply || updatesInProgress) - return; - - getUpdatesMetadata(updatesGetUrlsReply); -} + connect(this, SIGNAL(updatingError(QString)), NOTIFY_MANAGER, SLOT(error(QString))); -QString UpdateManager::getPlatformForUpdate() const -{ -#if defined(Q_OS_LINUX) - if (QSysInfo::WordSize == 64) - return "linux64"; - else - return "linux32"; -#elif defined(Q_OS_WIN) - return "win32"; + QString updateBinary = +#if defined(Q_OS_WIN) + "UpdateSQLiteStudio.exe"; +#elif defined(Q_OS_LINUX) + "UpdateSQLiteStudio"; #elif defined(Q_OS_OSX) - return "macosx"; + "../../UpdateSQLiteStudio.app/Contents/MacOS/UpdateSQLiteStudio"; #else - return QString(); + ""; #endif -} - -QString UpdateManager::getCurrentVersions() const -{ - QJsonArray versionsArray; - - QJsonObject arrayEntry; - arrayEntry["component"] = "SQLiteStudio"; - arrayEntry["version"] = SQLITESTUDIO->getVersionString(); - versionsArray.append(arrayEntry); - - for (const PluginManager::PluginDetails& details : PLUGINS->getAllPluginDetails()) - { - if (details.builtIn) - continue; - arrayEntry["component"] = details.name; - arrayEntry["version"] = details.versionString; - versionsArray.append(arrayEntry); + if (!updateBinary.isEmpty()) { + updateBinaryAbsolutePath = QFileInfo(QCoreApplication::applicationDirPath() + "/" + updateBinary).absoluteFilePath(); } - - QJsonObject topObj; - topObj["versions"] = versionsArray; - - QJsonDocument doc(topObj); - return QString::fromLatin1(doc.toJson(QJsonDocument::Compact)); -} - -bool UpdateManager::isPlatformEligibleForUpdate() const -{ - return !getPlatformForUpdate().isNull() && getDistributionType() != DistributionType::OS_MANAGED; -} - -#if defined(Q_OS_WIN32) -bool UpdateManager::executePreFinalStepWin(const QString &tempDir, const QString &backupDir, const QString &appDir, bool reqAdmin) -{ - bool res; - if (reqAdmin) - res = executeFinalStepAsRootWin(tempDir, backupDir, appDir); - else - res = executeFinalStep(tempDir, backupDir, appDir); - - if (res) - { - QFileInfo path(qApp->applicationFilePath()); - QProcess::startDetached(appDir + "/" + path.fileName(), {WIN_POST_FINAL_UPDATE_OPTION_NAME, tempDir}); - } - return res; } -#endif -void UpdateManager::handleAvailableUpdatesReply(QNetworkReply* reply) -{ - if (reply->error() != QNetworkReply::NoError) - { - updatingFailed(tr("An error occurred while checking for updates: %1.").arg(reply->errorString())); - reply->deleteLater(); - return; - } - - QJsonParseError err; - QByteArray data = reply->readAll(); - reply->deleteLater(); - - QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) - { - qWarning() << "Invalid response from update service:" << err.errorString() << "\n" << "The data was:" << QString::fromLatin1(data); - notifyWarn(tr("Could not check available updates, because server responded with invalid message format. It is safe to ignore this warning.")); - return; - } - - QList<UpdateEntry> updates = readMetadata(doc); - if (updates.size() > 0) - emit updatesAvailable(updates); - else - emit noUpdatesAvailable(); -} - -void UpdateManager::getUpdatesMetadata(QNetworkReply*& replyStoragePointer, bool force) +UpdateManager::~UpdateManager() { -#ifdef PORTABLE_CONFIG - if ((!CFG_CORE.General.CheckUpdatesOnStartup.get() && !force) || !isPlatformEligibleForUpdate() || replyStoragePointer) - return; - - QUrlQuery query; - query.addQueryItem("platform", getPlatformForUpdate()); - query.addQueryItem("data", getCurrentVersions()); - QUrl url(QString::fromLatin1(updateServiceUrl) + "?" + query.query(QUrl::FullyEncoded)); - QNetworkRequest request(url); - replyStoragePointer = networkManager->get(request); -#endif } -void UpdateManager::handleUpdatesMetadata(QNetworkReply* reply) +void UpdateManager::checkForUpdates() { - if (reply->error() != QNetworkReply::NoError) - { - updatingFailed(tr("An error occurred while reading updates metadata: %1.").arg(reply->errorString())); - reply->deleteLater(); + if (!CFG_CORE.General.CheckUpdatesOnStartup.get()) return; - } - QJsonParseError err; - QByteArray data = reply->readAll(); - reply->deleteLater(); - - QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) - { - qWarning() << "Invalid response from update service for getting metadata:" << err.errorString() << "\n" << "The data was:" << QString::fromLatin1(data); - notifyWarn(tr("Could not download updates, because server responded with invalid message format. " - "You can try again later or download and install updates manually. See <a href=\"%1\">User Manual</a> for details.").arg(manualUpdatesHelpUrl)); + if (updateBinaryAbsolutePath.isEmpty()) { + qDebug() << "Updater binary not defined. Skipping updates checking."; return; } - tempDir = new QTemporaryDir(); - if (!tempDir->isValid()) { - notifyWarn(tr("Could not create temporary directory for downloading the update. Updating aborted.")); + if (!QFileInfo(updateBinaryAbsolutePath).exists()) { + QString errorDetails = tr("Updates installer executable is missing."); + emit updatingError(tr("Unable to check for updates (%1)").arg(errorDetails.trimmed())); + qWarning() << "Error while checking for updates: " << errorDetails; return; } - updatesInProgress = true; - updatesToDownload = readMetadata(doc); - totalDownloadsCount = updatesToDownload.size(); - totalPercent = 0; - - if (totalDownloadsCount == 0) - { - updatingFailed(tr("There was no updates to download. Updating aborted.")); - return; - } - - downloadUpdates(); + QtConcurrent::run(this, &UpdateManager::checkForUpdatesAsync); } -QList<UpdateManager::UpdateEntry> UpdateManager::readMetadata(const QJsonDocument& doc) +void UpdateManager::checkForUpdatesAsync() { - QList<UpdateEntry> updates; - UpdateEntry entry; - QJsonObject obj = doc.object(); - QJsonArray versionsArray = obj["newVersions"].toArray(); - QJsonObject entryObj; - for (const QJsonValue& value : versionsArray) - { - entryObj = value.toObject(); - entry.compontent = entryObj["component"].toString(); - entry.version = entryObj["version"].toString(); - entry.url = entryObj["url"].toString(); - updates << entry; - } - - return updates; -} - -void UpdateManager::downloadUpdates() -{ - if (updatesToDownload.size() == 0) + QProcess proc; + proc.start(updateBinaryAbsolutePath, {"--checkupdates"}); + if (!waitForProcess(proc)) { - QtConcurrent::run(this, &UpdateManager::installUpdates); - return; - } + QString errorDetails = QString::fromLocal8Bit(proc.readAllStandardError()); - UpdateEntry entry = updatesToDownload.takeFirst(); - currentJobTitle = tr("Downloading: %1").arg(entry.compontent); - emit updatingProgress(currentJobTitle, 0, totalPercent); + if (errorDetails.toLower().contains("no updates")) { + emit noUpdatesAvailable(); + return; + } - QStringList parts = entry.url.split("/"); - if (parts.size() < 1) - { - updatingFailed(tr("Could not determinate file name from update URL: %1. Updating aborted.").arg(entry.url)); - return; - } + if (errorDetails.isEmpty()) + errorDetails = tr("details are unknown"); - QString path = tempDir->path() + QLatin1Char('/') + parts.last(); - currentDownloadFile = new QFile(path); - if (!currentDownloadFile->open(QIODevice::WriteOnly)) - { - updatingFailed(tr("Failed to open file '%1' for writting: %2. Updating aborted.").arg(path, currentDownloadFile->errorString())); + emit updatingError(tr("Unable to check for updates (%1)").arg(errorDetails.trimmed())); + qWarning() << "Error while checking for updates: " << errorDetails; return; } - updatesToInstall[entry.compontent] = path; - - QNetworkRequest request(QUrl(entry.url)); - updatesGetReply = networkManager->get(request); - connect(updatesGetReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64))); - connect(updatesGetReply, SIGNAL(readyRead()), this, SLOT(readDownload())); + processCheckResults(proc.readAllStandardOutput()); } -void UpdateManager::updatingFailed(const QString& errMsg) -{ - cleanup(); - updatesInProgress = false; - emit updatingError(errMsg); -} - -void UpdateManager::installUpdates() +void UpdateManager::update() { - currentJobTitle = tr("Installing updates."); - totalPercent = (totalDownloadsCount - updatesToDownload.size()) * 100 / (totalDownloadsCount + 1); - emit updatingProgress(currentJobTitle, 0, totalPercent); - - requireAdmin = doRequireAdminPrivileges(); - - QTemporaryDir installTempDir; - QString appDirName = QDir(getAppDirPath()).dirName(); - QString targetDir = installTempDir.path() + QLatin1Char('/') + appDirName; - if (!copyRecursively(getAppDirPath(), targetDir)) + bool success = QProcess::startDetached(updateBinaryAbsolutePath, {"--updater"}); + if (!success) { - updatingFailed(tr("Could not copy current application directory into %1 directory.").arg(installTempDir.path())); + emit updatingError(tr("Unable to run updater application (%1). Please report this.").arg(updateBinaryAbsolutePath)); return; } - emit updatingProgress(currentJobTitle, 40, totalPercent); - - int i = 0; - int updatesCnt = updatesToInstall.size(); - for (const QString& component : updatesToInstall.keys()) - { - if (!installComponent(component, targetDir)) - { - cleanup(); - updatesInProgress = false; - return; - } - i++; - emit updatingProgress(currentJobTitle, (30 + (50 / updatesCnt * i)), totalPercent); - } - - if (!executeFinalStep(targetDir)) - { - cleanup(); - updatesInProgress = false; - return; - } - - currentJobTitle = QString(); - totalPercent = 100; - emit updatingProgress(currentJobTitle, 100, totalPercent); - cleanup(); - updatesInProgress = false; -#ifdef Q_OS_WIN32 - installTempDir.setAutoRemove(false); -#endif - - SQLITESTUDIO->setImmediateQuit(true); qApp->exit(0); } -bool UpdateManager::executeFinalStep(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ - bool isWin = false; -#ifdef Q_OS_WIN32 - isWin = true; - - // Windows needs to wait for previus process to exit - QThread::sleep(3); - - QDir dir(backupDir); - QString dirName = dir.dirName(); - dir.cdUp(); - if (!dir.mkdir(dirName)) - { - staticUpdatingFailed(tr("Could not create directory %1.").arg(backupDir)); - return false; - } -#endif - while (!moveDir(appDir, backupDir, isWin)) - { - if (!retryFunction) - { - staticUpdatingFailed(tr("Could not rename directory %1 to %2.\nDetails: %3").arg(appDir, backupDir, staticErrorMessage)); - return false; - } - - if (!retryFunction(tr("Cannot not rename directory %1 to %2.\nDetails: %3").arg(appDir, backupDir, staticErrorMessage))) - return false; - } - - if (!moveDir(tempDir, appDir, isWin)) - { - if (!moveDir(backupDir, appDir, isWin)) - { - staticUpdatingFailed(tr("Could not move directory %1 to %2 and also failed to restore original directory, " - "so the original SQLiteStudio directory is now located at: %3").arg(tempDir, appDir, backupDir)); - } - else - { - staticUpdatingFailed(tr("Could not rename directory %1 to %2. Rolled back to the original SQLiteStudio version.").arg(tempDir, appDir)); - } - deleteDir(backupDir); - return false; - } - - deleteDir(backupDir); - return true; -} - -bool UpdateManager::handleUpdateOptions(const QStringList& argList, int& returnCode) -{ - if (argList.size() == 5 && argList[1] == UPDATE_OPTION_NAME) - { - bool result = UpdateManager::executeFinalStep(argList[2], argList[3], argList[4]); - if (result) - returnCode = 0; - else - returnCode = 1; - - return true; - } - -#ifdef Q_OS_WIN32 - if (argList.size() == 6 && argList[1] == WIN_PRE_FINAL_UPDATE_OPTION_NAME) - { - bool result = UpdateManager::executePreFinalStepWin(argList[2], argList[3], argList[4], (bool)argList[5].toInt()); - if (result) - returnCode = 0; - else - returnCode = -1; - - return true; - } - - if (argList.size() == 3 && argList[1] == WIN_POST_FINAL_UPDATE_OPTION_NAME) - { - QThread::sleep(1); // to make sure that the previous process has quit - returnCode = 0; - UpdateManager::executePostFinalStepWin(argList[2]); - return true; - } -#endif - - return false; -} - -QString UpdateManager::getStaticErrorMessage() -{ - return staticErrorMessage; -} - -bool UpdateManager::executeFinalStep(const QString& tempDir) -{ - QString appDir = getAppDirPath(); - - // Find inexisting dir name next to app dir - QDir backupDir(getBackupDir(appDir)); - -#if defined(Q_OS_WIN32) - return runAnotherInstanceForUpdate(tempDir, backupDir.absolutePath(), qApp->applicationDirPath(), requireAdmin); -#else - bool res; - if (requireAdmin) - res = executeFinalStepAsRoot(tempDir, backupDir.absolutePath(), appDir); - else - res = executeFinalStep(tempDir, backupDir.absolutePath(), appDir); - - if (res) - QProcess::startDetached(qApp->applicationFilePath(), QStringList()); - - return res; -#endif -} - -bool UpdateManager::installComponent(const QString& component, const QString& tempDir) -{ - if (!unpackToDir(updatesToInstall[component], tempDir)) - { - updatingFailed(tr("Could not unpack component %1 into %2 directory.").arg(component, tempDir)); - return false; - } - - // In future here we might also delete/change some files, according to some update script. - return true; -} - -void UpdateManager::cleanup() +bool UpdateManager::isPlatformEligibleForUpdate() const { - safe_delete(currentDownloadFile); - safe_delete(tempDir); - updatesToDownload.clear(); - updatesToInstall.clear(); - requireAdmin = false; + return getDistributionType() != DistributionType::OS_MANAGED; } bool UpdateManager::waitForProcess(QProcess& proc) @@ -481,582 +120,33 @@ bool UpdateManager::waitForProcess(QProcess& proc) return true; } -QString UpdateManager::readError(QProcess& proc, bool reverseOrder) -{ - QString err = QString::fromLocal8Bit(reverseOrder ? proc.readAllStandardOutput() : proc.readAllStandardError()); - if (err.isEmpty()) - err = QString::fromLocal8Bit(reverseOrder ? proc.readAllStandardError() : proc.readAllStandardOutput()); - - QString errStr = proc.errorString(); - if (!errStr.isEmpty()) - err += "\n" + errStr; - - return err; -} - -void UpdateManager::staticUpdatingFailed(const QString& errMsg) +void UpdateManager::processCheckResults(const QByteArray &results) { -#if defined(Q_OS_WIN32) - staticErrorMessage = errMsg; -#else - UPDATES->handleStaticFail(errMsg); -#endif - qCritical() << errMsg; -} - -bool UpdateManager::executeFinalStepAsRoot(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ -#if defined(Q_OS_LINUX) - return executeFinalStepAsRootLinux(tempDir, backupDir, appDir); -#elif defined(Q_OS_WIN32) - return executeFinalStepAsRootWin(tempDir, backupDir, appDir); -#elif defined(Q_OS_MACX) - return executeFinalStepAsRootMac(tempDir, backupDir, appDir); -#else - qCritical() << "Unknown update platform in UpdateManager::executeFinalStepAsRoot() for package" << packagePath; - return false; -#endif -} - -#if defined(Q_OS_LINUX) -bool UpdateManager::executeFinalStepAsRootLinux(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ - QStringList args = {qApp->applicationFilePath(), UPDATE_OPTION_NAME, tempDir, backupDir, appDir}; - - QProcess proc; - LinuxPermElevator elevator = findPermElevatorForLinux(); - switch (elevator) - { - case LinuxPermElevator::KDESU: - proc.setProgram("kdesu"); - args.prepend("-t"); - proc.setArguments(args); - break; - case LinuxPermElevator::GKSU: - proc.setProgram("gksu"); // TODO test gksu updates - proc.setArguments(args); - break; - case LinuxPermElevator::PKEXEC: - { - // We call CLI for doing final step, because pkexec runs cmd completly in root env, so there's no X server. - args[0] += "cli"; - - QStringList newArgs; - for (const QString& arg : args) - newArgs << wrapCmdLineArgument(arg); - - QString cmd = "cd " + wrapCmdLineArgument(qApp->applicationDirPath()) +"; " + newArgs.join(" "); - - proc.setProgram("pkexec"); - proc.setArguments({"sh", "-c", cmd}); - } - break; - case LinuxPermElevator::NONE: - updatingFailed(tr("Could not find permissions elevator application to run update as a root. Looked for: %1").arg("kdesu, gksu, pkexec")); - return false; - } - - proc.start(); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Could not execute final updating steps as root: %1").arg(readError(proc, (elevator == LinuxPermElevator::KDESU)))); - return false; - } - - return true; -} -#endif - -#ifdef Q_OS_MACX -bool UpdateManager::executeFinalStepAsRootMac(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ - // Prepare script for updater - // osascript -e "do shell script \"stufftorunasroot\" with administrator privileges" - QStringList args = {wrapCmdLineArgument(qApp->applicationFilePath() + "cli"), - UPDATE_OPTION_NAME, - wrapCmdLineArgument(tempDir), - wrapCmdLineArgument(backupDir), - wrapCmdLineArgument(appDir)}; - QProcess proc; - - QString innerCmd = wrapCmdLineArgument(args.join(" ")); - - static_qstring(scriptTpl, "do shell script %1 with administrator privileges"); - QString scriptCmd = scriptTpl.arg(innerCmd); - - // Prepare updater temporary directory - QTemporaryDir updaterDir; - if (!updaterDir.isValid()) - { - updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(tr("Cannot create temporary directory for updater."))); - return false; - } - - // Create updater script - QString scriptPath = updaterDir.path() + "/UpdateSQLiteStudio.scpt"; - QFile updaterScript(scriptPath); - if (!updaterScript.open(QIODevice::WriteOnly)) - { - updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(tr("Cannot create updater script file."))); - return false; - } - updaterScript.write(scriptCmd.toLocal8Bit()); - updaterScript.close(); - - // Compile script to updater application - QString updaterApp = updaterDir.path() + "/UpdateSQLiteStudio.app"; - proc.setProgram("osacompile"); - proc.setArguments({"-o", updaterApp, scriptPath}); - proc.start(); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(readError(proc))); - return false; - } - - // Execute updater - proc.setProgram(updaterApp + "/Contents/MacOS/applet"); - proc.setArguments({}); - proc.start(); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(readError(proc))); - return false; - } - - // Validating update - // The updater script will not return error if the user canceled the password prompt. - // We need to check if the update was actually made and return true only then. - if (QDir(tempDir).exists()) - { - // Temp dir still exists, so it was not moved by root process - updatingFailed(tr("Updating canceled.")); - return false; - } - - return true; -} -#endif - -#ifdef Q_OS_WIN32 -bool UpdateManager::executeFinalStepAsRootWin(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ - QString updateBin = qApp->applicationDirPath() + "/" + WIN_UPDATER_BINARY; - - QString installFilePath = tempDir + "/" + WIN_INSTALL_FILE; - QFile installFile(installFilePath); - installFile.open(QIODevice::WriteOnly); - QString nl("\n"); - installFile.write(UPDATE_OPTION_NAME); - installFile.write(nl.toLocal8Bit()); - installFile.write(backupDir.toLocal8Bit()); - installFile.write(nl.toLocal8Bit()); - installFile.write(appDir.toLocal8Bit()); - installFile.write(nl.toLocal8Bit()); - installFile.close(); - - int res = (int)::ShellExecuteA(0, "runas", updateBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); - if (res < 32) - { - staticUpdatingFailed(tr("Could not execute final updating steps as administrator.")); - return false; - } - - // Since I suck as a developer and I cannot implement a simple synchronous app call under Windows - // (QProcess does it somehow, but I'm too lazy to look it up and probably the solution wouldn't be compatible - // with our "privileges elevation" trick above... so after all I think we're stuck with this solution for now), - // I do the workaround here, which makes this process wait for the other process to create the "done" - // file when it's done, so this process knows when the other has ended. This way we can proceed with this - // process and we will delete some directories later on, which were required by that other process. - if (!waitForFileToDisappear(installFilePath, 10)) - { - staticUpdatingFailed(tr("Could not execute final updating steps as administrator. Updater startup timed out.")); - return false; - } - - if (!waitForFileToAppear(appDir + QLatin1Char('/') + WIN_UPDATE_DONE_FILE, 30)) - { - staticUpdatingFailed(tr("Could not execute final updating steps as administrator. Updater operation timed out.")); - return false; - } - - return true; -} -#endif - -#if defined(Q_OS_WIN32) -bool UpdateManager::executePostFinalStepWin(const QString &tempDir) -{ - QString doneFile = qApp->applicationDirPath() + QLatin1Char('/') + WIN_UPDATE_DONE_FILE; - QFile::remove(doneFile); - - QDir dir(tempDir); - dir.cdUp(); - if (!deleteDir(dir.absolutePath())) - staticUpdatingFailed(tr("Could not clean up temporary directory %1. You can delete it manually at any time.").arg(dir.absolutePath())); - - QProcess::startDetached(qApp->applicationFilePath(), QStringList()); - return true; -} - -bool UpdateManager::waitForFileToDisappear(const QString &filePath, int seconds) -{ - QFile file(filePath); - while (file.exists() && seconds > 0) - { - QThread::sleep(1); - seconds--; - } - - return !file.exists(); -} - -bool UpdateManager::waitForFileToAppear(const QString &filePath, int seconds) -{ - QFile file(filePath); - while (!file.exists() && seconds > 0) - { - QThread::sleep(1); - seconds--; - } - - return file.exists(); -} - -bool UpdateManager::runAnotherInstanceForUpdate(const QString &tempDir, const QString &backupDir, const QString &appDir, bool reqAdmin) -{ - bool res = QProcess::startDetached(tempDir + "/SQLiteStudio.exe", {WIN_PRE_FINAL_UPDATE_OPTION_NAME, tempDir, backupDir, appDir, - QString::number((int)reqAdmin)}); - if (!res) - { - updatingFailed(tr("Could not run new version for continuing update.")); - return false; - } - - return true; -} -#endif - -UpdateManager::LinuxPermElevator UpdateManager::findPermElevatorForLinux() -{ -#if defined(Q_OS_LINUX) - QProcess proc; - proc.setProgram("which"); - - if (!SQLITESTUDIO->getEnv("DISPLAY").isEmpty()) - { - proc.setArguments({"kdesu"}); - proc.start(); - if (waitForProcess(proc)) - return LinuxPermElevator::KDESU; - - proc.setArguments({"gksu"}); - proc.start(); - if (waitForProcess(proc)) - return LinuxPermElevator::GKSU; - } - - proc.setArguments({"pkexec"}); - proc.start(); - if (waitForProcess(proc)) - return LinuxPermElevator::PKEXEC; -#endif - - return LinuxPermElevator::NONE; -} - -QString UpdateManager::wrapCmdLineArgument(const QString& arg) -{ - return "\"" + escapeCmdLineArgument(arg) + "\""; -} - -QString UpdateManager::escapeCmdLineArgument(const QString& arg) -{ - if (!arg.contains("\\") && !arg.contains("\"")) - return arg; - - QString str = arg; - return str.replace("\\", "\\\\").replace("\"", "\\\""); -} - -QString UpdateManager::getBackupDir(const QString &appDir) -{ - static_qstring(bakDirTpl, "%1.old%2"); - QDir backupDir(bakDirTpl.arg(appDir, "")); - int cnt = 1; - while (backupDir.exists()) - backupDir = QDir(bakDirTpl.arg(appDir, QString::number(cnt))); - - return backupDir.absolutePath(); -} - -bool UpdateManager::unpackToDir(const QString& packagePath, const QString& outputDir) -{ -#if defined(Q_OS_LINUX) - return unpackToDirLinux(packagePath, outputDir); -#elif defined(Q_OS_WIN32) - return unpackToDirWin(packagePath, outputDir); -#elif defined(Q_OS_MACX) - return unpackToDirMac(packagePath, outputDir); -#else - qCritical() << "Unknown update platform in UpdateManager::unpackToDir() for package" << packagePath; - return false; -#endif -} - -#if defined(Q_OS_LINUX) -bool UpdateManager::unpackToDirLinux(const QString &packagePath, const QString &outputDir) -{ - QProcess proc; - proc.setWorkingDirectory(outputDir); - proc.setStandardOutputFile(QProcess::nullDevice()); - proc.setStandardErrorFile(QProcess::nullDevice()); - - if (!packagePath.endsWith("tar.gz")) - { - updatingFailed(tr("Package not in tar.gz format, cannot install: %1").arg(packagePath)); - return false; - } - - proc.start("mv", {packagePath, outputDir}); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Package %1 cannot be installed, because cannot move it to directory: %2").arg(packagePath, outputDir)); - return false; - } - - QString fileName = packagePath.split("/").last(); - QString newPath = outputDir + "/" + fileName; - proc.start("tar", {"-xzf", newPath}); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Package %1 cannot be installed, because cannot unpack it: %2").arg(packagePath, readError(proc))); - return false; - } - - QProcess::execute("rm", {"-f", newPath}); - return true; -} -#endif - -#if defined(Q_OS_MACX) -bool UpdateManager::unpackToDirMac(const QString &packagePath, const QString &outputDir) -{ - QProcess proc; - proc.setWorkingDirectory(outputDir); - proc.setStandardOutputFile(QProcess::nullDevice()); - proc.setStandardErrorFile(QProcess::nullDevice()); - - if (!packagePath.endsWith("zip")) - { - updatingFailed(tr("Package not in zip format, cannot install: %1").arg(packagePath)); - return false; - } - - proc.start("unzip", {"-o", "-d", outputDir, packagePath}); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Package %1 cannot be installed, because cannot unzip it to directory %2: %3") - .arg(packagePath, outputDir, readError(proc))); - return false; - } - - return true; -} -#endif - -#if defined(Q_OS_WIN32) -bool UpdateManager::unpackToDirWin(const QString& packagePath, const QString& outputDir) -{ - if (JlCompress::extractDir(packagePath, outputDir + "/..").isEmpty()) - { - updatingFailed(tr("Package %1 cannot be installed, because cannot unzip it to directory: %2").arg(packagePath, outputDir)); - return false; - } - - return true; -} -#endif - -void UpdateManager::handleStaticFail(const QString& errMsg) -{ - emit updatingFailed(errMsg); -} - -QString UpdateManager::getAppDirPath() const -{ - static QString appDir; - if (appDir.isNull()) - { - appDir = qApp->applicationDirPath(); -#ifdef Q_OS_MACX - QDir tmpAppDir(appDir); - tmpAppDir.cdUp(); - tmpAppDir.cdUp(); - appDir = tmpAppDir.absolutePath(); -#endif - } - return appDir; -} - -bool UpdateManager::moveDir(const QString& src, const QString& dst, bool contentsOnly) -{ - // If we're doing a rename in the very same parent directory then we don't want - // the 'move between partitions' to be involved, cause any failure to rename - // is due to permissions or file lock. - QFileInfo srcFi(src); - QFileInfo dstFi(dst); - bool sameParentDir = (srcFi.dir() == dstFi.dir()); - - QDir dir; - if (contentsOnly) - { - QString localSrc; - QString localDst; - QDir srcDir(src); - for (const QFileInfo& entry : srcDir.entryInfoList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot|QDir::Hidden|QDir::System)) - { - localSrc = entry.absoluteFilePath(); - localDst = dst + "/" + entry.fileName(); - if (!dir.rename(localSrc, localDst) && (sameParentDir || !renameBetweenPartitions(localSrc, localDst))) - { - staticUpdatingFailed(tr("Could not rename directory %1 to %2.").arg(localSrc, localDst)); - return false; - } - } - } - else - { - if (!dir.rename(src, dst) && (sameParentDir || !renameBetweenPartitions(src, dst))) - { - staticUpdatingFailed(tr("Could not rename directory %1 to %2.").arg(src, dst)); - return false; - } - } - - return true; -} - -bool UpdateManager::deleteDir(const QString& path) -{ - QDir dir(path); - if (!dir.removeRecursively()) - { - staticUpdatingFailed(tr("Could not delete directory %1.").arg(path)); - return false; - } - - return true; -} - -bool UpdateManager::execCmd(const QString& cmd, const QStringList& args, QString* errorMsg) -{ - QProcess proc; - proc.start(cmd, args); - QString cmdString = QString("%1 \"%2\"").arg(cmd, args.join("\\\" \\\"")); - - if (!waitForProcess(proc)) - { - if (errorMsg) - *errorMsg = tr("Error executing update command: %1\nError message: %2").arg(cmdString).arg(readError(proc)); - - return false; - } - - return true; -} - -void UpdateManager::setRetryFunction(const RetryFunction &value) -{ - retryFunction = value; -} - -bool UpdateManager::doRequireAdminPrivileges() -{ - QString appDirPath = getAppDirPath(); - QDir appDir(appDirPath); - bool isWritable = isWritableRecursively(appDir.absolutePath()); - - appDir.cdUp(); - QFileInfo fi(appDir.absolutePath()); - isWritable &= fi.isWritable(); - - if (isWritable) - { - QDir backupDir(getBackupDir(appDirPath)); - QString backupDirName = backupDir.dirName(); - backupDir.cdUp(); - if (backupDir.mkdir(backupDirName)) - backupDir.rmdir(backupDirName); - else - isWritable = false; - } - - return !isWritable; -} - -void UpdateManager::finished(QNetworkReply* reply) -{ - if (reply == updatesCheckReply) - { - updatesCheckReply = nullptr; - handleAvailableUpdatesReply(reply); - return; - } - - if (reply == updatesGetUrlsReply) - { - updatesGetUrlsReply = nullptr; - handleUpdatesMetadata(reply); + if (results.trimmed().isEmpty()) { + emit noUpdatesAvailable(); return; } - if (reply == updatesGetReply) - { - handleDownloadReply(reply); - if (reply == updatesGetReply) // if no new download is requested - updatesGetReply = nullptr; + QRegularExpression re(R"(\<update\s+([^\>]+)\>)"); + QRegularExpression versionRe(R"(version\=\"([\d\.]+)\")"); + QRegularExpression nameRe(R"(name\=\"([^\"]+)\")"); - return; - } -} - -void UpdateManager::handleDownloadReply(QNetworkReply* reply) -{ - if (reply->error() != QNetworkReply::NoError) + QRegularExpressionMatchIterator reIter = re.globalMatch(results); + QString updateNode; + UpdateEntry theUpdate; + QList<UpdateEntry> updates; + while (reIter.hasNext()) { - updatingFailed(tr("An error occurred while downloading updates: %1. Updating aborted.").arg(reply->errorString())); - reply->deleteLater(); - return; + updateNode = reIter.next().captured(1); + theUpdate.version = versionRe.match(updateNode).captured(1); + theUpdate.compontent = nameRe.match(updateNode).captured(1); + updates << theUpdate; } - totalPercent = (totalDownloadsCount - updatesToDownload.size()) * 100 / (totalDownloadsCount + 1); - - readDownload(); - currentDownloadFile->close(); - - safe_delete(currentDownloadFile); - - reply->deleteLater(); - downloadUpdates(); -} - -void UpdateManager::downloadProgress(qint64 bytesReceived, qint64 totalBytes) -{ - int perc; - if (totalBytes < 0) - perc = -1; - else if (totalBytes == 0) - perc = 100; + if (updates.isEmpty()) + emit noUpdatesAvailable(); else - perc = bytesReceived * 100 / totalBytes; - - emit updatingProgress(currentJobTitle, perc, totalPercent); -} - -void UpdateManager::readDownload() -{ - currentDownloadFile->write(updatesGetReply->readAll()); + emit updatesAvailable(updates); } #endif // PORTABLE_CONFIG diff --git a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h index bb33487..50f4b6b 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h +++ b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.h @@ -18,122 +18,34 @@ class API_EXPORT UpdateManager : public QObject { Q_OBJECT public: - typedef std::function<bool(const QString& msg)> RetryFunction; - struct UpdateEntry { QString compontent; QString version; - QString url; }; explicit UpdateManager(QObject *parent = 0); ~UpdateManager(); - void checkForUpdates(bool force = false); + void checkForUpdates(); void update(); bool isPlatformEligibleForUpdate() const; - static bool executeFinalStep(const QString& tempDir, const QString& backupDir, const QString& appDir); - static bool handleUpdateOptions(const QStringList& argList, int& returnCode); - static QString getStaticErrorMessage(); - - static void setRetryFunction(const RetryFunction &value); - - static_char* UPDATE_OPTION_NAME = "--update-final-step"; - static_char* WIN_INSTALL_FILE = "install.dat"; - static_char* WIN_UPDATE_DONE_FILE = "UpdateFinished.lck"; private: - enum class LinuxPermElevator - { - KDESU, - GKSU, - PKEXEC, - NONE - }; + QString updateBinaryAbsolutePath; - QString getPlatformForUpdate() const; - QString getCurrentVersions() const; - void handleAvailableUpdatesReply(QNetworkReply* reply); - void handleDownloadReply(QNetworkReply* reply); - void getUpdatesMetadata(QNetworkReply*& replyStoragePointer, bool force = false); - void handleUpdatesMetadata(QNetworkReply* reply); - QList<UpdateEntry> readMetadata(const QJsonDocument& doc); - void downloadUpdates(); - void updatingFailed(const QString& errMsg); - void installUpdates(); - bool installComponent(const QString& component, const QString& tempDir); - bool executeFinalStep(const QString& tempDir); - bool executeFinalStepAsRoot(const QString& tempDir, const QString& backupDir, const QString& appDir); -#if defined(Q_OS_LINUX) - bool executeFinalStepAsRootLinux(const QString& tempDir, const QString& backupDir, const QString& appDir); - bool unpackToDirLinux(const QString& packagePath, const QString& outputDir); -#elif defined(Q_OS_MACX) - bool unpackToDirMac(const QString& packagePath, const QString& outputDir); - bool executeFinalStepAsRootMac(const QString& tempDir, const QString& backupDir, const QString& appDir); -#elif defined(Q_OS_WIN32) - bool runAnotherInstanceForUpdate(const QString& tempDir, const QString& backupDir, const QString& appDir, bool reqAdmin); - bool unpackToDirWin(const QString& packagePath, const QString& outputDir); -#endif - bool doRequireAdminPrivileges(); - bool unpackToDir(const QString& packagePath, const QString& outputDir); - void handleStaticFail(const QString& errMsg); - QString getAppDirPath() const; - void cleanup(); - - static bool moveDir(const QString& src, const QString& dst, bool contentsOnly = false); - static bool deleteDir(const QString& path); - static bool execCmd(const QString& cmd, const QStringList& args, QString* errorMsg = nullptr); - static bool waitForProcess(QProcess& proc); - static QString readError(QProcess& proc, bool reverseOrder = false); - static void staticUpdatingFailed(const QString& errMsg); - static LinuxPermElevator findPermElevatorForLinux(); - static QString wrapCmdLineArgument(const QString& arg); - static QString escapeCmdLineArgument(const QString& arg); - static QString getBackupDir(const QString& appDir); -#if defined(Q_OS_WIN32) - static bool executePreFinalStepWin(const QString& tempDir, const QString& backupDir, const QString& appDir, bool reqAdmin); - static bool executeFinalStepAsRootWin(const QString& tempDir, const QString& backupDir, const QString& appDir); - static bool executePostFinalStepWin(const QString& tempDir); - static bool waitForFileToDisappear(const QString& filePath, int seconds); - static bool waitForFileToAppear(const QString& filePath, int seconds); -#endif - - QNetworkAccessManager* networkManager = nullptr; - QNetworkReply* updatesCheckReply = nullptr; - QNetworkReply* updatesGetUrlsReply = nullptr; - QNetworkReply* updatesGetReply = nullptr; - bool updatesInProgress = false; - QList<UpdateEntry> updatesToDownload; - QHash<QString,QString> updatesToInstall; - QTemporaryDir* tempDir = nullptr; - QFile* currentDownloadFile = nullptr; - int totalPercent = 0; - int totalDownloadsCount = 0; - QString currentJobTitle; - bool requireAdmin = false; - static RetryFunction retryFunction; - - static QString staticErrorMessage; - static_char* WIN_PRE_FINAL_UPDATE_OPTION_NAME = "--update-pre-final-step"; - static_char* WIN_POST_FINAL_UPDATE_OPTION_NAME = "--update-post-final-step"; - static_char* WIN_UPDATER_BINARY = "UpdateSQLiteStudio.exe"; - static_char* updateServiceUrl = "http://sqlitestudio.pl/updates3.rvt"; - static_char* manualUpdatesHelpUrl = "http://wiki.sqlitestudio.pl/index.php/User_Manual#Manual"; - - private slots: - void finished(QNetworkReply* reply); - void downloadProgress(qint64 bytesReceived, qint64 totalBytes); - void readDownload(); + void checkForUpdatesAsync(); + bool waitForProcess(QProcess& proc); + void processCheckResults(const QByteArray& results); signals: void updatesAvailable(const QList<UpdateManager::UpdateEntry>& updates); void noUpdatesAvailable(); - void updatingProgress(const QString& jobTitle, int jobPercent, int totalPercent); - void updatingFinished(); void updatingError(const QString& errorMessage); }; +Q_DECLARE_METATYPE(QList<UpdateManager::UpdateEntry>) + #define UPDATES SQLITESTUDIO->getUpdateManager() #endif // PORTABLE_CONFIG |
