diff options
| author | Jeremy Rimpo <jrim@rimpo.org> | 2019-08-02 01:49:33 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-08-02 01:49:33 -0500 |
| commit | cff526415d781cb8a7761961ae2bd1fb6775c376 (patch) | |
| tree | cf5ff4f0d7bdd3767155a8a3e251201861284f89 | |
| parent | bdf45aea69ab7df0b01eb87cc80a2641ea4261d0 (diff) | |
| parent | e4cf2c314d6397c5d73bcf567d4420171238bd29 (diff) | |
Merge pull request #807 from isanae/logging-rework
Logging rework
78 files changed, 4189 insertions, 3430 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 94c76373..ac9d8fc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) -ADD_COMPILE_OPTIONS($<$<CXX_COMPILER_ID:MSVC>:/MP> $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELEASE>:/O2>> $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELWITHDEBINFO>:/O2>>) +ADD_COMPILE_OPTIONS( + $<$<CXX_COMPILER_ID:MSVC>:/MP> + $<$<CXX_COMPILER_ID:MSVC>:/D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING> + $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELEASE>:/O2>> + $<$<CXX_COMPILER_ID:MSVC>:$<$<CONFIG:RELWITHDEBINFO>:/O2>>) PROJECT(organizer) @@ -11,9 +15,11 @@ set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) set(CMAKE_INSTALL_MESSAGE NEVER) SET(DEPENDENCIES_DIR CACHE PATH "") + # hint to find qt in dependencies path LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll) +LIST(APPEND CMAKE_PREFIX_PATH ${FMT_ROOT}/build) ADD_SUBDIRECTORY(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f197211a..9785dc3d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,7 +73,7 @@ SET(organizer_SRCS mainwindow.cpp main.cpp loghighlighter.cpp - logbuffer.cpp + loglist.cpp lockeddialogbase.cpp lockeddialog.cpp waitingonclosedialog.cpp @@ -129,6 +129,12 @@ SET(organizer_SRCS filerenamer.cpp texteditor.cpp expanderwidget.cpp + env.cpp + envmetrics.cpp + envmodule.cpp + envsecurity.cpp + envshortcut.cpp + envwindows.cpp shared/windows_error.cpp shared/error_report.cpp @@ -181,7 +187,7 @@ SET(organizer_HDRS messagedialog.h mainwindow.h loghighlighter.h - logbuffer.h + loglist.h lockeddialogbase.h lockeddialog.h waitingonclosedialog.h @@ -239,6 +245,12 @@ SET(organizer_HDRS filerenamer.h texteditor.h expanderwidget.h + env.h + envmetrics.h + envmodule.h + envsecurity.h + envshortcut.h + envwindows.h shared/windows_error.h shared/error_report.h @@ -350,6 +362,15 @@ set(downloads downloadmanager ) +set(env + env + envmetrics + envmodule + envsecurity + envshortcut + envwindows +) + set(executables executableslist editexecutablesdialog @@ -436,7 +457,7 @@ set(widgets filterwidget icondelegate lcdnumber - logbuffer + loglist loghighlighter modflagicondelegate modidlineedit @@ -447,8 +468,8 @@ set(widgets ) set(src_filters - application core browser dialogs downloads executables locking modinfo modinfo\\dialog - modlist plugins previews profiles settings utilities widgets + application core browser dialogs downloads env executables locking modinfo + modinfo\\dialog modlist plugins previews profiles settings utilities widgets ) foreach(filter in list ${src_filters}) @@ -520,6 +541,9 @@ LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) FIND_PACKAGE(zlib REQUIRED) # TODO FindZlib doesn't find the static zlib library +# fmt +find_package(fmt REQUIRED) + INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/bsatk/src ${project_path}/esptk/src @@ -551,10 +575,11 @@ ELSE() ENDIF() ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm}) + TARGET_LINK_LIBRARIES(ModOrganizer Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick Qt5::Qml Qt5::QuickWidgets Qt5::Network Qt5::WebSockets - ${Boost_LIBRARIES} + ${Boost_LIBRARIES} fmt::fmt zlibstatic uibase esptk bsatk githubpp ${usvfs_name} diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 323dd128..d9b7debd 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -18,13 +18,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */
#include "bbcode.h"
-
+#include <log.h>
#include <QRegExp>
#include <map>
-
namespace BBCode {
+namespace log = MOBase::log;
class BBCodeMap {
@@ -88,7 +88,7 @@ public: return temp.replace(tagIter->second.first, QString("<font style=\"color: #%1;\">%2</font>").arg(color, content));
}
} else {
- qWarning("don't know how to deal with tag %s", qUtf8Printable(tagName));
+ log::warn("don't know how to deal with tag {}", tagName);
}
} else {
if (tagName == "*") {
@@ -99,8 +99,7 @@ public: } else {
// expression doesn't match. either the input string is invalid
// or the expression is
- qWarning("%s doesn't match the expression for %s",
- qUtf8Printable(temp), qUtf8Printable(tagName));
+ log::warn("{} doesn't match the expression for {}", temp, tagName);
length = 0;
return QString();
}
diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 70190433..73a6a2d0 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -24,9 +24,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "messagedialog.h"
#include "report.h"
#include "persistentcookiejar.h"
+#include "settings.h"
#include <utility.h>
-#include "settings.h"
+#include <log.h>
#include <QWebEngineSettings>
#include <QNetworkCookieJar>
@@ -37,6 +38,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDir>
#include <QKeyEvent>
+using namespace MOBase;
BrowserDialog::BrowserDialog(QWidget *parent)
@@ -190,12 +192,12 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply) try {
QWebEnginePage *page = qobject_cast<QWebEnginePage*>(sender());
if (page == nullptr) {
- qCritical("sender not a page");
+ log::error("sender not a page");
return;
}
BrowserView *view = qobject_cast<BrowserView*>(page->view());
if (view == nullptr) {
- qCritical("no view?");
+ log::error("no view?");
return;
}
@@ -204,14 +206,14 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply) if (isVisible()) {
MessageDialog::showMessage(tr("failed to start download"), this);
}
- qCritical("exception downloading unsupported content: %s", e.what());
+ log::error("exception downloading unsupported content: {}", e.what());
}
}
void BrowserDialog::downloadRequested(const QNetworkRequest &request)
{
- qCritical("download request %s ignored", request.url().toString().toUtf8().constData());
+ log::error("download request {} ignored", request.url().toString());
}
diff --git a/src/categories.cpp b/src/categories.cpp index 9e5fa9f7..12b18998 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <utility.h>
#include <report.h>
+#include <log.h>
#include <QObject>
#include <QFile>
@@ -61,8 +62,9 @@ void CategoryFactory::loadCategories() ++lineNum;
QList<QByteArray> cells = line.split('|');
if (cells.count() != 4) {
- qCritical("invalid category line %d: %s (%d cells)",
- lineNum, line.constData(), cells.count());
+ log::error(
+ "invalid category line {}: {} ({} cells)",
+ lineNum, line.constData(), cells.count());
} else {
std::vector<int> nexusIDs;
if (cells[2].length() > 0) {
@@ -72,7 +74,7 @@ void CategoryFactory::loadCategories() bool ok = false;
int temp = iter->toInt(&ok);
if (!ok) {
- qCritical("invalid category id %s", iter->constData());
+ log::error("invalid category id {}", iter->constData());
}
nexusIDs.push_back(temp);
}
@@ -82,8 +84,7 @@ void CategoryFactory::loadCategories() int id = cells[0].toInt(&cell0Ok);
int parentID = cells[3].trimmed().toInt(&cell3Ok);
if (!cell0Ok || !cell3Ok) {
- qCritical("invalid category line %d: %s",
- lineNum, line.constData());
+ log::error("invalid category line {}: {}", lineNum, line.constData());
}
addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID);
}
@@ -294,7 +295,7 @@ bool CategoryFactory::isDecendantOf(int id, int parentID) const return isDecendantOf(m_Categories[index].m_ParentID, parentID);
}
} else {
- qWarning("%d is no valid category id", id);
+ log::warn("{} is no valid category id", id);
return false;
}
}
@@ -359,10 +360,10 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const {
std::map<int, unsigned int>::const_iterator iter = m_NexusMap.find(nexusID);
if (iter != m_NexusMap.end()) {
- qDebug("nexus category id %d maps to internal %d", nexusID, iter->second);
+ log::debug("nexus category id {} maps to internal {}", nexusID, iter->second);
return iter->second;
} else {
- qDebug("nexus category id %d not mapped", nexusID);
+ log::debug("nexus category id {} not mapped", nexusID);
return 0U;
}
}
diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index 3ce4691b..87305599 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -127,7 +127,7 @@ void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructu FilesOrigin &origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority);
for (const QString &filename : stealFiles) {
if (filename.isEmpty()) {
- qWarning("Trying to find file with no name");
+ log::warn("Trying to find file with no name");
continue;
}
QFileInfo fileInfo(filename);
@@ -143,7 +143,7 @@ void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructu QString warnStr = fileInfo.absolutePath();
if (warnStr.isEmpty())
warnStr = filename;
- qWarning("file not found: %s", qUtf8Printable(warnStr));
+ log::warn("file not found: {}", warnStr);
}
}
} else {
diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 5e698e0e..36bc2b7f 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -19,12 +19,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "downloadlist.h"
#include "downloadmanager.h"
+#include <log.h>
#include <QEvent>
#include <QColor>
#include <QIcon>
-
#include <QSortFilterProxyModel>
+using namespace MOBase;
DownloadList::DownloadList(DownloadManager *manager, QObject *parent)
: QAbstractTableModel(parent), m_Manager(manager)
@@ -192,7 +193,7 @@ void DownloadList::update(int row) else if (row < this->rowCount())
emit dataChanged(this->index(row, 0, QModelIndex()), this->index(row, this->columnCount(QModelIndex())-1, QModelIndex()));
else
- qCritical("invalid row %d in download list, update failed", row);
+ log::error("invalid row {} in download list, update failed", row);
}
QString DownloadList::sizeFormat(quint64 size) const
diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index e2a6f321..85d27831 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "downloadlist.h"
#include "downloadlistwidget.h"
+#include <log.h>
#include <QPainter>
#include <QMouseEvent>
#include <QMenu>
@@ -29,6 +30,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QCheckBox>
#include <QWidgetAction>
+using namespace MOBase;
+
void DownloadProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QModelIndex sourceIndex = m_SortProxy->mapToSource(index);
@@ -286,7 +289,7 @@ void DownloadListWidget::issueDelete() void DownloadListWidget::issueRemoveFromView()
{
- qDebug() << "removing from view: " << m_ContextRow;
+ log::debug("removing from view: {}", m_ContextRow);
emit removeDownload(m_ContextRow, false);
}
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index ec1faed4..3b084b83 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -352,7 +352,7 @@ void DownloadManager::refreshList() } } if (orphans.size() > 0) { - qDebug("%d orphaned meta files will be deleted", orphans.size()); + log::debug("{} orphaned meta files will be deleted", orphans.size()); shellDelete(orphans, true); } @@ -378,9 +378,6 @@ void DownloadManager::refreshList() } } - //if (m_ActiveDownloads.size() != downloadsBefore) { - qDebug("Downloads after refresh: %d", m_ActiveDownloads.size()); - //} emit update(-1); //let watcher trigger refreshes again @@ -401,7 +398,7 @@ bool DownloadManager::addDownload(const QStringList &URLs, QString gameName, } QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); - qDebug("selected download url: %s", qUtf8Printable(preferredUrl.toString())); + log::debug("selected download url: {}", preferredUrl.toString()); QNetworkRequest request(preferredUrl); request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, gameName, modID, fileID, fileInfo); @@ -562,9 +559,9 @@ void DownloadManager::addNXMDownload(const QString &url) break; } } - qDebug("add nxm download: %s", qUtf8Printable(url)); + log::debug("add nxm download: {}", url); if (foundGame == nullptr) { - qDebug("download requested for wrong game (game: %s, url: %s)", qUtf8Printable(m_ManagedGame->gameShortName()), qUtf8Printable(nxmInfo.game())); + log::debug("download requested for wrong game (game: {}, url: {})", m_ManagedGame->gameShortName(), nxmInfo.game()); QMessageBox::information(nullptr, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " "has been set up for \"%2\".").arg(nxmInfo.game()).arg(m_ManagedGame->gameShortName()), QMessageBox::Ok); return; @@ -572,13 +569,14 @@ void DownloadManager::addNXMDownload(const QString &url) for (auto tuple : m_PendingDownloads) { if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) { - QString debugStr("download requested is already queued (mod: %1, file: %2)"); - QString infoStr(tr("There is already a download queued for this file.\n\nMod %1\nFile %2")); + const auto infoStr = + tr("There is already a download queued for this file.\n\nMod %1\nFile %2") + .arg(nxmInfo.modId()).arg(nxmInfo.fileId()); - debugStr = debugStr.arg(nxmInfo.modId()).arg(nxmInfo.fileId()); - infoStr = infoStr.arg(nxmInfo.modId()).arg(nxmInfo.fileId()); + log::debug( + "download requested is already queued (mod: {}, file: {})", + nxmInfo.modId(), nxmInfo.fileId()); - qDebug(qUtf8Printable(debugStr)); QMessageBox::information(nullptr, tr("Already Queued"), infoStr, QMessageBox::Ok); return; } @@ -622,7 +620,7 @@ void DownloadManager::addNXMDownload(const QString &url) infoStr = infoStr.arg(QStringLiteral("<blank>")); } - qDebug(qUtf8Printable(debugStr)); + log::debug("{}", debugStr); QMessageBox::information(nullptr, tr("Already Started"), infoStr, QMessageBox::Ok); return; } @@ -660,7 +658,7 @@ void DownloadManager::removeFile(int index, bool deleteFile) if ((download->m_State == STATE_STARTED) || (download->m_State == STATE_DOWNLOADING)) { // shouldn't have been possible - qCritical("tried to remove active download"); + log::error("tried to remove active download"); endDisableDirWatcher(); return; } @@ -798,7 +796,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) emit update(-1); endDisableDirWatcher(); } catch (const std::exception &e) { - qCritical("failed to remove download: %s", e.what()); + log::error("failed to remove download: {}", e.what()); } refreshList(); } @@ -883,7 +881,7 @@ void DownloadManager::resumeDownloadInt(int index) if (info->m_State == STATE_ERROR) { info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count(); } - qDebug("request resume from url %s", qUtf8Printable(info->currentURL())); + log::debug("request resume from url {}", info->currentURL()); QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); if (info->m_State != STATE_ERROR) { @@ -896,7 +894,7 @@ void DownloadManager::resumeDownloadInt(int index) std::get<2>(info->m_SpeedDiff) = 0; std::get<3>(info->m_SpeedDiff) = 0; std::get<4>(info->m_SpeedDiff) = 0; - qDebug("resume at %lld bytes", info->m_ResumePos); + log::debug("resume at {} bytes", info->m_ResumePos); startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); } emit update(index); @@ -924,7 +922,7 @@ void DownloadManager::queryInfo(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("re-querying file info is currently only possible with Nexus"); + log::warn("re-querying file info is currently only possible with Nexus"); return; } @@ -976,7 +974,7 @@ void DownloadManager::queryInfoMd5(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("re-querying file info is currently only possible with Nexus"); + log::warn("re-querying file info is currently only possible with Nexus"); return; } @@ -993,11 +991,11 @@ void DownloadManager::queryInfoMd5(int index) downloadFile.setFileName(m_OrganizerCore->downloadsPath() + "\\" + info->m_FileName); } if (!downloadFile.exists()) { - qDebug("Can't find download file %s", info->m_FileName); + log::debug("Can't find download file {}", info->m_FileName); return; } if (!downloadFile.open(QIODevice::ReadOnly)) { - qDebug("Can't open download file %s", info->m_FileName); + log::debug("Can't open download file {}", info->m_FileName); return; } info->m_Hash = QCryptographicHash::hash(downloadFile.readAll(), QCryptographicHash::Md5); @@ -1016,7 +1014,7 @@ void DownloadManager::visitOnNexus(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("Visiting mod page is currently only possible with Nexus"); + log::warn("Visiting mod page is currently only possible with Nexus"); return; } @@ -1389,7 +1387,7 @@ void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadMana m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->gameName, info->m_FileInfo->modID, this, info->m_DownloadID, QString())); } break; case STATE_FETCHINGMODINFO_MD5: { - qDebug(qUtf8Printable(QString("Searching %1 for MD5 of %2").arg(info->m_GamesToQuery[0]).arg(QString(info->m_Hash.toHex())))); + log::debug("Searching {} for MD5 of {}", info->m_GamesToQuery[0], QString(info->m_Hash.toHex())); m_RequestIDs.insert(m_NexusInterface->requestInfoFromMd5(info->m_GamesToQuery[0], info->m_Hash, this, info->m_DownloadID, QString())); } break; case STATE_READY: { @@ -1619,8 +1617,9 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian } } else { if (info->m_FileInfo->fileID == 0) { - qWarning("could not determine file id for %s (state %d)", - qUtf8Printable(info->m_FileName), info->m_State); + log::warn( + "could not determine file id for {} (state {})", + info->m_FileName, info->m_State); } } @@ -1784,7 +1783,7 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use if (chosenIdx < 0) { chosenIdx = i; //intentional to not break in order to check other results } else { - qDebug("Multiple active files found during MD5 search. Defaulting to time stamps..."); + log::debug("Multiple active files found during MD5 search. Defaulting to time stamps..."); chosenIdx = -1; break; } @@ -2004,7 +2003,7 @@ void DownloadManager::downloadFinished(int index) resumeDownloadInt(index); } } else { - qWarning("no download index %d", index); + log::warn("no download index {}", index); } } @@ -2013,9 +2012,9 @@ void DownloadManager::downloadError(QNetworkReply::NetworkError error) { if (error != QNetworkReply::OperationCanceledError) { QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); - qWarning("%s (%d)", reply != nullptr ? qUtf8Printable(reply->errorString()) - : "Download error occured", - error); + log::warn("{} ({})", + reply != nullptr ? reply->errorString() : "Download error occured", + error); } } @@ -2038,7 +2037,7 @@ void DownloadManager::metaDataChanged() } } } else { - qWarning("meta data event for unknown download"); + log::warn("meta data event for unknown download"); } } @@ -2073,7 +2072,11 @@ void DownloadManager::writeData(DownloadInfo *info) if (ret < info->m_Reply->size()) { QString fileName = info->m_FileName; // m_FileName may be destroyed after setState setState(info, DownloadState::STATE_CANCELED); - qCritical(QString("Unable to write download \"%2\" to drive (return %1)").arg(ret).arg(info->m_FileName).toLocal8Bit()); + + log::error( + "Unable to write download \"{}\" to drive (return {})", + info->m_FileName, ret); + reportError(tr("Unable to write download to drive (return %1).\n" "Check the drive's available storage.\n\n" "Canceling download \"%2\"...").arg(ret).arg(fileName)); diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 8929d207..3ec3d64f 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -101,9 +101,7 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const auto itor = m_executablesList.find(title); if (itor == m_executablesList.end()) { - qWarning().nospace() - << "getExecutablesList(): executable '" << title << "' not found"; - + log::warn("getExecutablesList(): executable '{}' not found", title); continue; } @@ -293,9 +291,10 @@ void EditExecutablesDialog::setEdits(const Executable& e) modIndex = ui->mods->findText(modName->value); if (modIndex == -1) { - qWarning().nospace() - << "executable '" << e.title() << "' uses mod '" << modName->value << "' " - << "as a custom overwrite, but that mod doesn't exist"; + log::warn( + "executable '{}' uses mod '{}' as a custom overwrite, but that mod " + "doesn't exist", + e.title(), modName->value); } } @@ -335,7 +334,7 @@ void EditExecutablesDialog::save() auto* e = selectedExe(); if (!e) { - qWarning("trying to save but nothing is selected"); + log::warn("trying to save but nothing is selected"); return; } @@ -475,13 +474,13 @@ void EditExecutablesDialog::on_remove_clicked() { auto* item = selectedItem(); if (!item) { - qWarning("trying to remove entry but nothing is selected"); + log::warn("trying to remove entry but nothing is selected"); return; } auto* exe = selectedExe(); if (!exe) { - qWarning("trying to remove entry but nothing is selected"); + log::warn("trying to remove entry but nothing is selected"); return; } @@ -646,7 +645,7 @@ void EditExecutablesDialog::on_configureLibraries_clicked() { auto* e = selectedExe(); if (!e) { - qWarning("trying to configure libraries but nothing is selected"); + log::warn("trying to configure libraries but nothing is selected"); return; } diff --git a/src/env.cpp b/src/env.cpp new file mode 100644 index 00000000..641eb4a7 --- /dev/null +++ b/src/env.cpp @@ -0,0 +1,479 @@ +#include "env.h" +#include "envmetrics.h" +#include "envmodule.h" +#include "envsecurity.h" +#include "envshortcut.h" +#include "envwindows.h" +#include <log.h> +#include <utility.h> + +namespace env +{ + +using namespace MOBase; + +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + +Environment::Environment() + : m_windows(new WindowsInfo), m_metrics(new Metrics) +{ + m_modules = getLoadedModules(); + m_security = getSecurityProducts(); +} + +// anchor +Environment::~Environment() = default; + +const std::vector<Module>& Environment::loadedModules() const +{ + return m_modules; +} + +const WindowsInfo& Environment::windowsInfo() const +{ + return *m_windows; +} + +const std::vector<SecurityProduct>& Environment::securityProducts() const +{ + return m_security; +} + +const Metrics& Environment::metrics() const +{ + return *m_metrics; +} + +void Environment::dump() const +{ + log::debug("windows: {}", m_windows->toString()); + + if (m_windows->compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : m_security) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : m_modules) { + log::debug(" . {}", m.toString()); + } + + log::debug("displays:"); + for (const auto& d : m_metrics->displays()) { + log::debug(" . {}", d.toString()); + } +} + + +struct Process +{ + std::wstring filename; + DWORD pid; + + Process(std::wstring f, DWORD id) + : filename(std::move(f)), pid(id) + { + } +}; + + +// returns the filename of the given process or the current one +// +std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +{ + // double the buffer size 10 times + const int MaxTries = 10; + + DWORD bufferSize = MAX_PATH; + + for (int tries=0; tries<MaxTries; ++tries) + { + auto buffer = std::make_unique<wchar_t[]>(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + DWORD writtenSize = 0; + + if (process == INVALID_HANDLE_VALUE) { + // query this process + writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); + } else { + // query another process + writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); + } + + if (writtenSize == 0) { + // hard failure + const auto e = GetLastError(); + std::wcerr << formatSystemMessage(e) << L"\n"; + break; + } else if (writtenSize >= bufferSize) { + // buffer is too small, try again + bufferSize *= 2; + } else { + // if GetModuleFileName() works, `writtenSize` does not include the null + // terminator + const std::wstring s(buffer.get(), writtenSize); + const std::filesystem::path path(s); + + return path.filename().native(); + } + } + + // something failed or the path is way too long to make sense + + std::wstring what; + if (process == INVALID_HANDLE_VALUE) { + what = L"the current process"; + } else { + what = L"pid " + std::to_wstring(reinterpret_cast<std::uintptr_t>(process)); + } + + std::wcerr << L"failed to get filename for " << what << L"\n"; + return {}; +} + +std::vector<DWORD> runningProcessesIds() +{ + // double the buffer size 10 times + const int MaxTries = 10; + + // initial size of 300 processes, unlikely to be more than that + std::size_t size = 300; + + for (int tries=0; tries<MaxTries; ++tries) { + auto ids = std::make_unique<DWORD[]>(size); + std::fill(ids.get(), ids.get() + size, 0); + + DWORD bytesGiven = static_cast<DWORD>(size * sizeof(ids[0])); + DWORD bytesWritten = 0; + + if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) + { + const auto e = GetLastError(); + + std::wcerr + << L"failed to enumerate processes, " + << formatSystemMessage(e) << L"\n"; + + return {}; + } + + if (bytesWritten == bytesGiven) { + // no way to distinguish between an exact fit and not enough space, + // just try again + size *= 2; + continue; + } + + const auto count = bytesWritten / sizeof(ids[0]); + return std::vector<DWORD>(ids.get(), ids.get() + count); + } + + std::cerr << L"too many processes to enumerate"; + return {}; +} + +std::vector<Process> runningProcesses() +{ + const auto pids = runningProcessesIds(); + std::vector<Process> v; + + for (const auto& pid : pids) { + if (pid == 0) { + // the idle process has pid 0 and seems to be picked up by EnumProcesses() + continue; + } + + HandlePtr h(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + + if (e != ERROR_ACCESS_DENIED) { + // don't log access denied, will happen a lot for system processes, even + // when elevated + std::wcerr + << L"failed to open process " << pid << L", " + << formatSystemMessage(e) << L"\n"; + } + + continue; + } + + auto filename = processFilename(h.get()); + if (!filename.empty()) { + v.emplace_back(std::move(filename), pid); + } + } + + return v; +} + +DWORD findOtherPid() +{ + const std::wstring defaultName = L"ModOrganizer.exe"; + + std::wclog << L"looking for the other process...\n"; + + // used to skip the current process below + const auto thisPid = GetCurrentProcessId(); + std::wclog << L"this process id is " << thisPid << L"\n"; + + // getting the filename for this process, assumes the other process has the + // smae one + auto filename = processFilename(); + if (filename.empty()) { + std::wcerr + << L"can't get current process filename, defaulting to " + << defaultName << L"\n"; + + filename = defaultName; + } else { + std::wclog << L"this process filename is " << filename << L"\n"; + } + + // getting all running processes + const auto processes = runningProcesses(); + std::wclog << L"there are " << processes.size() << L" processes running\n"; + + // going through processes, trying to find one with the same name and a + // different pid than this process has + for (const auto& p : processes) { + if (p.filename == filename) { + if (p.pid != thisPid) { + return p.pid; + } + } + } + + std::wclog + << L"no process with this filename\n" + << L"MO may not be running, or it may be running as administrator\n" + << L"you can try running this again as administrator\n"; + + return 0; +} + +std::wstring tempDir() +{ + const DWORD bufferSize = MAX_PATH + 1; + wchar_t buffer[bufferSize + 1] = {}; + + const auto written = GetTempPathW(bufferSize, buffer); + if (written == 0) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + // `written` does not include the null terminator + return std::wstring(buffer, buffer + written); +} + +HandlePtr tempFile(const std::wstring dir) +{ + // maximum tries of incrementing the counter + const int MaxTries = 100; + + // UTC time and date will be in the filename + const auto now = std::time(0); + const auto tm = std::gmtime(&now); + + // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where + // i can go until MaxTries + std::wostringstream oss; + oss + << L"ModOrganizer-" + << std::setw(4) << (1900 + tm->tm_year) + << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) + << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" + << std::setw(2) << std::setfill(L'0') << tm->tm_hour + << std::setw(2) << std::setfill(L'0') << tm->tm_min + << std::setw(2) << std::setfill(L'0') << tm->tm_sec; + + const std::wstring prefix = oss.str(); + const std::wstring ext = L".dmp"; + + // first path to try, without counter in it + std::wstring path = dir + L"\\" + prefix + ext; + + for (int i=0; i<MaxTries; ++i) { + std::wclog << L"trying file '" << path << L"'\n"; + + HandlePtr h (CreateFileW( + path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); + + if (h.get() != INVALID_HANDLE_VALUE) { + // worked + return h; + } + + const auto e = GetLastError(); + + if (e != ERROR_FILE_EXISTS) { + // probably no write access + std::wcerr + << L"failed to create dump file, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + // try again with "-i" + path = dir + L"\\" + prefix + L"-" + std::to_wstring(i + 1) + ext; + } + + std::wcerr << L"can't create dump file, ran out of filenames\n"; + return {}; +} + +HandlePtr dumpFile() +{ + // try the current directory + HandlePtr h = tempFile(L"."); + if (h.get() != INVALID_HANDLE_VALUE) { + return h; + } + + std::wclog << L"cannot write dump file in current directory\n"; + + // try the temp directory + const auto dir = tempDir(); + + if (!dir.empty()) { + h = tempFile(dir.c_str()); + if (h.get() != INVALID_HANDLE_VALUE) { + return h; + } + } + + return {}; +} + +bool createMiniDump(HANDLE process, CoreDumpTypes type) +{ + const DWORD pid = GetProcessId(process); + + const HandlePtr file = dumpFile(); + if (!file) { + std::wcerr << L"nowhere to write the dump file\n"; + return false; + } + + auto flags = _MINIDUMP_TYPE( + MiniDumpNormal | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithProcessThreadData); + + if (type == CoreDumpTypes::Data) { + std::wclog << L"writing minidump with data\n"; + flags = _MINIDUMP_TYPE(flags | MiniDumpWithDataSegs); + } else if (type == CoreDumpTypes::Full) { + std::wclog << L"writing full minidump\n"; + flags = _MINIDUMP_TYPE(flags | MiniDumpWithFullMemory); + } else { + std::wclog << L"writing mini minidump\n"; + } + + const auto ret = MiniDumpWriteDump( + process, pid, file.get(), flags, nullptr, nullptr, nullptr); + + if (!ret) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to write mini dump, " << formatSystemMessage(e) << L"\n"; + + return false; + } + + std::wclog << L"minidump written correctly\n"; + return true; +} + + +bool coredump(CoreDumpTypes type) +{ + std::wclog << L"creating minidump for the current process\n"; + return createMiniDump(GetCurrentProcess(), type); +} + +bool coredumpOther(CoreDumpTypes type) +{ + std::wclog << L"creating minidump for an running process\n"; + + const auto pid = findOtherPid(); + if (pid == 0) { + std::wcerr << L"no other process found\n"; + return false; + } + + std::wclog << L"found other process with pid " << pid << L"\n"; + + HandlePtr handle(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + + if (!handle) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to open process " << pid << L", " + << formatSystemMessage(e) << L"\n"; + + return false; + } + + return createMiniDump(handle.get(), type); +} + +} // namespace diff --git a/src/env.h b/src/env.h new file mode 100644 index 00000000..0e88263b --- /dev/null +++ b/src/env.h @@ -0,0 +1,154 @@ +namespace env +{ + +class Module; +class SecurityProduct; +class WindowsInfo; +class Metrics; + + +// used by HandlePtr, calls CloseHandle() as the deleter +// +struct HandleCloser +{ + using pointer = HANDLE; + + void operator()(HANDLE h) + { + if (h != INVALID_HANDLE_VALUE) { + ::CloseHandle(h); + } + } +}; + +using HandlePtr = std::unique_ptr<HANDLE, HandleCloser>; + + +// used by DesktopDCPtr, calls ReleaseDC(0, dc) as the deleter +// +struct DesktopDCReleaser +{ + using pointer = HDC; + + void operator()(HDC dc) + { + if (dc != 0) { + ::ReleaseDC(0, dc); + } + } +}; + +using DesktopDCPtr = std::unique_ptr<HDC, DesktopDCReleaser>; + + +// used by LibraryPtr, calls FreeLibrary as the deleter +// +struct LibraryFreer +{ + using pointer = HINSTANCE; + + void operator()(HINSTANCE h) + { + if (h != 0) { + ::FreeLibrary(h); + } + } +}; + +using LibraryPtr = std::unique_ptr<HINSTANCE, LibraryFreer>; + + +// used by COMPtr, calls Release() as the deleter +// +struct COMReleaser +{ + void operator()(IUnknown* p) + { + if (p) { + p->Release(); + } + } +}; + +template <class T> +using COMPtr = std::unique_ptr<T, COMReleaser>; + + +// creates a console in the constructor and destroys it in the destructor, +// also redirects standard streams +// +class Console +{ +public: + // opens the console and redirects standard streams to it + // + Console(); + + // destroys the console and redirects the standard stream to NUL + // + ~Console(); + +private: + // whether the console was allocated successfully + bool m_hasConsole; + + // standard streams + FILE* m_in; + FILE* m_out; + FILE* m_err; +}; + + +// represents the process's environment +// +class Environment +{ +public: + Environment(); + ~Environment(); + + // list of loaded modules in the current process + // + const std::vector<Module>& loadedModules() const; + + // information about the operating system + // + const WindowsInfo& windowsInfo() const; + + // information about the installed security products + // + const std::vector<SecurityProduct>& securityProducts() const; + + // information about displays + // + const Metrics& metrics() const; + + // logs the environment + // + void dump() const; + +private: + std::vector<Module> m_modules; + std::unique_ptr<WindowsInfo> m_windows; + std::vector<SecurityProduct> m_security; + std::unique_ptr<Metrics> m_metrics; +}; + + +enum class CoreDumpTypes +{ + Mini = 1, + Data, + Full +}; + +// creates a minidump file for the given process +// +bool coredump(CoreDumpTypes type); + +// finds another process with the same name as this one and creates a minidump +// file for it +// +bool coredumpOther(CoreDumpTypes type); + +} // namespace env diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp new file mode 100644 index 00000000..b1b9bd2e --- /dev/null +++ b/src/envmetrics.cpp @@ -0,0 +1,254 @@ +#include "envmetrics.h" +#include "env.h" +#include <Windows.h> +#include <shellscalingapi.h> +#include <log.h> +#include <utility.h> + +namespace env +{ + +using namespace MOBase; + +// fallback for windows 7 +// +int getDesktopDpi() +{ + // desktop DC + DesktopDCPtr dc(GetDC(0)); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessage(e)); + return 0; + } + + return GetDeviceCaps(dc.get(), LOGPIXELSX); +} + +// finds a monitor by device name; there's no real good way to do that except +// by enumerating all the monitors and checking their name +// +HMONITOR findMonitor(const QString& name) +{ + // passed to the enumeration callback + struct Data + { + QString name; + HMONITOR hm; + }; + + Data data = {name, 0}; + + // callback + auto callback = [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast<Data*>(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessage(e)); + + // error for this monitor, but continue + return TRUE; + } + + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } + + // not found, continue to the next monitor + return TRUE; + }; + + + // for each monitor + EnumDisplayMonitors(0, nullptr, callback, reinterpret_cast<LPARAM>(&data)); + + return data.hm; +} + +// returns the dpi for the given monitor; for systems that do not support +// per-monitor dpi (such as windows 7), this is the desktop dpi +// +int getDpi(const QString& monitorDevice) +{ + using GetDpiForMonitorFunction = + HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + static LibraryPtr shcore; + static GetDpiForMonitorFunction* GetDpiForMonitor = nullptr; + static bool checked = false; + + if (!checked) { + // try to find GetDpiForMonitor() from shcored.dll + + shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (shcore) { + // windows 8.1+ only + GetDpiForMonitor = reinterpret_cast<GetDpiForMonitorFunction*>( + GetProcAddress(shcore.get(), "GetDpiForMonitor")); + } + + checked = true; + } + + if (!GetDpiForMonitor) { + // get the desktop dpi instead + return getDesktopDpi(); + } + + + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(monitorDevice); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", monitorDevice); + return 0; + } + + UINT dpiX=0, dpiY=0; + const auto r = GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + monitorDevice, formatSystemMessage(r)); + + return 0; + } + + // dpiX and dpiY are always identical, as per the documentation + return dpiX; +} + + +Display::Display(QString adapter, QString monitorDevice, bool primary) : + m_adapter(std::move(adapter)), + m_monitorDevice(std::move(monitorDevice)), + m_primary(primary), + m_resX(0), m_resY(0), m_dpi(0), m_refreshRate(0) +{ + getSettings(); + m_dpi = getDpi(m_monitorDevice); +} + +const QString& Display::adapter() const +{ + return m_adapter; +} + +const QString& Display::monitorDevice() const +{ + return m_monitorDevice; +} + +bool Display::primary() +{ + return m_primary; +} + +int Display::resX() const +{ + return m_resX; +} + +int Display::resY() const +{ + return m_resY; +} + +int Display::dpi() +{ + return m_dpi; +} + +int Display::refreshRate() const +{ + return m_refreshRate; +} + +QString Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(m_resX) + .arg(m_resY) + .arg(m_refreshRate) + .arg(m_dpi) + .arg(m_adapter) + .arg(m_primary ? " (primary)" : ""); +} + +void Display::getSettings() +{ + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + const auto wsDevice = m_monitorDevice.toStdWString(); + + if (!EnumDisplaySettingsW(wsDevice.c_str(), ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", m_monitorDevice); + return; + } + + // all these fields should be available + + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + m_refreshRate = dm.dmDisplayFrequency; + } + + if (dm.dmFields & DM_PELSWIDTH) { + m_resX = dm.dmPelsWidth; + } + + if (dm.dmFields & DM_PELSHEIGHT) { + m_resY = dm.dmPelsHeight; + } +} + + +Metrics::Metrics() +{ + getDisplays(); +} + +const std::vector<Display>& Metrics::displays() const +{ + return m_displays; +} + +void Metrics::getDisplays() +{ + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.emplace_back( + QString::fromWCharArray(device.DeviceString), + QString::fromWCharArray(device.DeviceName), + (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)); + } +} + +} // namespace diff --git a/src/envmetrics.h b/src/envmetrics.h new file mode 100644 index 00000000..bede36fc --- /dev/null +++ b/src/envmetrics.h @@ -0,0 +1,72 @@ +#include <QString> +#include <vector> + +namespace env +{ + +// information about a monitor +// +class Display +{ +public: + Display(QString adapter, QString monitorDevice, bool primary); + + // display name of the adapter running the monitor + // + const QString& adapter() const; + + // internal device name of the monitor, this is not a display name + // + const QString& monitorDevice() const; + + // whether this monitor is the primary + // + bool primary(); + + // resolution + // + int resX() const; + int resY() const; + + // dpi + // + int dpi(); + + // refresh rate in hz + // + int refreshRate() const; + + // string representation + // + QString toString() const; + +private: + QString m_adapter; + QString m_monitorDevice; + bool m_primary; + int m_resX, m_resY; + int m_dpi; + int m_refreshRate; + + void getSettings(); +}; + + +// holds various information about Windows metrics +// +class Metrics +{ +public: + Metrics(); + + // list of displays on the system + // + const std::vector<Display>& displays() const; + +private: + std::vector<Display> m_displays; + + void getDisplays(); +}; + +} // namespace diff --git a/src/envmodule.cpp b/src/envmodule.cpp new file mode 100644 index 00000000..8cea414a --- /dev/null +++ b/src/envmodule.cpp @@ -0,0 +1,376 @@ +#include "envmodule.h" +#include "env.h" +#include <utility.h> +#include <log.h> + +namespace env +{ + +using namespace MOBase; + +Module::Module(QString path, std::size_t fileSize) + : m_path(std::move(path)), m_fileSize(fileSize) +{ + const auto fi = getFileInfo(); + + m_version = getVersion(fi.ffi); + m_timestamp = getTimestamp(fi.ffi); + m_versionString = fi.fileDescription; + m_md5 = getMD5(); +} + +const QString& Module::path() const +{ + return m_path; +} + +QString Module::displayPath() const +{ + return QDir::fromNativeSeparators(m_path.toLower()); +} + +std::size_t Module::fileSize() const +{ + return m_fileSize; +} + +const QString& Module::version() const +{ + return m_version; +} + +const QString& Module::versionString() const +{ + return m_versionString; +} + +const QDateTime& Module::timestamp() const +{ + return m_timestamp; +} + +const QString& Module::md5() const +{ + return m_md5; +} + +QString Module::timestampString() const +{ + if (!m_timestamp.isValid()) { + return "(no timestamp)"; + } + + return m_timestamp.toString(Qt::DateFormat::ISODate); +} + +QString Module::toString() const +{ + QStringList sl; + + // file size + sl.push_back(displayPath()); + sl.push_back(QString("%1 B").arg(m_fileSize)); + + // version + if (m_version.isEmpty() && m_versionString.isEmpty()) { + sl.push_back("(no version)"); + } else { + if (!m_version.isEmpty()) { + sl.push_back(m_version); + } + + if (!m_versionString.isEmpty() && m_versionString != m_version) { + sl.push_back(versionString()); + } + } + + // timestamp + if (m_timestamp.isValid()) { + sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); + } else { + sl.push_back("(no timestamp)"); + } + + // md5 + if (!m_md5.isEmpty()) { + sl.push_back(m_md5); + } + + return sl.join(", "); +} + +Module::FileInfo Module::getFileInfo() const +{ + const auto wspath = m_path.toStdWString(); + + // getting version info size + DWORD dummy = 0; + const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); + + if (size == 0) { + const auto e = GetLastError(); + + if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { + // not an error, no version information built into that module + return {}; + } + + log::error( + "GetFileVersionInfoSizeW() failed on '{}', {}", + m_path, formatSystemMessage(e)); + + return {}; + } + + // getting version info + auto buffer = std::make_unique<std::byte[]>(size); + + if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { + const auto e = GetLastError(); + + log::error( + "GetFileVersionInfoW() failed on '{}', {}", + m_path, formatSystemMessage(e)); + + return {}; + } + + // the version info has two major parts: a fixed version and a localizable + // set of strings + + FileInfo fi; + fi.ffi = getFixedFileInfo(buffer.get()); + fi.fileDescription = getFileDescription(buffer.get()); + + return fi; +} + +VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const +{ + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // the fixed version info is in the root + const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no fixed file info + return {}; + } + + const auto* fi = static_cast<VS_FIXEDFILEINFO*>(valuePointer); + + // signature is always 0xfeef04bd + if (fi->dwSignature != 0xfeef04bd) { + log::error( + "bad file info signature {:#x} for '{}'", + fi->dwSignature, m_path); + + return {}; + } + + return *fi; +} + +QString Module::getFileDescription(std::byte* buffer) const +{ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + }; + + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // getting list of available languages + auto ret = VerQueryValueW( + buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + log::error("VerQueryValueW() for translations failed on '{}'", m_path); + return {}; + } + + // number of languages + const auto count = valueSize / sizeof(LANGANDCODEPAGE); + if (count == 0) { + return {}; + } + + // using the first language in the list to get FileVersion + const auto* lcp = static_cast<LANGANDCODEPAGE*>(valuePointer); + + const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") + .arg(lcp->wLanguage, 4, 16, QChar('0')) + .arg(lcp->wCodePage, 4, 16, QChar('0')); + + ret = VerQueryValueW( + buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no file version + return {}; + } + + // valueSize includes the null terminator + return QString::fromWCharArray( + static_cast<wchar_t*>(valuePointer), valueSize - 1); +} + +QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const +{ + if (fi.dwSignature == 0) { + return {}; + } + + const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; + const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; + const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; + const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; + + if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { + return {}; + } + + return QString("%1.%2.%3.%4") + .arg(major).arg(minor).arg(maintenance).arg(build); +} + +QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +{ + FILETIME ft = {}; + + if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { + // if the file info is invalid or doesn't have a date, use the creation + // time on the file + + // opening the file + HandlePtr h(CreateFileW( + m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); + + if (h.get() == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + + log::error( + "can't open file '{}' for timestamp, {}", + m_path, formatSystemMessage(e)); + + return {}; + } + + // getting the file time + if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { + const auto e = GetLastError(); + + log::error( + "can't get file time for '{}', {}", + m_path, formatSystemMessage(e)); + + return {}; + } + } else { + // use the time from the file info + ft.dwHighDateTime = fi.dwFileDateMS; + ft.dwLowDateTime = fi.dwFileDateLS; + } + + + // converting to SYSTEMTIME + SYSTEMTIME utc = {}; + + if (!FileTimeToSystemTime(&ft, &utc)) { + log::error( + "FileTimeToSystemTime() failed on timestamp high={:#x} low={:#x} for '{}'", + ft.dwHighDateTime, ft.dwLowDateTime, m_path); + + return {}; + } + + return QDateTime( + QDate(utc.wYear, utc.wMonth, utc.wDay), + QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); +} + +QString Module::getMD5() const +{ + if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { + // don't calculate md5 for system files, it's not really relevant and + // it takes a while + return {}; + } + + // opening the file + QFile f(m_path); + + if (!f.open(QFile::ReadOnly)) { + log::error("failed to open file '{}' for md5", m_path); + return {}; + } + + // hashing + QCryptographicHash hash(QCryptographicHash::Md5); + if (!hash.addData(&f)) { + log::error("failed to calculate md5 for '{}'", m_path); + return {}; + } + + return hash.result().toHex(); +} + + +std::vector<Module> getLoadedModules() +{ + HandlePtr snapshot(CreateToolhelp32Snapshot( + TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); + return {}; + } + + MODULEENTRY32 me = {}; + me.dwSize = sizeof(me); + + // first module, this shouldn't fail because there's at least the executable + if (!Module32First(snapshot.get(), &me)) + { + const auto e = GetLastError(); + log::error("Module32First() failed, {}", formatSystemMessage(e)); + return {}; + } + + std::vector<Module> v; + + for (;;) + { + const auto path = QString::fromWCharArray(me.szExePath); + if (!path.isEmpty()) { + v.push_back(Module(path, me.modBaseSize)); + } + + // next module + if (!Module32Next(snapshot.get(), &me)) { + const auto e = GetLastError(); + + // no more modules is not an error + if (e != ERROR_NO_MORE_FILES) { + log::error("Module32Next() failed, {}", formatSystemMessage(e)); + } + + break; + } + } + + // sorting by display name + std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { + return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); + }); + + return v; +} + +} // namespace diff --git a/src/envmodule.h b/src/envmodule.h new file mode 100644 index 00000000..ea1156bd --- /dev/null +++ b/src/envmodule.h @@ -0,0 +1,98 @@ +#include <QString> +#include <QDateTime> + +namespace env +{ + +// represents one module +// +class Module +{ +public: + explicit Module(QString path, std::size_t fileSize); + + // returns the module's path + // + const QString& path() const; + + // returns the module's path in lowercase and using forward slashes + // + QString displayPath() const; + + // returns the size in bytes, may be 0 + // + std::size_t fileSize() const; + + // returns the x.x.x.x version embedded from the version info, may be empty + // + const QString& version() const; + + // returns the FileVersion entry from the resource file, returns + // "(no version)" if not available + // + const QString& versionString() const; + + // returns the build date from the version info, or the creation time of the + // file on the filesystem, may be empty + // + const QDateTime& timestamp() const; + + // returns the md5 of the file, may be empty for system files + // + const QString& md5() const; + + // converts timestamp() to a string for display, returns "(no timestamp)" if + // not available + // + QString timestampString() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + // contains the information from the version resource + // + struct FileInfo + { + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; + + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + QString m_md5; + + // returns information from the version resource + // + FileInfo getFileInfo() const; + + // uses VS_FIXEDFILEINFO to build the version string + // + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + + // uses the file date from VS_FIXEDFILEINFO if available, or gets the + // creation date on the file + // + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + + // returns the md5 hash unless the path contains "\windows\" + // + QString getMD5() const; + + // gets VS_FIXEDFILEINFO from the file version info buffer + // + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + + // gets FileVersion from the file version info buffer + // + QString getFileDescription(std::byte* buffer) const; +}; + + +std::vector<Module> getLoadedModules(); + +} // namespace env diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp new file mode 100644 index 00000000..376be4df --- /dev/null +++ b/src/envsecurity.cpp @@ -0,0 +1,400 @@ +#include "envsecurity.h" +#include "env.h" +#include <utility.h> +#include <log.h> + +#include <Wbemidl.h> +#include <wscapi.h> +#include <comdef.h> +#include <netfw.h> +#pragma comment(lib, "Wbemuuid.lib") + +namespace env +{ + +using namespace MOBase; + +class WMI +{ +public: + class failed {}; + + WMI(const std::string& ns) + { + try + { + createLocator(); + createService(ns); + setSecurity(); + } + catch(failed&) + { + } + } + + template <class F> + void query(const std::string& q, F&& f) + { + if (!m_locator || !m_service) { + return; + } + + auto enumerator = getEnumerator(q); + if (!enumerator) { + return; + } + + for (;;) + { + COMPtr<IWbemClassObject> object; + + { + IWbemClassObject* rawObject = nullptr; + ULONG count = 0; + auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); + + if (count == 0 || !rawObject) { + break; + } + + if (FAILED(ret)) { + log::error("enum->next() failed, {}", formatSystemMessage(ret)); + break; + } + + object.reset(rawObject); + } + + f(object.get()); + } + } + +private: + COMPtr<IWbemLocator> m_locator; + COMPtr<IWbemServices> m_service; + + void createLocator() + { + void* rawLocator = nullptr; + + const auto ret = CoCreateInstance( + CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, &rawLocator); + + if (FAILED(ret) || !rawLocator) { + log::error( + "CoCreateInstance for WbemLocator failed, {}", + formatSystemMessage(ret)); + + throw failed(); + } + + m_locator.reset(static_cast<IWbemLocator*>(rawLocator)); + } + + void createService(const std::string& ns) + { + IWbemServices* rawService = nullptr; + + const auto res = m_locator->ConnectServer( + _bstr_t(ns.c_str()), + nullptr, nullptr, nullptr, 0, nullptr, nullptr, + &rawService); + + if (FAILED(res) || !rawService) { + log::error( + "locator->ConnectServer() failed for namespace '{}', {}", + ns, formatSystemMessage(res)); + + throw failed(); + } + + m_service.reset(rawService); + } + + void setSecurity() + { + auto ret = CoSetProxyBlanket( + m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); + + if (FAILED(ret)) + { + log::error("CoSetProxyBlanket() failed, {}", formatSystemMessage(ret)); + throw failed(); + } + } + + COMPtr<IEnumWbemClassObject> getEnumerator( + const std::string& query) + { + IEnumWbemClassObject* rawEnumerator = NULL; + + auto ret = m_service->ExecQuery( + bstr_t("WQL"), + bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &rawEnumerator); + + if (FAILED(ret) || !rawEnumerator) + { + log::error("query '{}' failed, {}", query, formatSystemMessage(ret)); + return {}; + } + + return COMPtr<IEnumWbemClassObject>(rawEnumerator); + } +}; + + +SecurityProduct::SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate) : + m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), + m_active(active), m_upToDate(upToDate) +{ +} + +const QString& SecurityProduct::name() const +{ + return m_name; +} + +int SecurityProduct::provider() const +{ + return m_provider; +} + +bool SecurityProduct::active() const +{ + return m_active; +} + +bool SecurityProduct::upToDate() const +{ + return m_upToDate; +} + +QString SecurityProduct::toString() const +{ + QString s; + + s += m_name + " (" + providerToString() + ")"; + + if (!m_active) { + s += ", inactive"; + } + + if (!m_upToDate) { + s += ", definitions outdated"; + } + + if (!m_guid.isNull()) { + s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); + } + + return s; +} + +QString SecurityProduct::providerToString() const +{ + QStringList ps; + + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { + ps.push_back("firewall"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + ps.push_back("autoupdate"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + ps.push_back("antivirus"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + ps.push_back("antispyware"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + ps.push_back("settings"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + ps.push_back("uac"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { + ps.push_back("service"); + } + + if (ps.empty()) { + return "doesn't provider anything"; + } + + return ps.join("|"); +} + + +std::vector<SecurityProduct> getSecurityProductsFromWMI() +{ + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map<QUuid, SecurityProduct> map; + + auto handleProduct = [&](auto* o) { + VARIANT prop; + + // display name + auto ret = o->Get(L"displayName", 0, &prop, 0, 0); + if (FAILED(ret)) { + log::error("failed to get displayName, {}", formatSystemMessage(ret)); + return; + } + + if (prop.vt != VT_BSTR) { + log::error("displayName is a {}, not a bstr", prop.vt); + return; + } + + const std::wstring name = prop.bstrVal; + VariantClear(&prop); + + // product state + ret = o->Get(L"productState", 0, &prop, 0, 0); + if (FAILED(ret)) { + log::error("failed to get productState, {}", formatSystemMessage(ret)); + return; + } + + if (prop.vt != VT_UI4 && prop.vt != VT_I4) { + log::error("productState is a {}, is not a VT_UI4", prop.vt); + return; + } + + DWORD state = 0; + if (prop.vt == VT_I4) { + state = prop.lVal; + } else { + state = prop.ulVal; + } + + VariantClear(&prop); + + // guid + ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); + return; + } + + if (prop.vt != VT_BSTR) { + log::error("instanceGuid is a {}, is not a bstr", prop.vt); + return; + } + + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); + + const auto provider = static_cast<int>((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; + + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); + + map.insert({ + guid, + {guid, QString::fromStdWString(name), provider, active, upToDate}}); + }; + + { + WMI wmi("root\\SecurityCenter2"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + { + WMI wmi("root\\SecurityCenter"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + std::vector<SecurityProduct> v; + + for (auto&& p : map) { + v.push_back(p.second); + } + + return v; +} + +std::optional<SecurityProduct> getWindowsFirewall() +{ + HRESULT hr = 0; + + COMPtr<INetFwPolicy2> policy; + + { + void* rawPolicy = nullptr; + + hr = CoCreateInstance( + __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(INetFwPolicy2), &rawPolicy); + + if (FAILED(hr) || !rawPolicy) { + log::error( + "CoCreateInstance for NetFwPolicy2 failed, {}", + formatSystemMessage(hr)); + + return {}; + } + + policy.reset(static_cast<INetFwPolicy2*>(rawPolicy)); + } + + VARIANT_BOOL enabledVariant; + + if (policy) { + hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); + if (FAILED(hr)) + { + log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr)); + return {}; + } + } + + const auto enabled = (enabledVariant != VARIANT_FALSE); + if (!enabled) { + return {}; + } + + return SecurityProduct( + {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); +} + + +std::vector<SecurityProduct> getSecurityProducts() +{ + std::vector<SecurityProduct> v; + + { + auto fromWMI = getSecurityProductsFromWMI(); + v.insert( + v.end(), + std::make_move_iterator(fromWMI.begin()), + std::make_move_iterator(fromWMI.end())); + } + + if (auto p=getWindowsFirewall()) { + v.push_back(std::move(*p)); + } + + return v; +} + +} // namespace diff --git a/src/envsecurity.h b/src/envsecurity.h new file mode 100644 index 00000000..200cb531 --- /dev/null +++ b/src/envsecurity.h @@ -0,0 +1,49 @@ +#include <QUuid> +#include <QString> + +namespace env +{ + +// represents a security product, such as an antivirus or a firewall +// +class SecurityProduct +{ +public: + SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate); + + // display name of the product + // + const QString& name() const; + + // a bunch of _WSC_SECURITY_PROVIDER flags + // + int provider() const; + + // whether the product is active + // + bool active() const; + + // whether its definitions are up-to-date + // + bool upToDate() const; + + // string representation of the above + // + QString toString() const; + +private: + QUuid m_guid; + QString m_name; + int m_provider; + bool m_active; + bool m_upToDate; + + QString providerToString() const; +}; + + +std::vector<SecurityProduct> getSecurityProducts(); + +} // namespace env diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp new file mode 100644 index 00000000..99495c39 --- /dev/null +++ b/src/envshortcut.cpp @@ -0,0 +1,374 @@ +#include "envshortcut.h" +#include "env.h" +#include "executableslist.h" +#include "instancemanager.h" +#include <utility.h> +#include <log.h> + +namespace env +{ + +using namespace MOBase; + +class ShellLinkException +{ +public: + ShellLinkException(QString s) + : m_what(std::move(s)) + { + } + + const QString& what() const + { + return m_what; + } + +private: + QString m_what; +}; + +// just a wrapper around IShellLink operations that throws ShellLinkException +// on errors +// +class ShellLinkWrapper +{ +public: + ShellLinkWrapper() + { + m_link = createShellLink(); + m_file = createPersistFile(); + } + + void setPath(const QString& s) + { + if (s.isEmpty()) { + throw ShellLinkException("path cannot be empty"); + } + + const auto r = m_link->SetPath(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set target path '%1'").arg(s)); + } + + void setArguments(const QString& s) + { + const auto r = m_link->SetArguments(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); + } + + void setDescription(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetDescription(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set description '%1'").arg(s)); + } + + void setIcon(const QString& file, int i) + { + if (file.isEmpty()) { + return; + } + + const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); + throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); + } + + void setWorkingDirectory(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); + } + + void save(const QString& path) + { + const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); + throwOnFail(r, QString("failed to save link '%1'").arg(path)); + } + +private: + COMPtr<IShellLink> m_link; + COMPtr<IPersistFile> m_file; + + void throwOnFail(HRESULT r, const QString& s) + { + if (FAILED(r)) { + throw ShellLinkException(QString("%1, %2") + .arg(s) + .arg(formatSystemMessage(r))); + } + } + + COMPtr<IShellLink> createShellLink() + { + void* link = nullptr; + + const auto r = CoCreateInstance( + CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, &link); + + throwOnFail(r, "failed to create IShellLink instance"); + + if (!link) { + throw ShellLinkException("creating IShellLink worked, pointer is null"); + } + + return COMPtr<IShellLink>(static_cast<IShellLink*>(link)); + } + + COMPtr<IPersistFile> createPersistFile() + { + void* file = nullptr; + + const auto r = m_link->QueryInterface(IID_IPersistFile, &file); + throwOnFail(r, "failed to get IPersistFile interface"); + + if (!file) { + throw ShellLinkException("querying IPersistFile worked, pointer is null"); + } + + return COMPtr<IPersistFile>(static_cast<IPersistFile*>(file)); + } +}; + + +Shortcut::Shortcut() + : m_iconIndex(0) +{ +} + +Shortcut::Shortcut(const Executable& exe) + : Shortcut() +{ + m_name = exe.title(); + m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); + + m_arguments = QString("\"moshortcut://%1:%2\"") + .arg(InstanceManager::instance().currentInstance()) + .arg(exe.title()); + + m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); + + if (exe.usesOwnIcon()) { + m_icon = exe.binaryInfo().absoluteFilePath(); + } + + m_workingDirectory = qApp->applicationDirPath(); +} + +Shortcut& Shortcut::name(const QString& s) +{ + m_name = s; + return *this; +} + +Shortcut& Shortcut::target(const QString& s) +{ + m_target = s; + return *this; +} + +Shortcut& Shortcut::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Shortcut& Shortcut::description(const QString& s) +{ + m_description = s; + return *this; +} + +Shortcut& Shortcut::icon(const QString& s, int index) +{ + m_icon = s; + m_iconIndex = index; + return *this; +} + +Shortcut& Shortcut::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +bool Shortcut::exists(Locations loc) const +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + return QFileInfo(path).exists(); +} + +bool Shortcut::toggle(Locations loc) +{ + if (exists(loc)) { + return remove(loc); + } else { + return add(loc); + } +} + +bool Shortcut::add(Locations loc) +{ + log::debug( + "adding shortcut to {}:\n" + " . name: '{}'\n" + " . target: '{}'\n" + " . arguments: '{}'\n" + " . description: '{}'\n" + " . icon: '{}' @ {}\n" + " . working directory: '{}'", + toString(loc), + m_name, + m_target, + m_arguments, + m_description, + m_icon, m_iconIndex, + m_workingDirectory); + + if (m_target.isEmpty()) { + log::error("shortcut: target is empty"); + return false; + } + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + log::debug("shorcut file will be saved at '{}'", path); + + try + { + ShellLinkWrapper link; + + link.setPath(m_target); + link.setArguments(m_arguments); + link.setDescription(m_description); + link.setIcon(m_icon, m_iconIndex); + link.setWorkingDirectory(m_workingDirectory); + + link.save(path); + + return true; + } + catch(ShellLinkException& e) + { + log::error("{}\nshortcut file was not saved", e.what()); + } + + return false; +} + +bool Shortcut::remove(Locations loc) +{ + log::debug("removing shortcut for '{}' from {}", m_name, toString(loc)); + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + log::debug("path to shortcut file is '{}'", path); + + if (!QFile::exists(path)) { + log::error("can't remove shortcut '{}', file not found", path); + return false; + } + + if (!MOBase::shellDelete({path})) { + const auto e = ::GetLastError(); + + log::error( + "failed to remove shortcut '{}', {}", + path, formatSystemMessage(e)); + + return false; + } + + return true; +} + +QString Shortcut::shortcutPath(Locations loc) const +{ + const auto dir = shortcutDirectory(loc); + if (dir.isEmpty()) { + return {}; + } + + const auto file = shortcutFilename(); + if (file.isEmpty()) { + return {}; + } + + return dir + QDir::separator() + file; +} + +QString Shortcut::shortcutDirectory(Locations loc) const +{ + QString dir; + + try + { + switch (loc) + { + case Desktop: + dir = MOBase::getDesktopDirectory(); + break; + + case StartMenu: + dir = MOBase::getStartMenuDirectory(); + break; + + case None: + default: + log::error("shortcut: bad location {}", loc); + break; + } + } + catch(std::exception&) + { + } + + return QDir::toNativeSeparators(dir); +} + +QString Shortcut::shortcutFilename() const +{ + if (m_name.isEmpty()) { + log::error("shortcut name is empty"); + return {}; + } + + return m_name + ".lnk"; +} + + +QString toString(Shortcut::Locations loc) +{ + switch (loc) + { + case Shortcut::None: + return "none"; + + case Shortcut::Desktop: + return "desktop"; + + case Shortcut::StartMenu: + return "start menu"; + + default: + return QString("? (%1)").arg(static_cast<int>(loc)); + } +} + +} // namespace diff --git a/src/envshortcut.h b/src/envshortcut.h new file mode 100644 index 00000000..82eea191 --- /dev/null +++ b/src/envshortcut.h @@ -0,0 +1,105 @@ +#include <QString> + +class Executable; + +namespace env +{ + +// an application shortcut that can be either on the desktop or the start menu +// +class Shortcut +{ +public: + // location of a shortcut + // + enum Locations + { + None = 0, + + // on the desktop + Desktop, + + // in the start menu + StartMenu + }; + + + // empty shortcut + // + Shortcut(); + + // shortcut from an executable + // + explicit Shortcut(const Executable& exe); + + // sets the name of the shortcut, shown on icons and start menu entries + // + Shortcut& name(const QString& s); + + // the program to start + // + Shortcut& target(const QString& s); + + // arguments to pass + // + Shortcut& arguments(const QString& s); + + // shows in the status bar of explorer, for example + // + Shortcut& description(const QString& s); + + // path to a binary that contains the icon and its index + // + Shortcut& icon(const QString& s, int index=0); + + // "start in" option for this shortcut + // + Shortcut& workingDirectory(const QString& s); + + + // returns whether this shortcut already exists at the given location; this + // does not check whether the shortcut parameters are different, it merely if + // the .lnk file exists + // + bool exists(Locations loc) const; + + // calls remove() if exists(), or add() + // + bool toggle(Locations loc); + + // adds the shortcut to the given location + // + bool add(Locations loc); + + // removes the shortcut from the given location + // + bool remove(Locations loc); + +private: + QString m_name; + QString m_target; + QString m_arguments; + QString m_description; + QString m_icon; + int m_iconIndex; + QString m_workingDirectory; + + // returns the path where the shortcut file should be saved + // + QString shortcutPath(Locations loc) const; + + // returns the directory where the shortcut file should be saved + // + QString shortcutDirectory(Locations loc) const; + + // returns the filename of the shortcut file that should be used when saving + // + QString shortcutFilename() const; +}; + + +// returns a string representation of the given location +// +QString toString(Shortcut::Locations loc); + +} // namespace diff --git a/src/envwindows.cpp b/src/envwindows.cpp new file mode 100644 index 00000000..3932a9b5 --- /dev/null +++ b/src/envwindows.cpp @@ -0,0 +1,237 @@ +#include "envwindows.h" +#include "env.h" +#include <utility.h> +#include <log.h> + +namespace env +{ + +using namespace MOBase; + +WindowsInfo::WindowsInfo() +{ + // loading ntdll.dll, the functions will be found with GetProcAddress() + LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + log::error("failed to load ntdll.dll while getting version"); + return; + } else { + m_reported = getReportedVersion(ntdll.get()); + m_real = getRealVersion(ntdll.get()); + } + + m_release = getRelease(); + m_elevated = getElevated(); +} + +bool WindowsInfo::compatibilityMode() const +{ + if (m_real == Version()) { + // don't know the real version, can't guess compatibility mode + return false; + } + + return (m_real != m_reported); +} + +const WindowsInfo::Version& WindowsInfo::reportedVersion() const +{ + return m_reported; +} + +const WindowsInfo::Version& WindowsInfo::realVersion() const +{ + return m_real; +} + +const WindowsInfo::Release& WindowsInfo::release() const +{ + return m_release; +} + +std::optional<bool> WindowsInfo::isElevated() const +{ + return m_elevated; +} + +QString WindowsInfo::toString() const +{ + QStringList sl; + + const QString reported = m_reported.toString(); + const QString real = m_real.toString(); + + // version + sl.push_back("version " + reported); + + // real version if different + if (compatibilityMode()) { + sl.push_back("real version " + real); + } + + // build.UBR, such as 17763.557 + if (m_release.UBR != 0) { + DWORD build = 0; + + if (compatibilityMode()) { + build = m_real.build; + } else { + build = m_reported.build; + } + + sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); + } + + // release ID + if (!m_release.ID.isEmpty()) { + sl.push_back("release " + m_release.ID); + } + + // buildlab string + if (!m_release.buildLab.isEmpty()) { + sl.push_back(m_release.buildLab); + } + + // product name + if (!m_release.productName.isEmpty()) { + sl.push_back(m_release.productName); + } + + // elevated + QString elevated = "?"; + if (m_elevated.has_value()) { + elevated = (*m_elevated ? "yes" : "no"); + } + + sl.push_back("elevated: " + elevated); + + return sl.join(", "); +} + +WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const +{ + // windows has been deprecating pretty much all the functions having to do + // with getting version information because apparently, people keep misusing + // them for feature detection + // + // there's still RtlGetVersion() though + + using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); + + auto* RtlGetVersion = reinterpret_cast<RtlGetVersionType*>( + GetProcAddress(ntdll, "RtlGetVersion")); + + if (!RtlGetVersion) { + log::error("RtlGetVersion() not found in ntdll.dll"); + return {}; + } + + OSVERSIONINFOEX vi = {}; + vi.dwOSVersionInfoSize = sizeof(vi); + + // this apparently never fails + RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); + + return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; +} + +WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const +{ + // getting the actual windows version is more difficult because all the + // functions are lying when running in compatibility mode + // + // RtlGetNtVersionNumbers() is an undocumented function that seems to work + // fine, but it might not in the future + + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast<RtlGetNtVersionNumbersType*>( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (!RtlGetNtVersionNumbers) { + log::error("RtlGetNtVersionNumbers not found in ntdll.dll"); + return {}; + } + + DWORD major=0, minor=0, build=0; + RtlGetNtVersionNumbers(&major, &minor, &build); + + // for whatever reason, the build number has 0xf0000000 set + build = 0x0fffffff & build; + + return {major, minor, build}; +} + +WindowsInfo::Release WindowsInfo::getRelease() const +{ + // there are several interesting items in the registry, but most of them + // are undocumented, not always available, and localizable + // + // most of them are used to provide as much information as possible in case + // any of the other versions fail to work + + QSettings settings( + R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", + QSettings::NativeFormat); + + Release r; + + // buildlab seems to be an internal name from the build system + r.buildLab = settings.value("BuildLabEx", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildLab", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildBranch", "").toString(); + } + } + + // localized name of windows, such as "Windows 10 Pro" + r.productName = settings.value("ProductName", "").toString(); + + // release ID, such as 1803 + r.ID = settings.value("ReleaseId", "").toString(); + + // some other build number, shown in winver.exe + r.UBR = settings.value("UBR", 0).toUInt(); + + return r; +} + +std::optional<bool> WindowsInfo::getElevated() const +{ + HandlePtr token; + + { + HANDLE rawToken = 0; + + if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { + const auto e = GetLastError(); + + log::error( + "while trying to check if process is elevated, " + "OpenProcessToken() failed: {}", formatSystemMessage(e)); + + return {}; + } + + token.reset(rawToken); + } + + TOKEN_ELEVATION e = {}; + DWORD size = sizeof(TOKEN_ELEVATION); + + if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { + const auto e = GetLastError(); + + log::error( + "while trying to check if process is elevated, " + "GetTokenInformation() failed: {}", formatSystemMessage(e)); + + return {}; + } + + return (e.TokenIsElevated != 0); +} + +} // namespace diff --git a/src/envwindows.h b/src/envwindows.h new file mode 100644 index 00000000..c23f99f4 --- /dev/null +++ b/src/envwindows.h @@ -0,0 +1,106 @@ +#include <QString> +#include <optional> + +namespace env +{ + +// a variety of information on windows +// +class WindowsInfo +{ +public: + struct Version + { + DWORD major=0, minor=0, build=0; + + QString toString() const + { + return QString("%1.%2.%3").arg(major).arg(minor).arg(build); + } + + friend bool operator==(const Version& a, const Version& b) + { + return + a.major == b.major && + a.minor == b.minor && + a.build == b.build; + } + + friend bool operator!=(const Version& a, const Version& b) + { + return !(a == b); + } + }; + + struct Release + { + // the BuildLab entry from the registry, may be empty + QString buildLab; + + // product name such as "Windows 10 Pro", may not be in English, may be + // empty + QString productName; + + // release ID such as 1809, may be mepty + QString ID; + + // some sub-build number, undocumented, may be empty + DWORD UBR; + + Release() + : UBR(0) + { + } + }; + + + WindowsInfo(); + + // tries to guess whether this process is running in compatibility mode + // + bool compatibilityMode() const; + + // returns the Windows version, may not correspond to the actual version + // if the process is running in compatibility mode + // + const Version& reportedVersion() const; + + // tries to guess the real Windows version that's running, can be empty + // + const Version& realVersion() const; + + // various information about the current release + // + const Release& release() const; + + // whether this process is running as administrator, may be empty if the + // information is not available + std::optional<bool> isElevated() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + Version m_reported, m_real; + Release m_release; + std::optional<bool> m_elevated; + + // uses RtlGetVersion() to get the version number as reported by Windows + // + Version getReportedVersion(HINSTANCE ntdll) const; + + // uses RtlGetNtVersionNumbers() to get the real version number + // + Version getRealVersion(HINSTANCE ntdll) const; + + // gets various information from the registry + // + Release getRelease() const; + + // gets whether the process is elevated + // + std::optional<bool> getElevated() const; +}; + +} // namespace diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 077d2a93..3f76bb6f 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "iplugingame.h"
#include "utility.h"
+#include <log.h>
#include <QFileInfo>
#include <QDir>
@@ -65,7 +66,7 @@ bool ExecutablesList::empty() const void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings)
{
- qDebug("setting up configured executables");
+ log::debug("loading executables");
m_Executables.clear();
@@ -103,6 +104,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) if (needsUpgrade)
upgradeFromCustom(game);
+
+ dump();
}
void ExecutablesList::store(QSettings& settings)
@@ -163,7 +166,7 @@ std::vector<Executable> ExecutablesList::getPluginExecutables( void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game)
{
- qDebug("resetting plugin executables");
+ log::debug("resetting plugin executables");
Q_ASSERT(game != nullptr);
@@ -240,16 +243,16 @@ void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags) if (flags == MoveExisting) {
const auto newTitle = makeNonConflictingTitle(exe.title());
if (!newTitle) {
- qCritical().nospace()
- << "executable '" << exe.title() << "' was in the way but could "
- << "not be renamed";
+ log::error(
+ "executable '{}' was in the way but could not be renamed",
+ exe.title());
return;
}
- qWarning().nospace()
- << "executable '" << itor->title() << "' was in the way and was "
- << "renamed to '" << *newTitle << "'";
+ log::warn(
+ "executable '{}' was in the way and was renamed to '{}'",
+ itor->title(), *newTitle);
itor->title(*newTitle);
itor = end();
@@ -286,15 +289,13 @@ std::optional<QString> ExecutablesList::makeNonConflictingTitle( title = prefix + QString(" (%1)").arg(i);
}
- qCritical().nospace()
- << "ran out of executable titles for prefix '" << prefix << "'";
-
+ log::error("ran out of executable titles for prefix '{}'", prefix);
return {};
}
void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game)
{
- qDebug() << "upgrading executables list";
+ log::debug("upgrading executables list");
Q_ASSERT(game != nullptr);
@@ -332,6 +333,31 @@ void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game) }
}
+void ExecutablesList::dump() const
+{
+ for (const auto& e : m_Executables) {
+ QStringList flags;
+
+ if (e.flags() & Executable::ShowInToolbar) {
+ flags.push_back("toolbar");
+ }
+
+ if (e.flags() & Executable::UseApplicationIcon) {
+ flags.push_back("icon");
+ }
+
+ log::debug(
+ " . executable '{}'\n"
+ " binary: {}\n"
+ " arguments: {}\n"
+ " steam ID: {}\n"
+ " directory: {}\n"
+ " flags: {} ({})",
+ e.title(), e.binaryInfo().absoluteFilePath(), e.arguments(),
+ e.steamAppID(), e.workingDirectory(), flags.join("|"), e.flags());
+ }
+}
+
Executable::Executable(QString title)
: m_title(title)
diff --git a/src/executableslist.h b/src/executableslist.h index 2d1dd28e..eda2034e 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -214,6 +214,10 @@ private: * called when MO is still using the old custom executables from 2.2.0
**/
void upgradeFromCustom(const MOBase::IPluginGame* game);
+
+ // logs all executables
+ //
+ void dump() const;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags)
diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index c5c6782b..a97d7742 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -1,13 +1,16 @@ #include "filerenamer.h" +#include <log.h> #include <QMessageBox> #include <QFileInfo> +using namespace MOBase; + FileRenamer::FileRenamer(QWidget* parent, QFlags<RenameFlags> flags) : m_parent(parent), m_flags(flags) { // sanity check for flags if ((m_flags & (HIDE|UNHIDE)) == 0) { - qCritical("renameFile() missing hide flag"); + log::error("renameFile() missing hide flag"); // doesn't really matter, it's just for text m_flags = HIDE; } @@ -15,10 +18,10 @@ FileRenamer::FileRenamer(QWidget* parent, QFlags<RenameFlags> flags) FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName) { - qDebug().nospace() << "renaming " << oldName << " to " << newName; + log::debug("renaming {} to {}", oldName, newName); if (QFileInfo(newName).exists()) { - qDebug().nospace() << newName << " already exists"; + log::debug("{} already exists", newName); // target file already exists, confirm replacement auto answer = confirmReplace(newName); @@ -26,24 +29,25 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt switch (answer) { case DECISION_SKIP: { // user wants to skip this file - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } case DECISION_REPLACE: { - qDebug().nospace() << "removing " << newName; + log::debug("removing {}", newName); + // user wants to replace the file, so remove it if (!QFile(newName).remove()) { - qWarning().nospace() << "failed to remove " << newName; + log::warn("failed to remove '{}'", newName); // removal failed, warn the user and allow canceling if (!removeFailed(newName)) { - qDebug().nospace() << "canceling " << oldName; + log::debug("canceling {}", oldName); // user wants to cancel return RESULT_CANCEL; } // ignore this file and continue on - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } @@ -53,7 +57,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt case DECISION_CANCEL: // fall-through default: { // user wants to stop - qDebug().nospace() << "canceling"; + log::debug("canceling"); return RESULT_CANCEL; } } @@ -62,22 +66,22 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt // target either didn't exist or was removed correctly if (!QFile::rename(oldName, newName)) { - qWarning().nospace() << "failed to rename " << oldName << " to " << newName; + log::warn("failed to rename '{}' to '{}'", oldName, newName); // renaming failed, warn the user and allow canceling if (!renameFailed(oldName, newName)) { // user wants to cancel - qDebug().nospace() << "canceling"; + log::debug("canceling"); return RESULT_CANCEL; } // ignore this file and continue on - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } // everything worked - qDebug().nospace() << "successfully renamed " << oldName << " to " << newName; + log::debug("successfully renamed {} to {}", oldName, newName); return RESULT_OK; } @@ -85,12 +89,12 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) { if (m_flags & REPLACE_ALL) { // user wants to silently replace all - qDebug().nospace() << "user has selected replace all"; + log::debug("user has selected replace all"); return DECISION_REPLACE; } else if (m_flags & REPLACE_NONE) { // user wants to silently skip all - qDebug().nospace() << "user has selected replace none"; + log::debug("user has selected replace none"); return DECISION_SKIP; } @@ -114,28 +118,28 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) switch (answer) { case QMessageBox::Yes: - qDebug().nospace() << "user wants to replace"; + log::debug("user wants to replace"); return DECISION_REPLACE; case QMessageBox::No: - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return DECISION_SKIP; case QMessageBox::YesToAll: - qDebug().nospace() << "user wants to replace all"; + log::debug("user wants to replace all"); // remember the answer m_flags |= REPLACE_ALL; return DECISION_REPLACE; case QMessageBox::NoToAll: - qDebug().nospace() << "user wants to replace none"; + log::debug("user wants to replace none"); // remember the answer m_flags |= REPLACE_NONE; return DECISION_SKIP; case QMessageBox::Cancel: // fall-through default: - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return DECISION_CANCEL; } } @@ -155,12 +159,12 @@ bool FileRenamer::removeFailed(const QString& name) if (answer == QMessageBox::Cancel) { // user wants to stop - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return false; } // skip this one and continue - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return true; } @@ -179,11 +183,11 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) if (answer == QMessageBox::Cancel) { // user wants to stop - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return false; } // skip this one and continue - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return true; } diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 44cbb274..0638add3 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -1,5 +1,8 @@ #include "filterwidget.h" #include "eventfilter.h" +#include <log.h> + +using namespace MOBase; FilterWidgetProxyModel::FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent) : QSortFilterProxyModel(parent), m_filter(fw) @@ -80,7 +83,7 @@ QModelIndex FilterWidget::map(const QModelIndex& index) if (m_proxy) { return m_proxy->mapToSource(index); } else { - qCritical() << "FilterWidget::map() called, but proxy isn't set up"; + log::error("FilterWidget::map() called, but proxy isn't set up"); return index; } } diff --git a/src/forcedloaddialogwidget.cpp b/src/forcedloaddialogwidget.cpp index b92838c3..b84f785f 100644 --- a/src/forcedloaddialogwidget.cpp +++ b/src/forcedloaddialogwidget.cpp @@ -1,9 +1,8 @@ #include "forcedloaddialogwidget.h" #include "ui_forcedloaddialogwidget.h" - -#include <QFileDialog> - #include "executableinfo.h" +#include <log.h> +#include <QFileDialog> using namespace MOBase; @@ -85,7 +84,7 @@ void ForcedLoadDialogWidget::on_libraryPathBrowseButton_clicked() if (fileInfo.exists()) { ui->libraryPathEdit->setText(filePath); } else { - qCritical("%ls does not exist", filePath.toStdWString().c_str()); + log::error("{} does not exist", filePath); } } } @@ -102,7 +101,7 @@ void ForcedLoadDialogWidget::on_processBrowseButton_clicked() if (fileInfo.exists()) { ui->processEdit->setText(fileName); } else { - qCritical("%ls does not exist", fileInfo.filePath().toStdWString().c_str()); + log::error("{} does not exist", fileInfo.filePath()); } } } diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 249dae6f..39038f3c 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -18,12 +18,14 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */
#include "icondelegate.h"
+#include <log.h>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QDebug>
#include <QPixmapCache>
+using namespace MOBase;
IconDelegate::IconDelegate(QObject *parent)
: QStyledItemDelegate(parent)
@@ -54,7 +56,7 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, if (!QPixmapCache::find(fullIconId, &icon)) {
icon = QIcon(iconId).pixmap(iconWidth, iconWidth);
if (icon.isNull()) {
- qWarning("failed to load icon %s", qUtf8Printable(iconId));
+ log::warn("failed to load icon {}", iconId);
}
QPixmapCache::insert(fullIconId, icon);
}
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index e443a8f2..89d0079f 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -263,7 +263,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool targetFile = wcsrchr(origFile/*data[i]->getFileName()*/, '/'); } if (targetFile == nullptr) { - qCritical() << "Failed to find backslash in " << data[i]->getFileName(); + log::error("Failed to find backslash in {}", data[i]->getFileName()); continue; } else { // skip the slash @@ -398,7 +398,7 @@ bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node *nod for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) || (!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) { - qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData()); + log::debug("{} on the top level", (*iter)->getData().name.toQString()); return true; } } @@ -424,7 +424,7 @@ DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *da (currentNode->numNodes() == 1)) { currentNode = *currentNode->nodesBegin(); } else { - qDebug("not a simple archive"); + log::debug("not a simple archive"); return nullptr; } } @@ -527,7 +527,7 @@ bool InstallationManager::testOverwrite(GuessedValue<QString> &modName, bool *me settingsFile.write(originalSettings); settingsFile.close(); } else { - qCritical("failed to restore original settings: %s", qUtf8Printable(metaFilename)); + log::error("failed to restore original settings: {}", metaFilename); } return true; } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { @@ -576,7 +576,7 @@ bool InstallationManager::doInstall(GuessedValue<QString> &modName, QString game QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory); - qDebug("installing to \"%s\"", qUtf8Printable(targetDirectoryNative)); + log::debug("installing to \"{}\"", targetDirectoryNative); m_InstallationProgress = new QProgressDialog(m_ParentWidget); ON_BLOCK_EXIT([this] () { @@ -693,7 +693,7 @@ void InstallationManager::postInstallCleanup() QFile::setPermissions(fileInfo.absoluteFilePath(), QFile::ReadOther | QFile::WriteOther); } if (!QFile::remove(fileInfo.absoluteFilePath())) { - qWarning() << "Unable to delete " << fileInfo.absoluteFilePath(); + log::warn("Unable to delete {}", fileInfo.absoluteFilePath()); } } directoriesToRemove.insert(fileInfo.absolutePath()); @@ -764,7 +764,7 @@ bool InstallationManager::install(const QString &fileName, if ((modID == 0) && (guessedModID != -1)) { modID = guessedModID; } else if (modID != guessedModID) { - qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); + log::debug("passed mod id: {}, guessed id: {}", modID, guessedModID); } modName.update(guessedModName, GUESS_GOOD); @@ -774,7 +774,7 @@ bool InstallationManager::install(const QString &fileName, if (fileInfo.dir() == QDir(m_DownloadsDirectory)) { m_CurrentFile = fileInfo.fileName(); } - qDebug("using mod name \"%s\" (id %d) -> %s", qUtf8Printable(modName), modID, qUtf8Printable(m_CurrentFile)); + log::debug("using mod name \"{}\" (id {}) -> {}", QString(modName), modID, m_CurrentFile); //If there's an archive already open, close it. This happens with the bundle //installer when it uncompresses a split archive, then finds it has a real archive @@ -785,9 +785,9 @@ bool InstallationManager::install(const QString &fileName, bool archiveOpen = m_ArchiveHandler->open(fileName, new MethodCallback<InstallationManager, void, QString *>(this, &InstallationManager::queryPassword)); if (!archiveOpen) { - qDebug("integrated archiver can't open %s: %s (%d)", - qUtf8Printable(fileName), - qUtf8Printable(getErrorString(m_ArchiveHandler->getLastError())), + log::debug("integrated archiver can't open {}: {} ({})", + fileName, + getErrorString(m_ArchiveHandler->getLastError()), m_ArchiveHandler->getLastError()); } ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); @@ -856,8 +856,7 @@ bool InstallationManager::install(const QString &fileName, } } } catch (const IncompatibilityException &e) { - qCritical("plugin \"%s\" incompatible: %s", - qUtf8Printable(installer->name()), e.what()); + log::error("plugin \"{}\" incompatible: {}", installer->name(), e.what()); } // act upon the installation result. at this point the files have already been diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index ddc2d067..fdc30e22 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "instancemanager.h" #include "selectiondialog.h" #include <utility.h> +#include <log.h> #include <appconfig.h> #include <QCoreApplication> #include <QDir> @@ -29,13 +30,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QMessageBox> #include <cstdint> +using namespace MOBase; static const char COMPANY_NAME[] = "Tannin"; static const char APPLICATION_NAME[] = "Mod Organizer"; static const char INSTANCE_KEY[] = "CurrentInstance"; - InstanceManager::InstanceManager() : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) { @@ -86,10 +87,13 @@ bool InstanceManager::deleteLocalInstance(const QString &instanceId) const if (!MOBase::shellDelete(QStringList(instancePath),true)) { - qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qUtf8Printable(instancePath), ::GetLastError()); + log::warn( + "Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", + instancePath, ::GetLastError()); + if (!MOBase::removeDir(instancePath)) { - qWarning("regular delete failed too"); + log::warn("regular delete failed too"); result = false; } } @@ -153,7 +157,7 @@ QString InstanceManager::queryInstanceName(const QStringList &instanceList) cons dialogText = dialog.textValue(); instanceId = sanitizeInstanceName(dialogText); if (instanceId != dialogText) { - if (QMessageBox::question( nullptr, + if (QMessageBox::question( nullptr, QObject::tr("Invalid instance name"), QObject::tr("The instance name \"%1\" is invalid. Use the name \"%2\" instead?").arg(dialogText,instanceId), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { @@ -220,7 +224,7 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const selection.setWindowFlags(selection.windowFlags() | Qt::WindowStaysOnTopHint); if (selection.exec() == QDialog::Rejected) { - qDebug("rejected"); + log::debug("rejected"); throw MOBase::MyException(QObject::tr("Canceled")); } @@ -323,7 +327,7 @@ QString InstanceManager::sanitizeInstanceName(const QString &name) const // Don't end in spaces and periods new_name = new_name.remove(QRegExp("\\.*$")); new_name = new_name.remove(QRegExp(" *$")); - + // Recurse until stuff stops changing if (new_name != name) { return sanitizeInstanceName(new_name); diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 8f0529ce..4d6cebd4 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <iplugingame.h>
#include <scriptextender.h>
#include <appconfig.h>
+#include <log.h>
#include <QFile>
#include <QFileInfo>
#include <QDir>
@@ -141,7 +142,7 @@ void LoadMechanism::deactivateScriptExtender() {
vfsDLLName = ToQString(AppConfig::vfs64DLLName());
}
- qDebug("USVFS DLL Name: " + vfsDLLName.toLatin1());
+ log::debug("USVFS DLL Name: {}", vfsDLLName);
if (vfsDLLName != "") {
if (QFile(pluginsDir.absoluteFilePath(vfsDLLName)).exists()) {
// remove dll from SE plugins directory
@@ -215,8 +216,8 @@ void LoadMechanism::activateScriptExtender() QString targetPath = pluginsDir.absoluteFilePath(ToQString(vfsDLL));
QString vfsDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(vfsDLL);
- qDebug("DLL USVFS Target Path: " + targetPath.toLatin1());
- qDebug("DLL USVFS VFS DLL Path: " + vfsDLLPath.toLatin1());
+ log::debug("DLL USVFS Target Path: {}", targetPath);
+ log::debug("DLL USVFS VFS DLL Path: {}", vfsDLLPath);
QFile dllFile(targetPath);
@@ -297,17 +298,17 @@ void LoadMechanism::activate(EMechanism mechanism) {
switch (mechanism) {
case LOAD_MODORGANIZER: {
- qDebug("Load Mechanism: Mod Organizer");
+ log::debug("Load Mechanism: Mod Organizer");
deactivateProxyDLL();
deactivateScriptExtender();
} break;
case LOAD_SCRIPTEXTENDER: {
- qDebug("Load Mechanism: ScriptExtender");
+ log::debug("Load Mechanism: ScriptExtender");
deactivateProxyDLL();
activateScriptExtender();
} break;
case LOAD_PROXYDLL: {
- qDebug("Load Mechanism: Proxy DLL");
+ log::debug("Load Mechanism: Proxy DLL");
deactivateScriptExtender();
activateProxyDLL();
} break;
diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp deleted file mode 100644 index dfe8f943..00000000 --- a/src/logbuffer.cpp +++ /dev/null @@ -1,278 +0,0 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "logbuffer.h"
-#include <scopeguard.h>
-#include <report.h>
-#include <QMutexLocker>
-#include <QFile>
-#include <QIcon>
-#include <QDateTime>
-#include <Windows.h>
-
-using MOBase::reportError;
-
-QScopedPointer<LogBuffer> LogBuffer::s_Instance;
-QMutex LogBuffer::s_Mutex;
-
-LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType,
- const QString &outputFileName)
- : QAbstractItemModel(nullptr)
- , m_OutFileName(outputFileName)
- , m_ShutDown(false)
- , m_MinMsgType(minMsgType)
- , m_NumMessages(0)
-{
- m_Messages.resize(messageCount);
-}
-
-LogBuffer::~LogBuffer()
-{
- qInstallMessageHandler(0);
- write();
-}
-
-void LogBuffer::logMessage(QtMsgType type, const QString &message)
-{
- if (type >= m_MinMsgType) {
- QStringList messagelist = message.split("\n");
- for (auto split_message : messagelist) {
- Message msg = {type, QTime::currentTime(), split_message};
- if (m_NumMessages < m_Messages.size()) {
- beginInsertRows(QModelIndex(), static_cast<int>(m_NumMessages),
- static_cast<int>(m_NumMessages) + 1);
- }
- m_Messages.at(m_NumMessages % m_Messages.size()) = msg;
- if (m_NumMessages < m_Messages.size()) {
- endInsertRows();
- } else {
- emit dataChanged(createIndex(0, 0),
- createIndex(static_cast<int>(m_Messages.size()), 0));
- }
- ++m_NumMessages;
- if (type >= QtCriticalMsg) {
- write();
- }
- }
- }
-}
-
-void LogBuffer::write() const
-{
- if (m_NumMessages == 0) {
- return;
- }
-
- DWORD lastError = ::GetLastError();
-
- QFile file(m_OutFileName);
- if (!file.open(QIODevice::WriteOnly)) {
- reportError(tr("failed to write log to %1: %2")
- .arg(m_OutFileName)
- .arg(file.errorString()));
- return;
- }
-
- unsigned int i
- = (m_NumMessages > m_Messages.size())
- ? static_cast<unsigned int>(m_NumMessages - m_Messages.size())
- : 0U;
- for (; i < m_NumMessages; ++i) {
- file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8());
- file.write("\r\n");
- }
- ::SetLastError(lastError);
-}
-
-void LogBuffer::init(int messageCount, QtMsgType minMsgType,
- const QString &outputFileName)
-{
- QMutexLocker guard(&s_Mutex);
-
- s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName));
- qInstallMessageHandler(LogBuffer::log);
-}
-
-char LogBuffer::msgTypeID(QtMsgType type)
-{
- switch (type) {
- case QtDebugMsg:
- return 'D';
- case QtInfoMsg:
- return 'I';
- case QtWarningMsg:
- return 'W';
- case QtCriticalMsg:
- return 'C';
- case QtFatalMsg:
- return 'F';
- default:
- return '?';
- }
-}
-
-void LogBuffer::log(QtMsgType type, const QMessageLogContext &context,
- const QString &message)
-{
- // QMutexLocker doesn't support timeout...
- if (!s_Mutex.tryLock(100)) {
- fprintf(stderr, "failed to log: %s", qUtf8Printable(message));
- return;
- }
- ON_BLOCK_EXIT([]() { s_Mutex.unlock(); });
-
- if (!s_Instance.isNull()) {
- s_Instance->logMessage(type, message);
- }
-
- if (type == QtDebugMsg) {
- fprintf(stdout, "%s [%c] %s\n", qUtf8Printable(QTime::currentTime().toString()),
- msgTypeID(type), qUtf8Printable(message));
- } else {
- if (context.line != 0) {
- fprintf(stdout, "%s [%c] (%s:%u) %s\n",
- qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type),
- context.file, context.line, qUtf8Printable(message));
- } else {
- fprintf(stdout, "%s [%c] %s\n",
- qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type),
- qUtf8Printable(message));
- }
- }
- fflush(stdout);
-}
-
-QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const
-{
- return createIndex(row, column, row);
-}
-
-QModelIndex LogBuffer::parent(const QModelIndex &) const
-{
- return QModelIndex();
-}
-
-int LogBuffer::rowCount(const QModelIndex &parent) const
-{
- if (parent.isValid())
- return 0;
- else
- return static_cast<int>(std::min(m_NumMessages, m_Messages.size()));
-}
-
-int LogBuffer::columnCount(const QModelIndex &) const
-{
- return 2;
-}
-
-QVariant LogBuffer::data(const QModelIndex &index, int role) const
-{
- unsigned int offset
- = m_NumMessages < m_Messages.size()
- ? 0
- : static_cast<unsigned int>(m_NumMessages - m_Messages.size());
- unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size();
- switch (role) {
- case Qt::DisplayRole: {
- if (index.column() == 0) {
- return m_Messages[msgIndex].time.toString("H: mm: ss");
- } else if (index.column() == 1) {
- const QString &msg = m_Messages[msgIndex].message;
- if (msg.length() < 200) {
- return msg;
- } else {
- return msg.mid(0, 200) + "...";
- }
- }
- } break;
- case Qt::DecorationRole: {
- if (index.column() == 1) {
- switch (m_Messages[msgIndex].type) {
- case QtDebugMsg:
- case QtInfoMsg:
- return QIcon(":/MO/gui/information");
- case QtWarningMsg:
- return QIcon(":/MO/gui/warning");
- case QtCriticalMsg:
- return QIcon(":/MO/gui/important");
- case QtFatalMsg:
- return QIcon(":/MO/gui/problem");
- }
- }
- } break;
- case Qt::UserRole: {
- if (index.column() == 1) {
- switch (m_Messages[msgIndex].type) {
- case QtDebugMsg:
- return "D";
- case QtInfoMsg:
- return "I";
- case QtWarningMsg:
- return "W";
- case QtCriticalMsg:
- return "C";
- case QtFatalMsg:
- return "F";
- }
- }
- } break;
- }
- return QVariant();
-}
-
-void LogBuffer::writeNow()
-{
- QMutexLocker guard(&s_Mutex);
- if (!s_Instance.isNull()) {
- s_Instance->write();
- }
-}
-
-void LogBuffer::cleanQuit()
-{
- QMutexLocker guard(&s_Mutex);
- if (!s_Instance.isNull()) {
- s_Instance->m_ShutDown = true;
- }
-}
-
-void log(const char *format, ...)
-{
- va_list argList;
- va_start(argList, format);
-
- static const int BUFFERSIZE = 1000;
-
- char buffer[BUFFERSIZE + 1];
- buffer[BUFFERSIZE] = '\0';
-
- vsnprintf(buffer, BUFFERSIZE, format, argList);
-
- qCritical("%s", buffer);
-
- va_end(argList);
-}
-
-QString LogBuffer::Message::toString() const
-{
- return QString("%1 [%2] %3")
- .arg(time.toString())
- .arg(msgTypeID(type))
- .arg(message);
-}
diff --git a/src/logbuffer.h b/src/logbuffer.h deleted file mode 100644 index 0cfecfa2..00000000 --- a/src/logbuffer.h +++ /dev/null @@ -1,95 +0,0 @@ -/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef LOGBUFFER_H
-#define LOGBUFFER_H
-
-#include <QObject>
-#include <QMutex>
-#include <QScopedPointer>
-#include <QStringListModel>
-#include <QTime>
-#include <vector>
-
-
-class LogBuffer : public QAbstractItemModel
-{
- Q_OBJECT
-
-public:
-
- static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName);
- static void log(QtMsgType type, const QMessageLogContext &context, const QString &message);
-
- static void writeNow();
- static void cleanQuit();
-
- static LogBuffer *instance() { return s_Instance.data(); }
-
-public:
-
- virtual ~LogBuffer();
-
- void logMessage(QtMsgType type, const QString &message);
-
- // QAbstractItemModel interface
-public:
- QModelIndex index(int row, int column, const QModelIndex &parent) const;
- QModelIndex parent(const QModelIndex &child) const;
- int rowCount(const QModelIndex &parent) const;
- int columnCount(const QModelIndex &parent) const;
- QVariant data(const QModelIndex &index, int role) const;
-
-signals:
-
-public slots:
-
-private:
-
- explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName);
- LogBuffer(const LogBuffer &reference); // not implemented
- LogBuffer &operator=(const LogBuffer &reference); // not implemented
-
- void write() const;
-
- static char msgTypeID(QtMsgType type);
-
-private:
-
- struct Message {
- QtMsgType type;
- QTime time;
- QString message;
- QString toString() const;
- };
-
-private:
-
- static QScopedPointer<LogBuffer> s_Instance;
- static QMutex s_Mutex;
-
- QString m_OutFileName;
- bool m_ShutDown;
- QtMsgType m_MinMsgType;
- size_t m_NumMessages;
- std::vector<Message> m_Messages;
-
-};
-
-#endif // LOGBUFFER_H
diff --git a/src/loglist.cpp b/src/loglist.cpp new file mode 100644 index 00000000..192913b6 --- /dev/null +++ b/src/loglist.cpp @@ -0,0 +1,254 @@ +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "loglist.h"
+#include "organizercore.h"
+
+using namespace MOBase;
+
+static LogModel* g_instance = nullptr;
+const std::size_t MaxLines = 1000;
+
+LogModel::LogModel()
+{
+ connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); });
+}
+
+void LogModel::create()
+{
+ g_instance = new LogModel;
+}
+
+LogModel& LogModel::instance()
+{
+ return *g_instance;
+}
+
+void LogModel::add(MOBase::log::Entry e)
+{
+ emit entryAdded(std::move(e));
+}
+
+void LogModel::clear()
+{
+ beginResetModel();
+ m_entries.clear();
+ endResetModel();
+}
+
+const std::deque<MOBase::log::Entry>& LogModel::entries() const
+{
+ return m_entries;
+}
+
+void LogModel::onEntryAdded(MOBase::log::Entry e)
+{
+ bool full = false;
+ if (m_entries.size() > MaxLines) {
+ m_entries.pop_front();
+ full = true;
+ }
+
+ const int row = static_cast<int>(m_entries.size());
+
+ if (!full) {
+ beginInsertRows(QModelIndex(), row, row + 1);
+ }
+
+ m_entries.emplace_back(std::move(e));
+
+ if (!full) {
+ endInsertRows();
+ } else {
+ emit dataChanged(
+ createIndex(row, 0),
+ createIndex(row + 1, columnCount({})));
+ }
+}
+
+QModelIndex LogModel::index(int row, int column, const QModelIndex&) const
+{
+ return createIndex(row, column, row);
+}
+
+QModelIndex LogModel::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+int LogModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ else
+ return static_cast<int>(m_entries.size());
+}
+
+int LogModel::columnCount(const QModelIndex&) const
+{
+ return 3;
+}
+
+QVariant LogModel::data(const QModelIndex& index, int role) const
+{
+ using namespace std::chrono;
+
+ const auto row = static_cast<std::size_t>(index.row());
+ if (row >= m_entries.size()) {
+ return {};
+ }
+
+ const auto& e = m_entries[row];
+
+ if (role == Qt::DisplayRole) {
+ if (index.column() == 1) {
+ const auto ms = duration_cast<milliseconds>(e.time.time_since_epoch());
+ const auto s = duration_cast<seconds>(ms);
+
+ const std::time_t tt = s.count();
+ const int frac = static_cast<int>(ms.count() % 1000);
+
+ const auto time = QDateTime::fromTime_t(tt).time().addMSecs(frac);
+ return time.toString("hh:mm:ss.zzz");
+ } else if (index.column() == 2) {
+ return QString::fromStdString(e.message);
+ }
+ }
+
+ if (role == Qt::DecorationRole) {
+ if (index.column() == 0) {
+ switch (e.level) {
+ case log::Warning:
+ return QIcon(":/MO/gui/warning");
+
+ case log::Error:
+ return QIcon(":/MO/gui/problem");
+
+ case log::Debug: // fall-through
+ case log::Info:
+ default:
+ return {};
+ }
+ }
+ }
+
+ return QVariant();
+}
+
+QVariant LogModel::headerData(int, Qt::Orientation, int) const
+{
+ return {};
+}
+
+
+LogList::LogList(QWidget* parent)
+ : QTreeView(parent), m_core(nullptr)
+{
+ setModel(&LogModel::instance());
+
+ const int timestampWidth = QFontMetrics(font()).width("00:00:00.000");
+
+ header()->setMinimumSectionSize(0);
+ header()->resizeSection(0, 20);
+ header()->resizeSection(1, timestampWidth + 8);
+
+ setAutoScroll(true);
+ scrollToBottom();
+
+ connect(
+ this, &QWidget::customContextMenuRequested,
+ [&](auto&& pos){ onContextMenu(pos); });
+
+ connect(
+ model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
+ this, SLOT(scrollToBottom()));
+
+ connect(
+ model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+ this, SLOT(scrollToBottom()));
+}
+
+void LogList::setCore(OrganizerCore& core)
+{
+ m_core = &core;
+}
+
+void LogList::copyToClipboard()
+{
+ std::string s;
+
+ auto* m = static_cast<LogModel*>(model());
+ for (const auto& e : m->entries()) {
+ s += e.formattedMessage + "\n";
+ }
+
+ if (!s.empty()) {
+ // last newline
+ s.pop_back();
+ }
+
+ QApplication::clipboard()->setText(QString::fromStdString(s));
+}
+
+void LogList::clear()
+{
+ static_cast<LogModel*>(model())->clear();
+}
+
+QMenu* LogList::createMenu(QWidget* parent)
+{
+ auto* menu = new QMenu(parent);
+
+ menu->addAction(tr("&Copy all"), [&]{ copyToClipboard(); });
+ menu->addSeparator();
+ menu->addAction(tr("C&lear all"), [&]{ clear(); });
+
+ auto* levels = new QMenu(tr("&Level"));
+ menu->addMenu(levels);
+
+ auto* ag = new QActionGroup(menu);
+
+ auto addAction = [&](auto&& text, auto&& level) {
+ auto* a = new QAction(text, ag);
+
+ a->setCheckable(true);
+ a->setChecked(log::getDefault().level() == level);
+
+ connect(a, &QAction::triggered, [this, level]{
+ if (m_core) {
+ m_core->setLogLevel(level);
+ }
+ });
+
+ levels->addAction(a);
+ };
+
+ addAction(tr("&Debug"), log::Debug);
+ addAction(tr("&Info"), log::Info);
+ addAction(tr("&Warnings"), log::Warning);
+ addAction(tr("&Errors"), log::Error);
+
+ return menu;
+}
+
+void LogList::onContextMenu(const QPoint& pos)
+{
+ auto* menu = createMenu(this);
+ menu->popup(viewport()->mapToGlobal(pos));
+}
diff --git a/src/loglist.h b/src/loglist.h new file mode 100644 index 00000000..0b25dfd1 --- /dev/null +++ b/src/loglist.h @@ -0,0 +1,79 @@ +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef LOGBUFFER_H
+#define LOGBUFFER_H
+
+#include <QTreeView>
+#include <log.h>
+
+class OrganizerCore;
+
+class LogModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ static void create();
+ static LogModel& instance();
+
+ void add(MOBase::log::Entry e);
+ void clear();
+
+ const std::deque<MOBase::log::Entry>& entries() const;
+
+protected:
+ QModelIndex index(int row, int column, const QModelIndex& parent) const override;
+ QModelIndex parent(const QModelIndex &child) const override;
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ QVariant headerData(
+ int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override;
+
+signals:
+ void entryAdded(MOBase::log::Entry e);
+
+private:
+ std::deque<MOBase::log::Entry> m_entries;
+
+ LogModel();
+ void onEntryAdded(MOBase::log::Entry e);
+};
+
+
+class LogList : public QTreeView
+{
+public:
+ LogList(QWidget* parent=nullptr);
+
+ void setCore(OrganizerCore& core);
+
+ void copyToClipboard();
+ void clear();
+
+ QMenu* createMenu(QWidget* parent=nullptr);
+
+private:
+ OrganizerCore* m_core;
+ void onContextMenu(const QPoint& pos);
+};
+
+#endif // LOGBUFFER_H
diff --git a/src/main.cpp b/src/main.cpp index bf4e5b97..0e61a781 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,7 +39,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "singleinstance.h" #include "utility.h" #include "helper.h" -#include "logbuffer.h" +#include "loglist.h" #include "selectiondialog.h" #include "moapplication.h" #include "tutorialmanager.h" @@ -47,10 +47,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "instancemanager.h" #include "moshortcut.h" #include "organizercore.h" +#include "env.h" +#include "envmodule.h" #include <eh.h> #include <windows_error.h> #include <usvfs.h> +#include <log.h> #include <QApplication> #include <QPushButton> @@ -132,9 +135,9 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except int dumpRes = CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); if (!dumpRes) - qCritical("ModOrganizer has crashed, crash dump created."); + log::error("ModOrganizer has crashed, crash dump created."); else - qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError()); + log::error("ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", dumpRes, GetLastError()); if (prevUnhandledExceptionFilter) return prevUnhandledExceptionFilter(exceptionPtrs); @@ -252,17 +255,17 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { - qDebug("profile overwritten on command line"); + log::debug("profile overwritten on command line"); selectedProfileName = arguments.at(profileIndex + 1); } arguments.removeAt(profileIndex); arguments.removeAt(profileIndex); } if (selectedProfileName.isEmpty()) { - qDebug("no configured profile"); + log::debug("no configured profile"); selectedProfileName = "Default"; } else { - qDebug("configured profile: %s", qUtf8Printable(selectedProfileName)); + log::debug("configured profile: {}", selectedProfileName); } return selectedProfileName; @@ -424,9 +427,6 @@ void setupPath() { static const int BUFSIZE = 4096; - qDebug("MO at: %s", qUtf8Printable(QDir::toNativeSeparators( - QCoreApplication::applicationDirPath()))); - QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); boost::scoped_array<TCHAR> oldPath(new TCHAR[BUFSIZE]); @@ -446,8 +446,6 @@ void setupPath() void preloadDll(const QString& filename) { - qDebug().nospace() << "preloading " << filename; - if (GetModuleHandleW(filename.toStdWString().c_str())) { // already loaded, this can happen when "restarting" MO by switching // instances, for example @@ -460,16 +458,13 @@ void preloadDll(const QString& filename) const auto dllPath = appPath + "\\" + filename; if (!QFile::exists(dllPath)) { - qWarning().nospace() << dllPath << "not found"; + log::warn("{} not found", dllPath); return; } if (!LoadLibraryW(dllPath.toStdWString().c_str())) { const auto e = GetLastError(); - - qWarning().nospace() - << "failed to load " << dllPath << ": " - << formatSystemMessage(e); + log::warn("failed to load {}: {}", dllPath, formatSystemMessage(e)); } } @@ -489,84 +484,129 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -int runApplication(MOApplication &application, SingleInstance &instance, - const QString &splashPath) +void dumpSettings(QSettings& settings) { + static const QStringList ignore({ + "username", "password", "nexus_api_key" + }); - qDebug().nospace() - << "Starting Mod Organizer version " - << getVersionDisplayString() << " revision " << GITID; + log::debug("settings:"); -#if !defined(QT_NO_SSL) - preloadSsl(); - qDebug("ssl support: %d", QSslSocket::supportsSsl()); -#else - qDebug("non-ssl build"); -#endif + settings.beginGroup("Settings"); - { - env::Environment env; + for (auto k : settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } - qDebug().nospace().noquote() - << "windows: " << env.windowsInfo().toString(); + log::debug(" . {}={}", k, settings.value(k).toString()); + } - if (env.windowsInfo().compatibilityMode()) { - qWarning() << "MO seems to be running in compatibility mode"; - } + settings.endGroup(); +} - qDebug().nospace().noquote() << "security products:"; - for (const auto& sp : env.securityProducts()) { - qDebug().nospace().noquote() << " . " << sp.toString(); +void checkMissingFiles() +{ + // files that are likely to be eaten + static const QStringList files({ + "helper.exe", "nxmhandler.exe", + "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", + "usvfs_x64.dll", "usvfs_x86.dll" + }); + + const auto dir = QCoreApplication::applicationDirPath(); + + for (const auto& name : files) { + const QFileInfo file(dir + QDir::separator() + name); + if (!file.exists()) { + log::warn( + "'{}' seems to be missing, an antivirus may have deleted it", + file.absoluteFilePath()); } + } +} + +void checkNahimic(const env::Environment& e) +{ + for (auto&& m : e.loadedModules()) { + const QFileInfo file(m.path()); - qDebug() << "modules loaded in process:"; - for (const auto& m : env.loadedModules()) { - qDebug().nospace().noquote() << " . " << m.toString(); + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { + log::warn( + "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "Mod Organizer, such as freezing or blank windows. Consider " + "uninstalling it."); + + break; } } +} + +void sanityChecks(const env::Environment& e) +{ + checkMissingFiles(); + checkNahimic(e); +} + + +int runApplication(MOApplication &application, SingleInstance &instance, + const QString &splashPath) +{ + log::info( + "starting Mod Organizer version {} revision {} in {}", + getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); + + preloadSsl(); + if (!QSslSocket::supportsSsl()) { + log::warn("no ssl support"); + } QString dataPath = application.property("dataPath").toString(); - qDebug("data path: %s", qUtf8Printable(dataPath)); + log::info("data path: {}", dataPath); if (!bootstrap()) { reportError("failed to set up data paths"); return 1; } - QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); + QWindowsWindowFunctions::setWindowActivationBehavior( + QWindowsWindowFunctions::AlwaysActivateWindow); QStringList arguments = application.arguments(); try { - qDebug("Working directory: %s", qUtf8Printable(QDir::toNativeSeparators(QDir::currentPath()))); + log::info("working directory: {}", QDir::currentPath()); + + QSettings initSettings( + dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), + QSettings::IniFormat); - QSettings settings(dataPath + "/" - + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); + Settings settings(initSettings); + log::getDefault().setLevel(settings.logLevel()); - // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast<int>(CrashDumpsType::Mini)).toInt()); + // global crashDumpType sits in OrganizerCore to make a bit less ugly to + // update it when the settings are changed during runtime + OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); - qDebug("Loaded settings:"); - settings.beginGroup("Settings"); - for (auto k : settings.allKeys()) - if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) - qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); - settings.endGroup(); + env::Environment env; + env.dump(); + dumpSettings(initSettings); + sanityChecks(env); - qDebug("initializing core"); + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { reportError("failed to set up data paths"); return 1; } - qDebug("initialize plugins"); + + log::debug("initializing plugins"); PluginContainer pluginContainer(&organizer); pluginContainer.loadPlugins(); MOBase::IPluginGame *game = determineCurrentGame( - application.applicationDirPath(), settings, pluginContainer); + application.applicationDirPath(), initSettings, pluginContainer); if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); @@ -583,15 +623,13 @@ int runApplication(MOApplication &application, SingleInstance &instance, QImage image(pluginSplash); if (!image.isNull()) { image.save(dataPath + "/splash.png"); - } else { - qDebug("no plugin splash"); } } organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (!settings.contains("game_edition")) { + if (!initSettings.contains("game_edition")) { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -607,18 +645,17 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - settings.setValue("game_edition", selection.getChoiceString()); + initSettings.setValue("game_edition", selection.getChoiceString()); } } } - game->setGameVariant(settings.value("game_edition").toString()); + game->setGameVariant(initSettings.value("game_edition").toString()); - qDebug("managing game at %s", qUtf8Printable(QDir::toNativeSeparators( - game->gameDirectory().absolutePath()))); + log::info("managing game at {}", game->gameDirectory().absolutePath()); - organizer.updateExecutablesList(settings); + organizer.updateExecutablesList(initSettings); - QString selectedProfileName = determineProfile(arguments, settings); + QString selectedProfileName = determineProfile(arguments, initSettings); organizer.setCurrentProfile(selectedProfileName); // if we have a command line parameter, it is either a nxm link or @@ -638,13 +675,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } else if (OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("starting download from command line: %s", - qUtf8Printable(arguments.at(1))); + log::debug("starting download from command line: {}", arguments.at(1)); organizer.externalMessage(arguments.at(1)); } else { QString exeName = arguments.at(1); - qDebug("starting %s from command line", qUtf8Printable(exeName)); + log::debug("starting {} from command line", exeName); arguments.removeFirst(); // remove application name (ModOrganizer.exe) arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary @@ -663,8 +699,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - if (settings.contains("window_monitor")) { - const int monitor = settings.value("window_monitor").toInt(); + if (initSettings.contains("window_monitor")) { + const int monitor = initSettings.value("window_monitor").toInt(); if (monitor != -1 && QGuiApplication::screens().size() > monitor) { QGuiApplication::screens().at(monitor)->geometry().center(); @@ -684,21 +720,21 @@ int runApplication(MOApplication &application, SingleInstance &instance, NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } - qDebug("initializing tutorials"); + log::debug("initializing tutorials"); TutorialManager::init( qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { + if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) { // disable invalid stylesheet - settings.setValue("Settings/style", ""); + initSettings.setValue("Settings/style", ""); } int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(settings, organizer, pluginContainer); + MainWindow mainWindow(initSettings, organizer, pluginContainer); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(&mainWindow); @@ -709,9 +745,13 @@ int runApplication(MOApplication &application, SingleInstance &instance, SLOT(externalMessage(QString))); mainWindow.processUpdates(); + + // this must be before readSettings(), see DockFixer in mainwindow.cpp + splash.finish(&mainWindow); + mainWindow.readSettings(); - qDebug("displaying main window"); + log::debug("displaying main window"); mainWindow.show(); mainWindow.activateWindow(); @@ -732,16 +772,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, int doCoreDump(env::CoreDumpTypes type) { - // open a console - AllocConsole(); - - // redirect stdin, stdout and stderr to it - FILE* in=nullptr; - FILE* out=nullptr; - FILE* err=nullptr; - freopen_s(&in, "CONIN$", "r", stdin); - freopen_s(&out, "CONOUT$", "w", stdout); - freopen_s(&err, "CONOUT$", "w", stderr); + env::Console c; // dump const auto b = env::coredumpOther(type); @@ -752,17 +783,69 @@ int doCoreDump(env::CoreDumpTypes type) std::wcerr << L"Press enter to continue..."; std::wcin.get(); - // close redirected handles - std::fclose(err); - std::fclose(out); - std::fclose(in); + return (b ? 0 : 1); +} + +log::Levels convertQtLevel(QtMsgType t) +{ + switch (t) + { + case QtDebugMsg: + return log::Debug; - // close console - FreeConsole(); + case QtWarningMsg: + return log::Warning; - return (b ? 0 : 1); + case QtCriticalMsg: // fall-through + case QtFatalMsg: + return log::Error; + + case QtInfoMsg: // fall-through + default: + return log::Info; + } } +void qtLogCallback( + QtMsgType type, const QMessageLogContext& context, const QString& message) +{ + std::string_view file = ""; + + if (type != QtDebugMsg) { + if (context.file) { + file = context.file; + + const auto lastSep = file.find_last_of("/\\"); + if (lastSep != std::string_view::npos) { + file = {context.file + lastSep + 1}; + } + } + } + + if (file.empty()) { + log::log( + convertQtLevel(type), "{}", + message.toStdString()); + } else { + log::log( + convertQtLevel(type), "[{}:{}] {}", + file, context.line, message.toStdString()); + } +} + +void initLogging() +{ + LogModel::create(); + + log::createDefault(MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$"); + + log::getDefault().setCallback( + [](log::Entry e){ LogModel::instance().add(e); }); + + qInstallMessageHandler(qtLogCallback); +} + + int main(int argc, char *argv[]) { // handle --crashdump first @@ -776,6 +859,8 @@ int main(int argc, char *argv[]) } } + initLogging(); + //Make sure the configured temp folder exists QDir tempDir = QDir::temp(); if (!tempDir.exists()) @@ -809,7 +894,7 @@ int main(int argc, char *argv[]) if (moshortcut || arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("not primary instance, sending shortcut/download message"); + log::debug("not primary instance, sending shortcut/download message"); instance.sendMessage(arguments.at(1)); return 0; } else if (arguments.size() == 1) { @@ -838,7 +923,11 @@ int main(int argc, char *argv[]) // initialize dump collection only after "dataPath" since the crashes are stored under it prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - LogBuffer::init(1000000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + const auto logFile = + qApp->property("dataPath").toString() + "/logs/mo_interface.log"; + + log::getDefault().setFile(MOBase::log::File::rotating( + logFile.toStdWString(), 5*1024*1024, 5)); QString splash = dataPath + "/splash.png"; if (!QFile::exists(dataPath + "/splash.png")) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d06f79f2..7c73bc8a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,7 +59,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "installationmanager.h" #include "lockeddialog.h" #include "waitingonclosedialog.h" -#include "logbuffer.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" @@ -86,6 +85,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <usvfs.h> #include "localsavegames.h" #include "listdialog.h" +#include "envshortcut.h" #include <QAbstractItemDelegate> #include <QAbstractProxyModel> @@ -194,6 +194,102 @@ const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +// this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock +// sizes are not restored when the main window is maximized; it is used in +// MainWindow::readSettings() and MainWindow::storeSettings() +// +// there's also https://stackoverflow.com/questions/44005852, which has what +// seems to be a popular fix, but it breaks the restored size of the window +// by setting it to the desktop's resolution, so that doesn't work +// +// the only fix I could find is to remember the sizes of the docks and manually +// setting them back; saving is straightforward, but restoring is messy +// +// this also depends on the window being visible before the timer in restore() +// is fired and the timer must be processed by application.exec(); therefore, +// the splash screen _must_ be closed before readSettings() is called, because +// it has its own event loop, which seems to interfere with this +// +// all of this should become unnecessary when QTBUG-46620 is fixed +// +class DockFixer +{ +public: + static void save(MainWindow* mw, QSettings& settings) + { + const auto docks = mw->findChildren<QDockWidget*>(); + + // saves the size of each dock + for (int i=0; i<docks.size(); ++i) { + int size = 0; + + // save the width for horizontal docks, or the height for vertical + if (orientation(mw, docks[i]) == Qt::Horizontal) { + size = docks[i]->size().width(); + } else { + size = docks[i]->size().height(); + } + + settings.setValue(settingName(docks[i]), size); + } + } + + static void restore(MainWindow* mw, const QSettings& settings) + { + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; + + std::vector<DockInfo> dockInfos; + + const auto docks = mw->findChildren<QDockWidget*>(); + + // for each dock + for (int i=0; i<docks.size(); ++i) { + const QString name = settingName(docks[i]); + + if (settings.contains(name)) { + // remember this dock, its size and orientation + const auto size = settings.value(name).toInt(); + dockInfos.push_back({docks[i], size, orientation(mw, docks[i])}); + } + } + + // the main window must have had time to process the settings from + // readSettings() or it seems to override whatever is set here + // + // some people said a single processEvents() call is enough, but it doesn't + // look like it + QTimer::singleShot(1, [=] { + for (const auto& info : dockInfos) { + mw->resizeDocks({info.d}, {info.size}, info.ori); + } + }); + } + + static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d) + { + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(d) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } + } + + static QString settingName(QDockWidget* d) + { + return "geometry/" + d->objectName() + "_size"; + } +}; + + MainWindow::MainWindow(QSettings &initSettings , OrganizerCore &organizerCore , PluginContainer &pluginContainer @@ -260,17 +356,10 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - ui->logList->setModel(LogBuffer::instance()); - ui->logList->setColumnWidth(0, 100); - ui->logList->setAutoScroll(true); - ui->logList->scrollToBottom(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + ui->logList->setCore(m_OrganizerCore); + int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList<int>() << splitterSize - 100 << 100); - connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), - ui->logList, SLOT(scrollToBottom())); - connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - ui->logList, SLOT(scrollToBottom())); updateProblemsButton(); @@ -421,7 +510,8 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); - connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ toolbarMenu_aboutToShow(); }); + connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); + connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); @@ -708,7 +798,7 @@ void MainWindow::setupToolbar() ui->toolBar->insertWidget(m_linksSeparator, spacer); } else { - qWarning("no separator found on the toolbar, icons won't be right-aligned"); + log::warn("no separator found on the toolbar, icons won't be right-aligned"); } } @@ -745,7 +835,7 @@ void MainWindow::updatePinnedExecutables() exeAction->setStatusTip(exe.binaryInfo().filePath()); if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { - qDebug("failed to connect trigger?"); + log::debug("failed to connect trigger?"); } if (m_linksSeparator) { @@ -763,7 +853,7 @@ void MainWindow::updatePinnedExecutables() ui->menuRun->menuAction()->setVisible(hasLinks); } -void MainWindow::toolbarMenu_aboutToShow() +void MainWindow::updateToolbarMenu() { // well, this is a bit of a hack to allow the same toolbar menu to be shown // in both the main menu and the context menu @@ -787,6 +877,11 @@ void MainWindow::toolbarMenu_aboutToShow() ui->actionToolBarIconsAndText->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon); } +void MainWindow::updateViewMenu() +{ + ui->actionViewLog->setChecked(ui->logDock->isVisible()); +} + QMenu* MainWindow::createPopupMenu() { return ui->menuToolbars; @@ -837,6 +932,11 @@ void MainWindow::on_actionToolBarIconsAndText_triggered() setToolbarButtonStyle(Qt::ToolButtonTextUnderIcon); } +void MainWindow::on_actionViewLog_triggered() +{ + ui->logDock->setVisible(!ui->logDock->isVisible()); +} + void MainWindow::setToolbarSize(const QSize& s) { for (auto* tb : findChildren<QToolBar*>()) { @@ -1080,14 +1180,14 @@ void MainWindow::createHelpMenu() QFile file(dirIter.filePath()); if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; + log::error("Failed to open {}", fileName); continue; } QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//TL")) { QStringList params = firstLine.mid(4).trimmed().split('#'); if (params.size() != 2) { - qCritical() << "invalid header line for tutorial " << fileName << " expected 2 parameters"; + log::error("invalid header line for tutorial {}, expected 2 parameters", fileName); continue; } QAction *tutAction = new QAction(params.at(0), tutorialMenu); @@ -1191,7 +1291,7 @@ void MainWindow::hookUpWindowTutorials() QString fileName = dirIter.fileName(); QFile file(dirIter.filePath()); if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; + log::error("Failed to open {}", fileName); continue; } QString firstLine = QString::fromUtf8(file.readLine()); @@ -1237,7 +1337,7 @@ void MainWindow::showEvent(QShowEvent *event) TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); } } else { - qCritical() << firstStepsTutorial << " missing"; + log::error("{} missing", firstStepsTutorial); QPoint pos = ui->toolBar->mapToGlobal(QPoint()); pos.rx() += ui->toolBar->width() / 2; pos.ry() += ui->toolBar->height(); @@ -1256,7 +1356,7 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().registerAsNXMHandler(false); m_WasVisible = true; - updateProblemsButton(); + updateProblemsButton(); } } @@ -1300,11 +1400,6 @@ bool MainWindow::confirmExit() void MainWindow::cleanup() { - if (ui->logList->model() != nullptr) { - disconnect(ui->logList->model(), nullptr, nullptr, nullptr); - ui->logList->setModel(nullptr); - } - QWebEngineProfile::defaultProfile()->clearAllVisitedLinks(); m_IntegratedBrowser.close(); m_SaveMetaTimer.stop(); @@ -1509,7 +1604,7 @@ void MainWindow::startExeAction() QAction *action = qobject_cast<QAction*>(sender()); if (action == nullptr) { - qCritical("not an action?"); + log::error("not an action?"); return; } @@ -1519,9 +1614,7 @@ void MainWindow::startExeAction() auto itor = list.find(title); if (itor == list.end()) { - qWarning().nospace() - << "startExeAction(): executable '" << title << "' not found"; - + log::warn("startExeAction(): executable '{}' not found", title); return; } @@ -1586,7 +1679,7 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) // ensure the new index is valid if (index < 0 || index >= ui->profileBox->count()) { - qDebug("invalid profile index, using last profile"); + log::debug("invalid profile index, using last profile"); ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1); } @@ -1747,17 +1840,18 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { // read the data we need from the sub-item, then dispose of it QTreeWidgetItem *onDemandDataItem = item->child(0); - std::wstring path = ToWString(onDemandDataItem->data(0, Qt::UserRole + 1).toString()); + const QString path = onDemandDataItem->data(0, Qt::UserRole + 1).toString(); + std::wstring wspath = path.toStdWString(); bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); - std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); + std::wstring virtualPath = (wspath + L"\\").substr(6) + ToWString(item->text(0)); DirectoryEntry *dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath); if (dir != nullptr) { QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - updateTo(item, path, *dir, conflictsOnly, &fileIcon, &folderIcon); + updateTo(item, wspath, *dir, conflictsOnly, &fileIcon, &folderIcon); } else { - qWarning("failed to update view of %ls", path.c_str()); + log::warn("failed to update view of {}", path); } m_RemoveWidget.push_back(item); QTimer::singleShot(5, this, SLOT(delayedRemove())); @@ -1934,7 +2028,7 @@ void MainWindow::refreshSaveList() QDir savesDir = currentSavesDir(); savesDir.setNameFilters(filters); - qDebug("reading save games from %s", qUtf8Printable(savesDir.absolutePath())); + log::debug("reading save games from {}", savesDir.absolutePath()); QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time); for (const QFileInfo &file : files) { @@ -2135,15 +2229,6 @@ void MainWindow::fixCategories() void MainWindow::setupNetworkProxy(bool activate) { QNetworkProxyFactory::setUseSystemConfiguration(activate); -/* QNetworkProxyQuery query(QUrl("http://www.google.com"), QNetworkProxyQuery::UrlRequest); - query.setProtocolTag("http"); - QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery(query); - if ((proxies.size() > 0) && (proxies.at(0).type() != QNetworkProxy::NoProxy)) { - qDebug("Using proxy: %s", qUtf8Printable(proxies.at(0).hostName())); - QNetworkProxy::setApplicationProxy(proxies[0]); - } else { - qDebug("Not using proxy"); - }*/ } @@ -2208,6 +2293,8 @@ void MainWindow::readSettings() if (settings.value("Settings/use_proxy", false).toBool()) { activateProxy(true); } + + DockFixer::restore(this, settings); } void MainWindow::processUpdates() { @@ -2251,10 +2338,17 @@ void MainWindow::processUpdates() { if (currentVersion > lastVersion) { //NOP - } else if (currentVersion < lastVersion) - qWarning() << tr("Notice: Your current MO version (%1) is lower than the previously used one (%2). " - "The GUI may not downgrade gracefully, so you may experience oddities. " - "However, there should be no serious issues.").arg(currentVersion.toString()).arg(lastVersion.toString()).toStdWString(); + } else if (currentVersion < lastVersion) { + const auto text = tr( + "Notice: Your current MO version (%1) is lower than the previously used one (%2). " + "The GUI may not downgrade gracefully, so you may experience oddities. " + "However, there should be no serious issues.") + .arg(currentVersion.toString()) + .arg(lastVersion.toString()); + + log::warn("{}", text); + } + //save version in all case settings.setValue("version", currentVersion.toString()); } @@ -2291,10 +2385,13 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + for (const std::pair<QString, QHeaderView*> kv : m_PersistedGeometry) { QString key = QString("geometry/") + kv.first; settings.setValue(key, kv.second->saveState()); } + + DockFixer::save(this, settings); } } @@ -2320,7 +2417,7 @@ void MainWindow::unlock() { //If you come through here with a null lock pointer, it's a bug! if (m_LockDialog == nullptr) { - qDebug("Unlocking main window when already unlocked"); + log::debug("Unlocking main window when already unlocked"); return; } --m_LockCount; @@ -2575,7 +2672,7 @@ void MainWindow::directory_refreshed() if (ui->tabWidget->currentIndex() == 2) { refreshDataTreeKeepExpandedNodes(); } - + } void MainWindow::esplist_changed() @@ -2794,7 +2891,7 @@ void MainWindow::refreshFilters() while (currentID != 0) { categoriesUsed.insert(currentID); if (!cycleTest.insert(currentID).second) { - qWarning("cycle in categories: %s", qUtf8Printable(SetJoin(cycleTest, ", "))); + log::warn("cycle in categories: {}", SetJoin(cycleTest, ", ")); break; } currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); @@ -3123,7 +3220,7 @@ void MainWindow::displayModInformation( ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) { if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) { - qDebug("A different mod information dialog is open. If this is incorrect, please restart MO"); + log::debug("A different mod information dialog is open. If this is incorrect, please restart MO"); return; } std::vector<ModInfo::EFlag> flags = modInfo->getFlags(); @@ -3279,7 +3376,7 @@ void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tab { unsigned int index = ModInfo::getIndex(modName); if (index == UINT_MAX) { - qCritical("failed to resolve mod name %s", qUtf8Printable(modName)); + log::error("failed to resolve mod name {}", modName); return; } @@ -3364,7 +3461,7 @@ void MainWindow::visitOnNexus_clicked() if (modID > 0) { linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); } else { - qCritical() << "mod '" << info->name() << "' has no nexus id"; + log::error("mod '{}' has no nexus id", info->name()); } } } @@ -3878,7 +3975,7 @@ void MainWindow::moveOverwriteContentToExistingMod() } if (modAbsolutePath.isNull()) { - qWarning("Mod %s has not been found, for some reason", qUtf8Printable(result)); + log::warn("Mod {} has not been found, for some reason", result); return; } @@ -3902,7 +3999,8 @@ void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) MessageDialog::showMessage(tr("Move successful."), this); } else { - qCritical("Move operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + log::error("Move operation failed: {}", formatSystemMessage(e)); } m_OrganizerCore.refreshModList(); @@ -3931,7 +4029,8 @@ void MainWindow::clearOverwrite() updateProblemsButton(); m_OrganizerCore.refreshModList(); } else { - qCritical("Delete operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + log::error("Delete operation failed: {}", formatSystemMessage(e)); } } } @@ -4175,7 +4274,7 @@ void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int refere void MainWindow::addRemoveCategories_MenuHandler() { QMenu *menu = qobject_cast<QMenu*>(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } @@ -4189,7 +4288,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { int maxRow = -1; for (const QPersistentModelIndex &idx : selected) { - qDebug("change categories on: %s", qUtf8Printable(idx.data().toString())); + log::debug("change categories on: {}", idx.data().toString()); QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); if (modIdx.row() != m_ContextIdx.row()) { addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); @@ -4216,7 +4315,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { void MainWindow::replaceCategories_MenuHandler() { QMenu *menu = qobject_cast<QMenu*>(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } @@ -4271,10 +4370,10 @@ void MainWindow::saveArchiveList() } } if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); + log::debug("{} saved", QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName())); } } else { - qWarning("archive list not initialised"); + log::warn("archive list not initialised"); } } @@ -4291,7 +4390,7 @@ void MainWindow::checkModsForUpdates() m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { - qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); + log::warn("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); } } @@ -4411,7 +4510,7 @@ void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, categoryBox->setChecked(categoryID == info->getPrimaryCategory()); action->setDefaultWidget(categoryBox); } catch (const std::exception &e) { - qCritical("failed to create category checkbox: %s", e.what()); + log::error("failed to create category checkbox: {}", e.what()); } action->setData(categoryID); @@ -4423,7 +4522,7 @@ void MainWindow::addPrimaryCategoryCandidates() { QMenu *menu = qobject_cast<QMenu*>(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } menu->clear(); @@ -5188,13 +5287,12 @@ void MainWindow::on_actionSettings_triggered() m_statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType(), settings.executablesBlacklist()); + m_OrganizerCore.setLogLevel(settings.logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); } - void MainWindow::on_actionNexus_triggered() { const IPluginGame *game = m_OrganizerCore.managedGame(); @@ -5217,7 +5315,7 @@ void MainWindow::installTranslator(const QString &name) QString fileName = name + "_" + m_CurrentLanguage; if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { if (m_CurrentLanguage.contains(QRegularExpression("^.*_(EN|en)(-.*)?$"))) { - qDebug("localization file %s not found", qUtf8Printable(fileName)); + log::debug("localization file %s not found", fileName); } // we don't actually expect localization files for English (en, en-us, en-uk, and any variation thereof) } @@ -5242,7 +5340,7 @@ void MainWindow::languageChange(const QString &newLanguage) installTranslator(QFileInfo(fileName).baseName()); } ui->retranslateUi(this); - qDebug("loaded language %s", qUtf8Printable(newLanguage)); + log::debug("loaded language {}", newLanguage); ui->profileBox->setItemText(0, QObject::tr("<Manage...>")); @@ -5487,7 +5585,7 @@ void MainWindow::openDataOriginExplorer_clicked() const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString(); - qDebug().nospace() << "opening in explorer: " << fullPath; + log::debug("opening in explorer: {}", fullPath); shell::ExploreFile(fullPath); } @@ -5661,7 +5759,7 @@ void MainWindow::modUpdateCheck(std::multimap<QString, int> IDs) m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else - qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); + log::warn("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); } } @@ -5766,7 +5864,7 @@ void MainWindow::finishUpdateInfo() auto finalMods = watcher->result(); if (finalMods.empty()) { - qInfo("None of your mods appear to have had recent file updates."); + log::info("None of your mods appear to have had recent file updates."); } std::set<std::pair<QString, int>> organizedGames; @@ -5777,7 +5875,7 @@ void MainWindow::finishUpdateInfo() } if (!finalMods.empty() && organizedGames.empty()) - qWarning("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + log::warn("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); for (auto game : organizedGames) NexusInterface::instance(&m_PluginContainer)->requestUpdates(game.second, this, QVariant(), game.first, QString()); @@ -5920,7 +6018,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa toggleMO2EndorseState(); if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) { - qCritical("failed to disconnect endorsement slot"); + log::error("failed to disconnect endorsement slot"); } } @@ -5973,7 +6071,7 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, QNetworkReply::NetworkError error, const QString &errorString) { if (error == QNetworkReply::ContentAccessDenied || error == QNetworkReply::ContentNotFoundError) { - qDebug(qUtf8Printable(tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID))); + log::debug("{}", tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID)); } else { MessageDialog::showMessage(tr("Request to Nexus failed: %1").arg(errorString), this); } @@ -6227,9 +6325,7 @@ void MainWindow::removeFromToolbar() auto itor = list.find(title); if (itor == list.end()) { - qWarning().nospace() - << "removeFromToolbar(): executable '" << title << "' not found"; - + log::warn("removeFromToolbar(): executable '{}' not found", title); return; } @@ -6382,11 +6478,11 @@ void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) secAttributes.lpSecurityDescriptor = nullptr; if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - qCritical("failed to create stdout reroute"); + log::error("failed to create stdout reroute"); } if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - qCritical("failed to correctly set up the stdout reroute"); + log::error("failed to correctly set up the stdout reroute"); *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; } } @@ -6429,7 +6525,7 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe if (progidx != std::string::npos) { dialog.setLabelText(line.substr(progidx + 11).c_str()); } else if (erroridx != std::string::npos) { - qWarning("%s", line.c_str()); + log::warn("{}", line); errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); } else { std::smatch match; @@ -6442,7 +6538,7 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe std::string dependency(match[2].first, match[2].second); m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { - qDebug("[loot] %s", line.c_str()); + log::debug("[loot] {}", line); } } } @@ -6487,7 +6583,7 @@ void MainWindow::on_bossButton_clicked() try { m_OrganizerCore.prepareVFS(); } catch (const UsvfsConnectorException &e) { - qDebug(e.what()); + log::debug("{}", e.what()); return; } catch (const std::exception &e) { QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); @@ -6517,7 +6613,7 @@ void MainWindow::on_bossButton_clicked() if (isJobHandle) { if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { if (info.NumberOfProcessIdsInList == 0) { - qDebug("no more processes in job"); + log::debug("no more processes in job"); break; } else { if (lastProcessID != info.ProcessIdList[0]) { @@ -6683,8 +6779,13 @@ void MainWindow::on_restoreButton_clicked() if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || !shellCopy(lockedName + "." + choice, lockedName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + + const auto e = GetLastError(); + + QMessageBox::critical( + this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); } m_OrganizerCore.refreshESPList(true); } @@ -6705,25 +6806,16 @@ void MainWindow::on_restoreModsButton_clicked() QString choice = queryRestore(modlistName); if (!choice.isEmpty()) { if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + QMessageBox::critical( + this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(formatSystemMessage(e))); } m_OrganizerCore.refreshModList(false); } } -void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() -{ - QStringList lines; - QAbstractItemModel *model = ui->logList->model(); - for (int i = 0; i < model->rowCount(); ++i) { - lines.append(QString("%1 [%2] %3").arg(model->index(i, 0).data().toString()) - .arg(model->index(i, 1).data(Qt::UserRole).toString()) - .arg(model->index(i, 1).data().toString())); - } - QApplication::clipboard()->setText(lines.join("\n")); -} - void MainWindow::on_categoriesAndBtn_toggled(bool checked) { if (checked) { @@ -6794,7 +6886,7 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m { QFileInfo file(url.toLocalFile()); if (!file.exists()) { - qWarning("invalid source file: %s", qUtf8Printable(file.absoluteFilePath())); + log::warn("invalid source file: {}", file.absoluteFilePath()); return; } QString target = outputDir + "/" + file.fileName(); @@ -6827,7 +6919,8 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m success = shellCopy(file.absoluteFilePath(), target, true, this); } if (!success) { - qCritical("file operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + log::error("file operation failed: {}", formatSystemMessage(e)); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 80508787..aa49205d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -30,6 +30,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "modlistsortproxy.h" #include "savegameinfo.h" #include "tutorialcontrol.h" +#include "plugincontainer.h" //class PluginContainer; +#include "iplugingame.h" //namespace MOBase { class IPluginGame; } +#include <log.h> //Note the commented headers here can be replaced with forward references, //when I get round to cleaning up main.cpp @@ -38,10 +41,10 @@ class CategoryFactory; class LockedDialogBase; class OrganizerCore; class StatusBar; -#include "plugincontainer.h" //class PluginContainer; + class PluginListSortProxy; namespace BSA { class Archive; } -#include "iplugingame.h" //namespace MOBase { class IPluginGame; } + namespace MOBase { class IPluginModPage; } namespace MOBase { class IPluginTool; } namespace MOBase { class ISaveGame; } @@ -219,7 +222,9 @@ private: void updatePinnedExecutables(); void setToolbarSize(const QSize& s); void setToolbarButtonStyle(Qt::ToolButtonStyle s); - void toolbarMenu_aboutToShow(); + + void updateToolbarMenu(); + void updateViewMenu(); QMenu* createPopupMenu() override; void activateSelectedProfile(); @@ -654,6 +659,7 @@ private slots: // ui slots void on_actionToolBarIconsOnly_triggered(); void on_actionToolBarTextOnly_triggered(); void on_actionToolBarIconsAndText_triggered(); + void on_actionViewLog_triggered(); void on_centralWidget_customContextMenuRequested(const QPoint &pos); void on_bsaList_customContextMenuRequested(const QPoint &pos); @@ -687,7 +693,6 @@ private slots: // ui slots void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_actionCopy_Log_to_Clipboard_triggered(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 70d1cf39..6c6d0bca 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1286,23 +1286,6 @@ p, li { white-space: pre-wrap; } </item> </layout> </widget> - <widget class="QTreeView" name="logList"> - <property name="contextMenuPolicy"> - <enum>Qt::ActionsContextMenu</enum> - </property> - <property name="selectionMode"> - <enum>QAbstractItemView::NoSelection</enum> - </property> - <property name="uniformRowHeights"> - <bool>true</bool> - </property> - <property name="itemsExpandable"> - <bool>false</bool> - </property> - <property name="headerHidden"> - <bool>true</bool> - </property> - </widget> </widget> </item> </layout> @@ -1403,6 +1386,7 @@ p, li { white-space: pre-wrap; } <addaction name="actionToolBarIconsAndText"/> </widget> <addaction name="menuToolbars"/> + <addaction name="actionViewLog"/> <addaction name="separator"/> <addaction name="actionNotifications"/> </widget> @@ -1417,6 +1401,49 @@ p, li { white-space: pre-wrap; } <addaction name="menuRun"/> <addaction name="menuHelp"/> </widget> + <widget class="QDockWidget" name="logDock"> + <property name="features"> + <set>QDockWidget::AllDockWidgetFeatures</set> + </property> + <property name="windowTitle"> + <string>Log</string> + </property> + <attribute name="dockWidgetArea"> + <number>8</number> + </attribute> + <widget class="QWidget" name="dockWidgetContents"> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="LogList" name="logList"> + <property name="contextMenuPolicy"> + <enum>Qt::CustomContextMenu</enum> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + </widget> <action name="actionInstallMod"> <property name="icon"> <iconset resource="resources.qrc"> @@ -1618,20 +1645,6 @@ p, li { white-space: pre-wrap; } <string>Endorse Mod Organizer</string> </property> </action> - <action name="actionCopy_Log_to_Clipboard"> - <property name="text"> - <string>Copy &Log</string> - </property> - <property name="iconText"> - <string>Copy &Log</string> - </property> - <property name="toolTip"> - <string>Copy log to clipboard</string> - </property> - <property name="statusTip"> - <string>Copy log to clipboard</string> - </property> - </action> <action name="actionChange_Game"> <property name="icon"> <iconset resource="resources.qrc"> @@ -1736,6 +1749,14 @@ p, li { white-space: pre-wrap; } <string>Status &bar</string> </property> </action> + <action name="actionViewLog"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Log</string> + </property> + </action> </widget> <layoutdefault spacing="6" margin="11"/> <customwidgets> @@ -1769,6 +1790,11 @@ p, li { white-space: pre-wrap; } <extends>QWidget</extends> <header>sortabletreewidget.h</header> </customwidget> + <customwidget> + <class>LogList</class> + <extends>QTreeView</extends> + <header>loglist.h</header> + </customwidget> </customwidgets> <resources> <include location="resources.qrc"/> diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 6c6de3e7..78a5dd4d 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -19,10 +19,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "messagedialog.h"
#include "ui_messagedialog.h"
+#include <log.h>
#include <QTimer>
#include <QResizeEvent>
#include <Windows.h>
+using namespace MOBase;
+
MessageDialog::MessageDialog(const QString &text, QWidget *reference) :
QDialog(reference),
ui(new Ui::MessageDialog)
@@ -81,7 +84,8 @@ void MessageDialog::resizeEvent(QResizeEvent *event) void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront)
{
- qDebug("%s", qUtf8Printable(text));
+ log::debug("{}", text);
+
if (reference != nullptr) {
if (bringToFront || (qApp->activeWindow() != nullptr)) {
MessageDialog *dialog = new MessageDialog(text, reference);
diff --git a/src/moapplication.cpp b/src/moapplication.cpp index e07db437..79e931fb 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "moapplication.h"
#include <report.h>
#include <utility.h>
+#include <log.h>
#include <appconfig.h>
#include <QFile>
#include <QStringList>
@@ -36,7 +37,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDebug>
-using MOBase::reportError;
+using namespace MOBase;
class ProxyStyle : public QProxyStyle {
@@ -114,13 +115,15 @@ bool MOApplication::notify(QObject *receiver, QEvent *event) try {
return QApplication::notify(receiver, event);
} catch (const std::exception &e) {
- qCritical("uncaught exception in handler (object %s, eventtype %d): %s",
- receiver->objectName().toUtf8().constData(), event->type(), e.what());
+ log::error(
+ "uncaught exception in handler (object {}, eventtype {}): {}",
+ receiver->objectName(), event->type(), e.what());
reportError(tr("an error occurred: %1").arg(e.what()));
return false;
} catch (...) {
- qCritical("uncaught non-std exception in handler (object %s, eventtype %d)",
- receiver->objectName().toUtf8().constData(), event->type());
+ log::error(
+ "uncaught non-std exception in handler (object {}, eventtype {})",
+ receiver->objectName(), event->type());
reportError(tr("an error occurred"));
return false;
}
@@ -137,7 +140,7 @@ void MOApplication::updateStyle(const QString &fileName) if (QFile::exists(fileName)) {
setStyleSheet(QString("file:///%1").arg(fileName));
} else {
- qWarning("invalid stylesheet: %s", qUtf8Printable(fileName));
+ log::warn("invalid stylesheet: {}", fileName);
}
}
}
diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index c3142962..7110a590 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -1,6 +1,8 @@ #include "modflagicondelegate.h"
+#include <log.h>
#include <QList>
+using namespace MOBase;
ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED
, ModInfo::FLAG_CONFLICT_OVERWRITE
@@ -117,7 +119,7 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const case ModInfo::FLAG_PLUGIN_SELECTED: return QString();
case ModInfo::FLAG_TRACKED: return QStringLiteral(":/MO/gui/tracked");
default:
- qWarning("ModInfo flag %d has no defined icon", flag);
+ log::warn("ModInfo flag {} has no defined icon", flag);
return QString();
}
}
diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 3484b644..5a05e7ca 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -37,6 +37,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <appconfig.h> #include <scriptextender.h> #include <unmanagedmods.h> +#include <log.h> #include <QApplication> #include <QDirIterator> @@ -319,13 +320,12 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei } if (organizedGames.empty()) { - qWarning() << tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.")); updatesAvailable = false; } else { - qInfo() << tr( + log::info("{}", tr( "You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. " - "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods." - ); + "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods.")); } for (auto game : organizedGames) @@ -412,7 +412,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei }); if (mods.size()) { - qInfo("Checking updates for %d mods...", mods.size()); + log::info("Checking updates for {} mods...", mods.size()); for (auto mod : mods) { organizedGames.insert(std::make_pair<QString, int>(mod->getGameName().toLower(), mod->getNexusID())); @@ -422,7 +422,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); } } else { - qInfo("None of the selected mods can be updated."); + log::info("None of the selected mods can be updated."); } } @@ -530,10 +530,7 @@ QUrl ModInfo::parseCustomURL() const const auto url = QUrl::fromUserInput(getCustomURL()); if (!url.isValid()) { - qCritical() - << "mod '" << name() << "' has an invalid custom url " - << "'" << getCustomURL() << "'"; - + log::error("mod '{}' has an invalid custom url '{}'", name(), getCustomURL()); return {}; } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 47ac84be..4b1e2f76 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -176,7 +176,7 @@ void ModInfoDialog::createTabs() // check for tabs in the ui not having a corresponding tab in the list int count = ui->tabWidget->count(); if (count < 0 || count > static_cast<int>(m_tabs.size())) { - qCritical() << "mod info dialog has more tabs than expected"; + log::error("mod info dialog has more tabs than expected"); count = static_cast<int>(m_tabs.size()); } @@ -239,13 +239,13 @@ void ModInfoDialog::setMod(const QString& name) { unsigned int index = ModInfo::getIndex(name); if (index == UINT_MAX) { - qCritical() << "failed to resolve mod name " << name; + log::error("failed to resolve mod name {}", name); return; } auto mod = ModInfo::getByIndex(index); if (!mod) { - qCritical() << "mod by index " << index << " is null"; + log::error("mod by index {} is null", index); return; } @@ -307,7 +307,7 @@ void ModInfoDialog::update(bool firstTime) // changed tabInfo->tab->activated(); } else { - qCritical() << "tab index " << oldTab << " not found"; + log::error("tab index {} not found", oldTab); } } } @@ -400,7 +400,7 @@ void ModInfoDialog::reAddTabs( if (itor == orderedNames.end()) { // this shouldn't happen, it means there's a tab in the UI that's no // in the list - qCritical() << "can't sort tabs, '" << objectName << "' not found"; + log::error("can't sort tabs, '{}' not found", objectName); canSort = false; } } @@ -556,9 +556,7 @@ void ModInfoDialog::switchToTab(ModInfoTabIDs id) } // this could happen if the tab is not visible right now - qDebug() - << "can't switch to tab ID " << static_cast<int>(id) - << ", not available"; + log::debug("can't switch to tab ID {}, not available", static_cast<int>(id)); } MOShared::FilesOrigin* ModInfoDialog::getOrigin() @@ -753,7 +751,7 @@ void ModInfoDialog::onTabMoved() } if (!found) { - qCritical() << "unknown tab at index " << i; + log::error("unknown tab at index {}", i); } } } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 698c8534..03b490a2 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -364,7 +364,7 @@ void for_each_in_selection(QTreeView* tree, F&& f) const auto* model = dynamic_cast<ConflictListModel*>(tree->model()); if (!model) { - qCritical() << "tree doesn't have a ConflictListModel"; + log::error("tree doesn't have a ConflictListModel"); return; } @@ -437,10 +437,18 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) const auto n = smallSelectionSize(tree); - qDebug().nospace().noquote() - << (visible ? "unhiding" : "hiding") << " " - << (n > max_small_selection ? "a lot of" : QString("%1").arg(n)) - << " conflict files"; + // logging + { + const QString action = (visible ? "unhiding" : "hiding"); + + QString files; + if (n > max_small_selection) + files = "a lot of"; + else + files = QString("%1").arg(n); + + log::debug("{} {} conflict files", action, files); + } QFlags<FileRenamer::RenameFlags> flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); @@ -453,7 +461,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) auto* model = dynamic_cast<ConflictListModel*>(tree->model()); if (!model) { - qCritical() << "list doesn't have a ConflictListModel"; + log::error("list doesn't have a ConflictListModel"); return; } @@ -466,7 +474,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) if (visible) { if (!item->canUnhide()) { - qDebug().nospace() << "cannot unhide " << item->relativeName() << ", skipping"; + log::debug("cannot unhide {}, skipping", item->relativeName()); return true; } @@ -474,7 +482,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) } else { if (!item->canHide()) { - qDebug().nospace() << "cannot hide " << item->relativeName() << ", skipping"; + log::debug("cannot hide {}, skipping", item->relativeName()); return true; } @@ -503,10 +511,10 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) return true; }); - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; + log::debug("{} conflict files done", (visible ? "unhiding" : "hiding")); if (changed) { - qDebug().nospace() << "triggering refresh"; + log::debug("triggering refresh"); if (origin()) { emitOriginModified(); @@ -632,7 +640,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) const auto* model = dynamic_cast<ConflictListModel*>(tree->model()); if (!model) { - qCritical() << "tree doesn't have a ConflictListModel"; + log::error("tree doesn't have a ConflictListModel"); return {}; } diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index fba5d39a..3130b4bd 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -3,8 +3,9 @@ #include "modinfodialog.h" #include "settings.h" #include <report.h> +#include <log.h> -using MOBase::reportError; +using namespace MOBase; class ESPItem { @@ -297,7 +298,7 @@ void ESPsTab::onActivate() } if (esp->isActive()) { - qWarning("ESPsTab::onActive(): item is already active"); + log::warn("ESPsTab::onActive(): item is already active"); return; } @@ -348,7 +349,7 @@ void ESPsTab::onDeactivate() } if (!esp->isActive()) { - qWarning("ESPsTab::onDeactivate(): item is already inactive"); + log::warn("ESPsTab::onDeactivate(): item is already inactive"); return; } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 0b519932..207c792d 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -5,8 +5,9 @@ #include "filerenamer.h" #include <utility.h> #include <report.h> +#include <log.h> -using MOBase::reportError; +using namespace MOBase; namespace shell = MOBase::shell; // if there are more than 50 selected items in the filetree, don't bother @@ -230,19 +231,19 @@ bool FileTreeTab::deleteFileRecursive(const QModelIndex& parent) if (m_fs->isDir(index)) { if (!deleteFileRecursive(index)) { - qCritical() << "failed to delete" << m_fs->fileName(index); + log::error("failed to delete {}", m_fs->fileName(index)); return false; } } else { if (!m_fs->remove(index)) { - qCritical() << "failed to delete", m_fs->fileName(index); + log::error("failed to delete {}", m_fs->fileName(index)); return false; } } } if (!m_fs->remove(parent)) { - qCritical() << "failed to delete" << m_fs->fileName(parent); + log::error("failed to delete {}", m_fs->fileName(parent)); return false; } @@ -256,9 +257,9 @@ void FileTreeTab::changeVisibility(bool visible) bool changed = false; bool stop = false; - qDebug().nospace() - << (visible ? "unhiding" : "hiding") << " " - << selection.size() << " filetree files"; + log::debug( + "{} {} filetree files", + (visible ? "unhiding" : "hiding"), selection.size()); QFlags<FileRenamer::RenameFlags> flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); @@ -279,13 +280,13 @@ void FileTreeTab::changeVisibility(bool visible) if (visible) { if (!canUnhideFile(false, path)) { - qDebug().nospace() << "cannot unhide " << path << ", skipping"; + log::debug("cannot unhide {}, skipping", path); continue; } result = unhideFile(renamer, path); } else { if (!canHideFile(false, path)) { - qDebug().nospace() << "cannot hide " << path << ", skipping"; + log::debug("cannot hide {}, skipping", path); continue; } result = hideFile(renamer, path); @@ -311,7 +312,7 @@ void FileTreeTab::changeVisibility(bool visible) } } - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done"; + log::debug("{} filetree files done", (visible ? "unhiding" : "hiding")); if (changed) { if (origin()) { diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 69866902..10362058 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -2,7 +2,9 @@ #include "ui_modinfodialog.h" #include "settings.h" #include "utility.h" +#include <log.h> +using namespace MOBase; using namespace ImagesTabHelpers; QSize resizeWithAspectRatio(const QSize& original, const QSize& available) @@ -896,10 +898,9 @@ void File::ensureOriginalLoaded() QImageReader reader(m_path); if (!reader.read(&m_original)) { - qCritical().noquote().nospace() - << "failed to load '" << m_path << "'\n" - << reader.errorString() << " " - << "(error " << static_cast<int>(reader.error()) << ")"; + log::error( + "failed to load '{}'\n{} (error {})", + m_path, reader.errorString(), static_cast<int>(reader.error())); m_failed = true; } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index e606525c..6d28cbe3 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -6,8 +6,9 @@ #include "bbcode.h" #include <versioninfo.h> #include <utility.h> +#include <log.h> -namespace shell = MOBase::shell; +using namespace MOBase; bool isValidModID(int id) { @@ -350,7 +351,7 @@ void NexusTab::onRefreshBrowser() mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); updateWebpage(); } else { - qInfo("Mod has no valid Nexus ID, info can't be updated."); + log::info("Mod has no valid Nexus ID, info can't be updated."); } } diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 12137bcb..ce29e11e 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -68,8 +68,7 @@ ModInfoRegular::~ModInfoRegular() try { saveMeta(); } catch (const std::exception &e) { - qCritical("failed to save meta information for \"%s\": %s", - qUtf8Printable(m_Name), e.what()); + log::error("failed to save meta information for \"{}\": {}", m_Name, e.what()); } } @@ -258,14 +257,14 @@ void ModInfoRegular::saveMeta() if (metaFile.status() == QSettings::NoError) { m_MetaInfoChanged = false; } else { - qCritical() - << QString("failed to write %1/meta.ini: error %2") - .arg(absolutePath()).arg(metaFile.status()); + log::error( + "failed to write {}/meta.ini: error {}", + absolutePath(), metaFile.status()); } } else { - qCritical() - << QString("failed to write %1/meta.ini: error %2") - .arg(absolutePath()).arg(metaFile.status()); + log::error( + "failed to write {}/meta.ini: error {}", + absolutePath(), metaFile.status()); } } } @@ -425,14 +424,13 @@ bool ModInfoRegular::setName(const QString &name) return false; } if (!modDir.rename(tempName, name)) { - qCritical("rename to final name failed after successful rename to intermediate name"); + log::error("rename to final name failed after successful rename to intermediate name"); modDir.rename(tempName, m_Name); return false; } } else { if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { - qCritical("failed to rename mod %s (errorcode %d)", - qUtf8Printable(name), ::GetLastError()); + log::error("failed to rename mod {} (errorcode {})", name, ::GetLastError()); return false; } } @@ -885,8 +883,9 @@ std::vector<QString> ModInfoRegular::getIniTweaks() const int numTweaks = metaFile.beginReadArray("INI Tweaks"); if (numTweaks != 0) { - qDebug("%d active ini tweaks in %s", - numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); + log::debug( + "{} active ini tweaks in {}", + numTweaks, QDir::toNativeSeparators(metaFileName)); } for (int i = 0; i < numTweaks; ++i) { diff --git a/src/modlist.cpp b/src/modlist.cpp index 1710a98d..c591c49b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -271,11 +271,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const int categoryIdx = categoryFactory.getCategoryIndex(category); return categoryFactory.getCategoryName(categoryIdx); } catch (const std::exception &e) { - qCritical("failed to retrieve category name: %s", e.what()); + log::error("failed to retrieve category name: {}", e.what()); return QString(); } } else { - qWarning("category %d doesn't exist (may have been removed)", category); + log::warn("category {} doesn't exist (may have been removed)", category); modInfo->setCategory(category, false); return QString(); } @@ -449,7 +449,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const try { return modInfo->getDescription(); } catch (const std::exception &e) { - qCritical("invalid mod description: %s", e.what()); + log::error("invalid mod description: {}", e.what()); return QString(); } } else if (column == COL_VERSION) { @@ -488,7 +488,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const try { categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>"; } catch (const std::exception &e) { - qCritical("failed to generate tooltip: %s", e.what()); + log::error("failed to generate tooltip: {}", e.what()); return QString(); } } @@ -618,8 +618,9 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) result = true; } break; default: { - qWarning("edit on column \"%s\" not supported", - getColumnName(index.column()).toUtf8().constData()); + log::warn( + "edit on column \"{}\" not supported", + getColumnName(index.column()).toUtf8().constData()); result = false; } break; } @@ -635,9 +636,9 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) try { m_ModStateChanged(info->name(), newState); } catch (const std::exception &e) { - qCritical("failed to invoke state changed notification: %s", e.what()); + log::error("failed to invoke state changed notification: {}", e.what()); } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); + log::error("failed to invoke state changed notification: unknown exception"); } } @@ -833,7 +834,7 @@ void ModList::modInfoChanged(ModInfo::Ptr info) emit dataChanged(index(row, 0), index(row, columnCount())); emit postDataChanged(); } else { - qCritical("modInfoChanged not called after modInfoAboutToChange"); + log::error("modInfoChanged not called after modInfoAboutToChange"); } m_ChangeInfo.name = QString(); } @@ -1012,9 +1013,8 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); for (auto url : mimeData->urls()) { - //qDebug("URL drop requested: %s -> %s", qUtf8Printable(url.url()), qUtf8Printable(modDir.canonicalPath())); if (!url.isLocalFile()) { - qDebug("URL drop ignored: \"%s\" is not a local file", qUtf8Printable(url.url())); + log::debug("URL drop ignored: \"{}\" is not a local file", url.url()); continue; } @@ -1034,7 +1034,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa originName = overwriteName; relativePath = overwriteDir.relativeFilePath(sourceFile); } else { - qDebug("URL drop ignored: \"%s\" is not a known file to MO", qUtf8Printable(sourceFile)); + log::debug("URL drop ignored: \"{}\" is not a known file to MO", sourceFile); continue; } @@ -1046,7 +1046,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa if (sourceList.count()) { if (!shellMove(sourceList, targetList)) { - qDebug("Failed to move file (error %d)", ::GetLastError()); + log::debug("Failed to move file (error {})", ::GetLastError()); return false; } } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 2d9ea4a5..77ffad96 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "profile.h"
#include "messagedialog.h"
#include "qtgroupingproxy.h"
+#include <log.h>
#include <QMenu>
#include <QCheckBox>
#include <QWidgetAction>
@@ -30,6 +31,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDebug>
#include <QTreeView>
+using namespace MOBase;
ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent)
: QSortFilterProxyModel(parent)
@@ -194,7 +196,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->getPrimaryCategory()));
lt = leftCatName < rightCatName;
} catch (const std::exception &e) {
- qCritical("failed to compare categories: %s", e.what());
+ log::error("failed to compare categories: {}", e.what());
}
}
}
@@ -240,7 +242,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, // nop, already compared by priority
} break;
default: {
- qWarning() << "Sorting is not defined for column " << left.column();
+ log::warn("Sorting is not defined for column {}", left.column());
} break;
}
return lt;
@@ -474,13 +476,13 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons }
if (row >= static_cast<int>(m_Profile->numMods())) {
- qWarning("invalid row index: %d", row);
+ log::warn("invalid row index: {}", row);
return false;
}
QModelIndex idx = sourceModel()->index(row, 0, parent);
if (!idx.isValid()) {
- qDebug("invalid mod index");
+ log::debug("invalid mod index");
return false;
}
if (sourceModel()->hasChildren(idx)) {
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 2bcd72f3..0e2bb45b 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "bbcode.h" #include <utility.h> #include <util.h> +#include <log.h> #include <QApplication> #include <QNetworkCookieJar> @@ -40,12 +41,11 @@ using namespace MOShared; void throttledWarning(const APIUserAccount& user) { - qCritical() << - QString( - "You have fewer than %1 requests remaining (%2). Only downloads and " - "login validation are being allowed.") - .arg(APIUserAccount::ThrottleThreshold) - .arg(user.remainingRequests()); + log::error( + "You have fewer than {} requests remaining ({}). Only downloads and " + "login validation are being allowed.", + APIUserAccount::ThrottleThreshold, + user.remainingRequests()); } @@ -316,15 +316,15 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo } else { modID = strtol(candidate.c_str(), nullptr, 10); } - qDebug("mod id guessed: %s -> %d", qUtf8Printable(fileName), modID); + log::debug("mod id guessed: {} -> {}", fileName, modID); } else if (std::regex_search(fileNameUTF8.constData(), result, simpleexp)) { - qDebug("simple expression matched, using name only"); + log::debug("simple expression matched, using name only"); modName = QString::fromUtf8(result[1].str().c_str()); modName = modName.replace('_', ' ').trimmed(); modID = -1; } else { - qDebug("no expression matched!"); + log::debug("no expression matched!"); modName.clear(); modID = -1; } @@ -343,7 +343,7 @@ QString NexusInterface::getGameURL(QString gameName) const if (game != nullptr) { return "https://www.nexusmods.com/" + game->gameNexusName().toLower(); } else { - qCritical("getGameURL can't find plugin for %s", qUtf8Printable(gameName)); + log::error("getGameURL can't find plugin for {}", gameName); return ""; } } @@ -354,7 +354,7 @@ QString NexusInterface::getOldModsURL(QString gameName) const if (game != nullptr) { return "https://" + game->gameNexusName().toLower() + ".nexusmods.com/mods"; } else { - qCritical("getOldModsURL can't find plugin for %s", qUtf8Printable(gameName)); + log::error("getOldModsURL can't find plugin for {}", gameName); return ""; } } @@ -463,7 +463,7 @@ int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant IPluginGame *game = getGame(gameName); if (game == nullptr) { - qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName)); + log::error("requestUpdates can't find plugin for {}", gameName); return -1; } @@ -520,7 +520,7 @@ int NexusInterface::requestFileInfo(QString gameName, int modID, int fileID, QOb { IPluginGame *gamePlugin = getGame(gameName); if (gamePlugin == nullptr) { - qCritical("requestFileInfo can't find plugin for %s", qUtf8Printable(gameName)); + log::error("requestFileInfo can't find plugin for {}", gameName); return -1; } @@ -686,7 +686,7 @@ void NexusInterface::nextRequest() } else if (getAccessManager()->validateWaiting()) { return; } else { - qCritical() << tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API."); + log::error("{}", tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API.")); } } @@ -695,9 +695,13 @@ void NexusInterface::nextRequest() QTime time = QTime::currentTime(); QTime targetTime; targetTime.setHMS((time.hour() + 1) % 23, 5, 0); - QString warning = tr("You've exceeded the Nexus API rate limit and requests are now being throttled. " - "Your next batch of requests will be available in approximately %1 minutes and %2 seconds."); - qWarning() << warning.arg(time.secsTo(targetTime) / 60).arg(time.secsTo(targetTime) % 60); + QString warning = tr( + "You've exceeded the Nexus API rate limit and requests are now being throttled. " + "Your next batch of requests will be available in approximately %1 minutes and %2 seconds.") + .arg(time.secsTo(targetTime) / 60) + .arg(time.secsTo(targetTime) % 60); + + log::warn("{}", warning); return; } @@ -747,8 +751,8 @@ void NexusInterface::nextRequest() url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6") .arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires); } else { - qWarning() << tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " - "or the download link was generated by a different account than the one stored in Mod Organizer."); + log::warn("{}", tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " + "or the download link was generated by a different account than the one stored in Mod Organizer.")); return; } } break; @@ -828,16 +832,16 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter) m_User.limits(parseLimits(reply)); if (!m_User.exhausted()) { - qWarning("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team."); + log::warn("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team."); } else { - qWarning("All API requests have been consumed and are now being denied."); + log::warn("All API requests have been consumed and are now being denied."); } emit requestsChanged(getAPIStats(), m_User); - qWarning("Error: %s", reply->errorString().toUtf8().constData()); + log::warn("Error: {}", reply->errorString()); } else { - qWarning("request failed: %s", reply->errorString().toUtf8().constData()); + log::warn("request failed: {}", reply->errorString()); } emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), reply->errorString()); } else { @@ -856,7 +860,7 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter) if (nexusError.length() == 0) { nexusError = tr("empty response"); } - qDebug("nexus error: %s", qUtf8Printable(nexusError)); + log::debug("nexus error: {}", nexusError); emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), nexusError); } else { QJsonDocument responseDoc = QJsonDocument::fromJson(data); @@ -940,14 +944,13 @@ void NexusInterface::requestError(QNetworkReply::NetworkError) { QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); if (reply == nullptr) { - qWarning("invalid sender type"); + log::warn("invalid sender type"); return; } - qCritical("request (%s) error: %s (%d)", - qUtf8Printable(reply->url().toString()), - qUtf8Printable(reply->errorString()), - reply->error()); + log::error( + "request ({}) error: {} ({})", + reply->url().toString(), reply->errorString(), reply->error()); } @@ -955,7 +958,7 @@ void NexusInterface::requestTimeout() { QTimer *timer = qobject_cast<QTimer*>(sender()); if (timer == nullptr) { - qWarning("invalid sender type"); + log::warn("invalid sender type"); return; } for (std::list<NXMRequestInfo>::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index c413e156..fd1dc0c1 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -600,9 +600,9 @@ void NXMAccessManager::showCookies() const { QUrl url(NexusBaseUrl + "/"); for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) { - qDebug("%s - %s (expires: %s)", + log::debug("{} - {} (expires: {})", cookie.name().constData(), cookie.value().constData(), - qUtf8Printable(cookie.expirationDate().toString())); + cookie.expirationDate().toString()); } } @@ -612,7 +612,7 @@ void NXMAccessManager::clearCookies() if (jar != nullptr) { jar->clear(); } else { - qWarning("failed to clear cookies, invalid cookie jar"); + log::warn("failed to clear cookies, invalid cookie jar"); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index eeb69e61..1e164525 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -14,7 +14,6 @@ #include "plugincontainer.h" #include "pluginlistsortproxy.h" #include "profile.h" -#include "logbuffer.h" #include "credentialsdialog.h" #include "filedialogmemory.h" #include "modinfodialog.h" @@ -78,26 +77,21 @@ CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; static bool isOnline() { - QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); + const auto runningFlags = + QNetworkInterface::IsUp | QNetworkInterface::IsRunning; - bool connected = false; - for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; - ++iter) { - if ((iter->flags() & QNetworkInterface::IsUp) - && (iter->flags() & QNetworkInterface::IsRunning) - && !(iter->flags() & QNetworkInterface::IsLoopBack)) { - auto addresses = iter->addressEntries(); - if (addresses.count() == 0) { - continue; + for (auto&& i : QNetworkInterface::allInterfaces()) { + if (!(i.flags() & QNetworkInterface::IsLoopBack)) { + if (i.flags() & runningFlags) { + auto addresses = i.addressEntries(); + if (!addresses.empty()) { + return true; + } } - qDebug("interface %s seems to be up (address: %s)", - qUtf8Printable(iter->humanReadableName()), - qUtf8Printable(addresses[0].ip().toString())); - connected = true; } } - return connected; + return false; } static bool renameFile(const QString &oldName, const QString &newName, @@ -201,49 +195,49 @@ bool checkService() try { serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); if (!serviceManagerHandle) { - qWarning("failed to open service manager (query status) (error %d)", GetLastError()); + log::warn("failed to open service manager (query status) (error {})", GetLastError()); throw 1; } serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); if (!serviceHandle) { - qWarning("failed to open EventLog service (query status) (error %d)", GetLastError()); + log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); throw 2; } if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - qWarning("failed to get size of service config (error %d)", GetLastError()); + log::warn("failed to get size of service config (error {})", GetLastError()); throw 3; } DWORD serviceConfigSize = bytesNeeded; serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { - qWarning("failed to query service config (error %d)", GetLastError()); + log::warn("failed to query service config (error {})", GetLastError()); throw 4; } if (serviceConfig->dwStartType == SERVICE_DISABLED) { - qCritical("Windows Event Log service is disabled!"); + log::error("Windows Event Log service is disabled!"); serviceRunning = false; } if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - qWarning("failed to get size of service status (error %d)", GetLastError()); + log::warn("failed to get size of service status (error {})", GetLastError()); throw 5; } DWORD serviceStatusSize = bytesNeeded; serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { - qWarning("failed to query service status (error %d)", GetLastError()); + log::warn("failed to query service status (error {})", GetLastError()); throw 6; } if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { - qCritical("Windows Event Log service is not running"); + log::error("Windows Event Log service is not running"); serviceRunning = false; } } @@ -269,12 +263,12 @@ bool checkService() } -OrganizerCore::OrganizerCore(const QSettings &initSettings) +OrganizerCore::OrganizerCore(Settings &settings) : m_UserInterface(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) - , m_Settings(initSettings) + , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() , m_FinishedRun() @@ -295,7 +289,7 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings) NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); - MOBase::QuestionBoxMemory::init(initSettings.fileName()); + MOBase::QuestionBoxMemory::init(m_Settings.directInterface().fileName()); m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); @@ -342,7 +336,6 @@ OrganizerCore::~OrganizerCore() m_CurrentProfile = nullptr; ModInfo::clear(); - LogBuffer::cleanQuit(); m_ModList.setProfile(nullptr); // NexusInterface::instance()->cleanup(); @@ -356,7 +349,7 @@ QString OrganizerCore::commitSettings(const QString &iniFile) // make a second attempt using qt functions but if that fails print the // error from the first attempt if (!renameFile(iniFile + ".new", iniFile)) { - return windowsErrorString(err); + return QString::fromStdWString(formatSystemMessage(err)); } } return QString(); @@ -389,10 +382,12 @@ void OrganizerCore::storeSettings() + QString::fromStdWString(AppConfig::iniFileName()); if (QFileInfo(iniFile).exists()) { if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { + const auto e = GetLastError(); QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), tr("An error occurred trying to update MO settings to %1: %2") - .arg(iniFile, windowsErrorString(::GetLastError()))); + .arg(iniFile) + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } } @@ -404,8 +399,9 @@ void OrganizerCore::storeSettings() if (result == QSettings::NoError) { QString errMsg = commitSettings(iniFile); if (!errMsg.isEmpty()) { - qWarning("settings file not writable, may be locked by another " - "application, trying direct write"); + log::warn( + "settings file not writable, may be locked by another " + "application, trying direct write"); writeTarget = iniFile; result = storeSettings(iniFile); } @@ -438,7 +434,7 @@ bool OrganizerCore::testForSteam(bool *found, bool *access) hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { lastError = GetLastError(); - qCritical("unable to get snapshot of processes (error %d)", lastError); + log::error("unable to get snapshot of processes (error {})", lastError); return false; } @@ -447,7 +443,7 @@ bool OrganizerCore::testForSteam(bool *found, bool *access) pe32.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(hProcessSnap, &pe32)) { lastError = GetLastError(); - qCritical("unable to get first process (error %d)", lastError); + log::error("unable to get first process (error {})", lastError); CloseHandle(hProcessSnap); return false; } @@ -487,7 +483,7 @@ return true; void OrganizerCore::updateExecutablesList(QSettings &settings) { if (m_PluginContainer == nullptr) { - qCritical("can't update executables list now"); + log::error("can't update executables list now"); return; } @@ -544,7 +540,7 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, if (isOnline() && !m_Settings.offlineMode()) { m_Updater.testForUpdate(); } else { - qDebug("user doesn't seem to be connected to the internet"); + log::debug("user doesn't seem to be connected to the internet"); } } } @@ -606,7 +602,7 @@ bool OrganizerCore::nexusApi(bool retry) QString apiKey; if (m_Settings.getNexusApiKey(apiKey)) { // credentials stored or user entered them manually - qDebug("attempt to verify nexus api key"); + log::debug("attempt to verify nexus api key"); accessManager->apiCheck(apiKey); return true; } else { @@ -628,7 +624,7 @@ void OrganizerCore::startMOUpdate() void OrganizerCore::downloadRequestedNXM(const QString &url) { - qDebug("download requested: %s", qUtf8Printable(url)); + log::debug("download requested: {}", url); if (nexusApi()) { m_PendingDownloads.append(url); } else { @@ -658,7 +654,7 @@ void OrganizerCore::downloadRequested(QNetworkReply *reply, QString gameName, in } } catch (const std::exception &e) { MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow()); - qCritical("exception starting download: %s", e.what()); + log::error("exception starting download: {}", e.what()); } } @@ -728,11 +724,25 @@ void OrganizerCore::prepareVFS() m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString())); } -void OrganizerCore::updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist) { +void OrganizerCore::updateVFSParams( + log::Levels logLevel, int crashDumpsType, QString executableBlacklist) +{ setGlobalCrashDumpsType(crashDumpsType); m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); } +void OrganizerCore::setLogLevel(log::Levels level) +{ + m_Settings.setLogLevel(level); + + updateVFSParams( + m_Settings.logLevel(), + m_Settings.crashDumpsType(), + m_Settings.executablesBlacklist()); + + log::getDefault().setLevel(m_Settings.logLevel()); +} + bool OrganizerCore::cycleDiagnostics() { if (int maxDumps = settings().crashDumpsMax()) removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); @@ -1207,7 +1217,9 @@ QString OrganizerCore::findJavaInstallation(const QString& jarFile) if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) { DWORD binaryType = 0UL; if (!::GetBinaryTypeW(buffer, &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); + log::debug( + "failed to determine binary type of \"{}\": {}", + QString::fromWCharArray(buffer), ::GetLastError()); } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) { return QString::fromWCharArray(buffer); } @@ -1381,9 +1393,9 @@ bool OrganizerCore::previewFileWithAlternatives( // sanity check, this shouldn't happen unless the caller passed an // incorrect id - qWarning().nospace() - << "selected preview origin " << selectedOrigin << " not found in " - << "list of alternatives"; + log::warn( + "selected preview origin {} not found in list of alternatives", + selectedOrigin); } for (int id : origins) { @@ -1458,7 +1470,7 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, // need to remove our stored load order because it may be outdated if a foreign tool changed the // file time. After removing that file, refreshESPList will use the file time as the order if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - qDebug("removing loadorder.txt"); + log::debug("removing loadorder.txt"); QFile::remove(m_CurrentProfile->getLoadOrderFileName()); } refreshDirectoryStructure(); @@ -1551,7 +1563,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, bool steamFound = true; bool steamAccess = true; if (!testForSteam(&steamFound, &steamAccess)) { - qCritical("unable to determine state of Steam"); + log::error("unable to determine state of Steam"); } if (!steamFound) { @@ -1568,9 +1580,9 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, steamFound = true; steamAccess = true; if (!testForSteam(&steamFound, &steamAccess)) { - qCritical("unable to determine state of Steam"); + log::error("unable to determine state of Steam"); } else if (!steamFound) { - qCritical("could not find Steam"); + log::error("could not find Steam"); } } else if (result == QDialogButtonBox::Cancel) { @@ -1591,14 +1603,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, if (result == QDialogButtonBox::Yes) { WCHAR cwd[MAX_PATH]; if (!GetCurrentDirectory(MAX_PATH, cwd)) { - qCritical("unable to get current directory (error %d)", GetLastError()); + log::error("unable to get current directory (error {})", GetLastError()); cwd[0] = L'\0'; } if (!Helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) { - qCritical("unable to relaunch MO as admin"); + log::error("unable to relaunch MO as admin"); return INVALID_HANDLE_VALUE; } qApp->exit(0); @@ -1626,7 +1638,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, m_USVFS.updateForcedLibraries(forcedLibraries); } catch (const UsvfsConnectorException &e) { - qDebug(e.what()); + log::debug(e.what()); return INVALID_HANDLE_VALUE; } catch (const std::exception &e) { QMessageBox::warning(window, tr("Error"), e.what()); @@ -1693,17 +1705,16 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, .arg(QDir::toNativeSeparators(cwdPath), QDir::toNativeSeparators(binPath), arguments); - qDebug() << "Spawning proxyed process <" << cmdline << ">"; + log::debug("Spawning proxyed process <{}>", cmdline); return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), cmdline, QCoreApplication::applicationDirPath(), true); } else { - qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">"; + log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); return startBinary(binary, arguments, currentDirectory, true); } } else { - qDebug("start of \"%s\" canceled by plugin", - qUtf8Printable(binary.absoluteFilePath())); + log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); return INVALID_HANDLE_VALUE; } } @@ -1798,8 +1809,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, currentDirectory = exe.workingDirectory(); } } catch (const std::runtime_error &) { - qWarning("\"%s\" not set up as executable", - qUtf8Printable(executable)); + log::warn("\"{}\" not set up as executable", executable); binary = QFileInfo(executable); } } @@ -1872,16 +1882,18 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL processName += QString(" (%1)").arg(currentPID); if (uilock) uilock->setProcessName(processName); - qDebug() << "Waiting for" - << (originalHandle ? "spawned" : "usvfs") - << "process completion :" << qUtf8Printable(processName); + + log::debug( + "Waiting for {} process completion: {}", + (originalHandle ? "spawned" : "usvfs"), processName); + newHandle = false; } // Wait for a an event on the handle, a key press, mouse click or timeout res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON); if (res == WAIT_FAILED) { - qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" << GetLastError(); + log::warn("Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED {}", GetLastError()); break; } @@ -1897,7 +1909,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL if (res == WAIT_OBJECT_0) { // process we were waiting on has completed if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode)) - qWarning() << "Failed getting exit code of complete process :" << GetLastError(); + log::warn("Failed getting exit code of complete process: {}", GetLastError()); CloseHandle(handle); handle = INVALID_HANDLE_VALUE; originalHandle = false; @@ -1943,11 +1955,11 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL } if (res == WAIT_OBJECT_0) - qDebug() << "Waiting for process completion successfull"; + log::debug("Waiting for process completion successfull"); else if (uiunlocked) - qDebug() << "Waiting for process completion aborted by UI"; + log::debug("Waiting for process completion aborted by UI"); else - qDebug() << "Waiting for process completion not successfull :" << res; + log::debug("Waiting for process completion not successfull: {}", res); if (handle != INVALID_HANDLE_VALUE) ::CloseHandle(handle); @@ -1962,7 +1974,7 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector<QString>& hidde DWORD pids[querySize]; size_t found = querySize; if (!::GetVFSProcessList(&found, pids)) { - qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!"; + log::warn("Failed seeking USVFS processes : GetVFSProcessList failed?!"); return INVALID_HANDLE_VALUE; } @@ -1974,7 +1986,7 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector<QString>& hidde HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); if (handle == INVALID_HANDLE_VALUE) { - qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError(); + log::warn("Failed opening USVFS process {}: OpenProcess failed {}", pids[i], GetLastError()); continue; } @@ -2118,7 +2130,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices, dir.entryList(QStringList() << "*.esm", QDir::Files)) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esm)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esm)); + log::warn("failed to activate {}", esm); continue; } @@ -2134,7 +2146,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices, dir.entryList(QStringList() << "*.esl", QDir::Files)) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esl)); + log::warn("failed to activate {}", esl); continue; } @@ -2150,7 +2162,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices, for (const QString &esp : esps) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esp)); + log::warn("failed to activate {}", esp); continue; } @@ -2558,7 +2570,7 @@ std::vector<unsigned int> OrganizerCore::activeProblems() const // of a "log spam". But since this is a sevre error which will most likely make the // game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it // easier for the user to notice the warning. - qWarning("hook.dll found in game folder: %s", qUtf8Printable(hookdll)); + log::warn("hook.dll found in game folder: {}", hookdll); problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND); } return problems; @@ -2604,7 +2616,7 @@ void OrganizerCore::startGuidedFix(unsigned int) const bool OrganizerCore::saveCurrentLists() { if (m_DirectoryUpdate) { - qWarning("not saving lists during directory update"); + log::warn("not saving lists during directory update"); return false; } @@ -2698,7 +2710,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName, result.reserve(result.size() + saveMap.size()); result.insert(result.end(), saveMap.begin(), saveMap.end()); } else { - qWarning("local save games not supported by this game plugin"); + log::warn("local save games not supported by this game plugin"); } } diff --git a/src/organizercore.h b/src/organizercore.h index 99b1c5f2..2aa7e707 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -21,6 +21,7 @@ #include <delayedfilewriter.h>
#include <boost/signals2.hpp>
#include "executableinfo.h"
+#include <log.h>
class ModListSortProxy;
class PluginListSortProxy;
@@ -96,7 +97,7 @@ public: static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); }
- OrganizerCore(const QSettings &initSettings);
+ OrganizerCore(Settings &settings);
~OrganizerCore();
@@ -191,7 +192,11 @@ public: void prepareVFS();
- void updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist);
+ void updateVFSParams(
+ MOBase::log::Levels logLevel, int crashDumpsType,
+ QString executableBlacklist);
+
+ void setLogLevel(MOBase::log::Levels level);
bool cycleDiagnostics();
@@ -333,7 +338,7 @@ private: Profile *m_CurrentProfile;
- Settings m_Settings;
+ Settings& m_Settings;
SelfUpdater m_Updater;
diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index ff4099cf..715e11e3 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -120,18 +120,18 @@ bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index) QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index);
if (m_FileSystemModel->isDir(childIndex)) {
if (!recursiveDelete(childIndex)) {
- qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData());
+ log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex));
return false;
}
} else {
if (!m_FileSystemModel->remove(childIndex)) {
- qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData());
+ log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex));
return false;
}
}
}
if (!m_FileSystemModel->remove(index)) {
- qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData());
+ log::error("failed to delete {}", m_FileSystemModel->fileName(index));
return false;
}
return true;
diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 1ed463c6..8657f356 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -1,8 +1,10 @@ #include "persistentcookiejar.h"
+#include <log.h>
#include <QTemporaryFile>
#include <QDataStream>
#include <QNetworkCookie>
+using namespace MOBase;
PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *parent)
: QNetworkCookieJar(parent), m_FileName(fileName)
@@ -11,7 +13,7 @@ PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *paren }
PersistentCookieJar::~PersistentCookieJar() {
- qDebug("save %s", qUtf8Printable(m_FileName));
+ log::debug("save {}", m_FileName);
save();
}
@@ -24,7 +26,7 @@ void PersistentCookieJar::clear() { void PersistentCookieJar::save() {
QTemporaryFile file;
if (!file.open()) {
- qCritical("failed to save cookies: couldn't create temporary file");
+ log::error("failed to save cookies: couldn't create temporary file");
return;
}
QDataStream data(&file);
@@ -40,14 +42,14 @@ void PersistentCookieJar::save() { QFile oldCookies(m_FileName);
if (oldCookies.exists()) {
if (!oldCookies.remove()) {
- qCritical("failed to save cookies: failed to remove %s", qUtf8Printable(m_FileName));
+ log::error("failed to save cookies: failed to remove {}", m_FileName);
return;
}
} // if it doesn't exists that's fine
}
if (!file.copy(m_FileName)) {
- qCritical("failed to save cookies: failed to write %s", qUtf8Printable(m_FileName));
+ log::error("failed to save cookies: failed to write {}", m_FileName);
}
}
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 2126c5ef..62cdff1e 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -69,7 +69,7 @@ bool PluginContainer::verifyPlugin(IPlugin *plugin) if (plugin == nullptr) {
return false;
} else if (!plugin->init(new OrganizerProxy(m_Organizer, this, plugin->name()))) {
- qWarning("plugin failed to initialize");
+ log::warn("plugin failed to initialize");
return false;
}
return true;
@@ -91,7 +91,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) { // generic treatment for all plugins
IPlugin *pluginObj = qobject_cast<IPlugin*>(plugin);
if (pluginObj == nullptr) {
- qDebug("not an IPlugin");
+ log::debug("not an IPlugin");
return false;
}
plugin->setProperty("filename", fileName);
@@ -164,12 +164,13 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) for (QObject *proxiedPlugin : matchingPlugins) {
if (proxiedPlugin != nullptr) {
if (registerPlugin(proxiedPlugin, pluginName)) {
- qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName()));
+ log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName());
}
else {
- qWarning("plugin \"%s\" failed to load. If this plugin is for an older version of MO "
- "you have to update it or delete it if no update exists.",
- qUtf8Printable(pluginName));
+ log::warn(
+ "plugin \"{}\" failed to load. If this plugin is for an older version of MO "
+ "you have to update it or delete it if no update exists.",
+ pluginName);
}
}
}
@@ -190,7 +191,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) }
}
- qDebug("no matching plugin interface");
+ log::debug("no matching plugin interface");
return false;
}
@@ -224,7 +225,7 @@ void PluginContainer::unloadPlugins() QPluginLoader *loader = m_PluginLoaders.back();
m_PluginLoaders.pop_back();
if ((loader != nullptr) && !loader->unload()) {
- qDebug("failed to unload %s: %s", qUtf8Printable(loader->fileName()), qUtf8Printable(loader->errorString()));
+ log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString());
}
delete loader;
}
@@ -273,13 +274,13 @@ void PluginContainer::loadPlugins() loadCheck.open(QIODevice::WriteOnly);
QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath());
- qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData());
+ log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath));
QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot);
while (iter.hasNext()) {
iter.next();
if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) {
- qDebug("plugin \"%s\" blacklisted", qUtf8Printable(iter.fileName()));
+ log::debug("plugin \"{}\" blacklisted", iter.fileName());
continue;
}
loadCheck.write(iter.fileName().toUtf8());
@@ -290,15 +291,16 @@ void PluginContainer::loadPlugins() std::unique_ptr<QPluginLoader> pluginLoader(new QPluginLoader(pluginName, this));
if (pluginLoader->instance() == nullptr) {
m_FailedPlugins.push_back(pluginName);
- qCritical("failed to load plugin %s: %s",
- qUtf8Printable(pluginName), qUtf8Printable(pluginLoader->errorString()));
+ log::error(
+ "failed to load plugin {}: {}",
+ pluginName, pluginLoader->errorString());
} else {
if (registerPlugin(pluginLoader->instance(), pluginName)) {
- qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName()));
+ log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName());
m_PluginLoaders.push_back(pluginLoader.release());
} else {
m_FailedPlugins.push_back(pluginName);
- qWarning("plugin \"%s\" failed to load (may be outdated)", qUtf8Printable(pluginName));
+ log::warn("plugin \"{}\" failed to load (may be outdated)", pluginName);
}
}
}
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 85160a88..8637f546 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -309,7 +309,7 @@ int PluginList::findPluginByPriority(int priority) return i;
}
}
- qCritical(QString("No plugin with priority %1").arg(priority).toLocal8Bit());
+ log::error("No plugin with priority {}", priority);
return -1;
}
@@ -417,7 +417,7 @@ void PluginList::addInformation(const QString &name, const QString &message) if (iter != m_ESPsByName.end()) {
m_AdditionalInfo[name.toLower()].m_Messages.append(message);
} else {
- qWarning("failed to associate message for \"%s\"", qUtf8Printable(name));
+ log::warn("failed to associate message for \"{}\"", name);
}
}
@@ -481,7 +481,7 @@ void PluginList::writeLockedOrder(const QString &fileName) const file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8());
}
file.commit();
- qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName)));
+ log::debug("{} saved", QDir::toNativeSeparators(fileName));
}
@@ -506,7 +506,7 @@ void PluginList::saveTo(const QString &lockedOrderFileName }
}
if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) {
- qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(deleterFileName)));
+ log::debug("{} saved", QDir::toNativeSeparators(deleterFileName));
}
} else if (QFile::exists(deleterFileName)) {
shellDelete(QStringList() << deleterFileName);
@@ -521,7 +521,7 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) return true;
}
- qDebug("setting file times on esps");
+ log::debug("setting file times on esps");
for (ESPInfo &esp : m_ESPs) {
std::wstring espName = ToWString(esp.m_Name);
@@ -694,7 +694,7 @@ void PluginList::setState(const QString &name, PluginStates state) { m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) ||
m_ESPs[iter->second].m_ForceEnabled;
} else {
- qWarning("Plugin not found: %s", qUtf8Printable(name));
+ log::warn("Plugin not found: {}", name);
}
}
@@ -824,7 +824,7 @@ void PluginList::updateIndices() continue;
}
if (m_ESPs[i].m_Priority >= static_cast<int>(m_ESPs.size())) {
- qCritical("invalid plugin priority: %d", m_ESPs[i].m_Priority);
+ log::error("invalid plugin priority: {}", m_ESPs[i].m_Priority);
continue;
}
m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i;
@@ -1067,9 +1067,9 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int this->index(0, 0),
this->index(static_cast<int>(m_ESPs.size()), columnCount()));
} catch (const std::exception &e) {
- qCritical("failed to invoke state changed notification: %s", e.what());
+ log::error("failed to invoke state changed notification: {}", e.what());
} catch (...) {
- qCritical("failed to invoke state changed notification: unknown exception");
+ log::error("failed to invoke state changed notification: unknown exception");
}
}
@@ -1368,7 +1368,7 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, m_Masters.insert(QString(iter->c_str()));
}
} catch (const std::exception &e) {
- qCritical("failed to parse plugin file %s: %s", qUtf8Printable(fullPath), e.what());
+ log::error("failed to parse plugin file {}: {}", fullPath, e.what());
m_IsMaster = false;
m_IsLight = false;
m_IsLightFlagged = false;
diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 1e8e800f..da09935b 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -96,7 +96,7 @@ void ProblemsDialog::startFix() {
QObject *fixButton = QObject::sender();
if (fixButton == NULL) {
- qWarning("no button");
+ log::warn("no button");
return;
}
IPluginDiagnose *plugin = reinterpret_cast<IPluginDiagnose*>(fixButton ->property("fix").value<void*>());
diff --git a/src/profile.cpp b/src/profile.cpp index 4ac15333..7f4ebcaa 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -40,7 +40,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QMessageBox> #include <QScopedArrayPointer> #include <QStringList> // for QStringList -#include <QtDebug> // for qDebug, qWarning, etc #include <QtGlobal> // for qUtf8Printable #include <QBuffer> #include <QDirIterator> @@ -125,7 +124,7 @@ Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin) findProfileSettings(); if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { - qWarning("missing modlist.txt in %s", qUtf8Printable(directory.path())); + log::warn("missing modlist.txt in {}", directory.path()); touchFile(m_Directory.filePath("modlist.txt")); } @@ -232,7 +231,6 @@ void Profile::doWriteModlist() } for (std::map<int, unsigned int>::const_reverse_iterator iter = m_ModIndexByPriority.crbegin(); iter != m_ModIndexByPriority.crend(); iter++ ) { - //qDebug(QString("write mod %1 to priority %2").arg(iter->first).arg(iter->second).toLocal8Bit()); // the priority order was inverted on load so it has to be inverted again unsigned int index = iter->second; if (index != UINT_MAX) { @@ -253,7 +251,7 @@ void Profile::doWriteModlist() } if (file.commitIfDifferent(m_LastModlistHash)) { - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName))); + log::debug("{} saved", QDir::toNativeSeparators(fileName)); } } catch (const std::exception &e) { reportError(tr("failed to write mod list: %1").arg(e.what())); @@ -267,7 +265,10 @@ void Profile::createTweakedIniFile() QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { - reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + reportError( + tr("failed to update tweaked ini file, wrong settings may be used: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } @@ -287,9 +288,12 @@ void Profile::createTweakedIniFile() } if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorString().c_str())); + const auto e = ::GetLastError(); + reportError(tr("failed to create tweaked ini: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); } - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(tweakedIni))); + + log::debug("{} saved", QDir::toNativeSeparators(tweakedIni)); } // static @@ -304,7 +308,7 @@ void Profile::renameModInAllProfiles(const QString& oldName, const QString& newN if (modList.exists()) renameModInList(modList, oldName, newName); else - qWarning("Profile has no modlist.txt : %s", qUtf8Printable(profileIter.filePath())); + log::warn("Profile has no modlist.txt: {}", profileIter.filePath()); } } @@ -325,7 +329,7 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr if (line.length() == 0) { // ignore empty lines - qWarning("mod list contained invalid data: empty line"); + log::warn("mod list contained invalid data: empty line"); continue; } @@ -340,7 +344,7 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr if (modName.isEmpty()) { // file broken? - qWarning("mod list contained invalid data: missing mod name"); + log::warn("mod list contained invalid data: missing mod name"); continue; } @@ -361,8 +365,9 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr } if (renamed) - qDebug("Renamed %d \"%s\" mod to \"%s\" in %s", - renamed, qUtf8Printable(oldName), qUtf8Printable(newName), qUtf8Printable(modList.fileName())); + log::debug( + "Renamed {} \"{}\" mod to \"{}\" in {}", + renamed, oldName, newName, modList.fileName()); } void Profile::refreshModStatus() @@ -421,14 +426,16 @@ void Profile::refreshModStatus() m_ModStatus[modIndex].m_Priority = index++; } } else { - qWarning("no mod state for \"%s\" (profile \"%s\")", - qUtf8Printable(modName), qUtf8Printable(m_Directory.path())); + log::warn( + "no mod state for \"{}\" (profile \"{}\")", + modName, m_Directory.path()); // need to rewrite the modlist to fix this modStatusModified = true; } } else { - qDebug("mod not found: \"%s\" (profile \"%s\")", - qUtf8Printable(modName), qUtf8Printable(m_Directory.path())); + log::debug( + "mod not found: \"{}\" (profile \"{}\")", + modName, m_Directory.path()); // need to rewrite the modlist to fix this modStatusModified = true; } @@ -492,8 +499,10 @@ void Profile::dumpModStatus() const { for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr info = ModInfo::getByIndex(i); - qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, - m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); + log::warn( + "{}: {} - {} ({})", + i, info->name(), m_ModStatus[i].m_Priority, + m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); } } @@ -566,7 +575,7 @@ void Profile::setModsEnabled(const QList<unsigned int> &modsToEnable, const QLis QList<unsigned int> dirtyMods; for (auto idx : modsToEnable) { if (idx >= m_ModStatus.size()) { - qCritical() << tr("invalid mod index: %1").arg(idx); + log::error("invalid mod index: {}", idx); continue; } if (!m_ModStatus[idx].m_Enabled) { @@ -576,7 +585,7 @@ void Profile::setModsEnabled(const QList<unsigned int> &modsToEnable, const QLis } for (auto idx : modsToDisable) { if (idx >= m_ModStatus.size()) { - qCritical() << tr("invalid mod index: %1").arg(idx); + log::error("invalid mod index: {}", idx); continue; } if (ModInfo::getByIndex(idx)->alwaysEnabled()) { @@ -800,7 +809,7 @@ bool Profile::localSettingsEnabled() const QStringList missingFiles; for (QString file : m_GamePlugin->iniFiles()) { if (!QFile::exists(m_Directory.filePath(file))) { - qWarning("missing %s in %s", qUtf8Printable(file), qUtf8Printable(m_Directory.path())); + log::warn("missing {} in {}", file, m_Directory.path()); missingFiles << file; } } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index f9ea655f..d7863fc8 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -214,9 +214,9 @@ void ProfilesDialog::on_removeProfileButton_clicked() delete item;
}
if (!shellDelete(QStringList(profilePath))) {
- qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qUtf8Printable(profilePath), ::GetLastError());
+ log::warn("Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", profilePath, ::GetLastError());
if (!removeDir(profilePath)) {
- qWarning("regular delete failed too");
+ log::warn("regular delete failed too");
}
}
}
diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index 5fcb84d3..ff9539d7 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -18,11 +18,14 @@ #include "qtgroupingproxy.h"
+#include <log.h>
#include <QDebug>
#include <QIcon>
#include <QInputDialog>
+using namespace MOBase;
+
/*!
\class QtGroupingProxy
\brief The QtGroupingProxy class will group source model rows by adding a new top tree-level.
@@ -86,7 +89,6 @@ QtGroupingProxy::setGroupedColumn( int groupedColumn ) QList<RowData>
QtGroupingProxy::belongsTo( const QModelIndex &idx )
{
- //qDebug() << __FILE__ << __FUNCTION__;
QList<RowData> rowDataList;
//get all the data for this index from the model
@@ -106,7 +108,7 @@ QtGroupingProxy::belongsTo( const QModelIndex &idx ) i.next();
int role = i.key();
QVariant variant = i.value();
- // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant;
+
if ( variant.type() == QVariant::List )
{
//a list of variants get's expanded to multiple rows
@@ -162,7 +164,7 @@ QtGroupingProxy::buildTree() m_parentCreateList.clear();
int max = sourceModel()->rowCount( m_rootNode );
- //qDebug() << QString("building tree with %1 leafs.").arg( max );
+
//WARNING: these have to be added in order because the addToGroups function is optimized for
//modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best.
for( int row = 0; row < max; row++ )
@@ -232,9 +234,6 @@ QtGroupingProxy::addSourceRow( const QModelIndex &idx ) int updatedGroup = -1;
if( !data.isEmpty() )
{
- // qDebug() << QString("index %1 belongs to group %2").arg( row )
- // .arg( data[0][Qt::DisplayRole].toString() );
-
foreach( const RowData &cachedData, m_groupMaps )
{
//when this matches the index belongs to an existing group
@@ -316,21 +315,12 @@ QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const pc.row = parent.row();
m_parentCreateList << pc;
- //dumpParentCreateList();
- // qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() );
- // for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
- // {
- // qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex <<
- // " | " << m_parentCreateList[i].row;
- // }
-
return m_parentCreateList.size() - 1;
}
QModelIndex
QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const
{
- // qDebug() << "index requested for: (" << row << "," << column << "), " << parent;
if( !hasIndex(row, column, parent) ) {
return QModelIndex();
}
@@ -350,17 +340,15 @@ QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const QModelIndex
QtGroupingProxy::parent( const QModelIndex &index ) const
{
- //qDebug() << "parent: " << index;
if( !index.isValid() )
return QModelIndex();
int parentCreateIndex = index.internalId();
- //qDebug() << "parentCreateIndex: " << parentCreateIndex;
if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() )
return QModelIndex();
struct ParentCreate pc = m_parentCreateList[parentCreateIndex];
- //qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")";
+
//only items at column 0 have children
return createIndex( pc.row, 0, pc.parentCreateIndex );
}
@@ -368,12 +356,10 @@ QtGroupingProxy::parent( const QModelIndex &index ) const int
QtGroupingProxy::rowCount( const QModelIndex &index ) const
{
- //qDebug() << "rowCount: " << index;
if( !index.isValid() )
{
//the number of top level groups + the number of non-grouped items
int rows = m_groupMaps.count() + m_groupHash.value( std::numeric_limits<quint32>::max() ).count();
- //qDebug() << rows << " in root group";
return rows;
}
@@ -382,12 +368,10 @@ QtGroupingProxy::rowCount( const QModelIndex &index ) const {
qint64 groupIndex = index.row();
int rows = m_groupHash.value( groupIndex ).count();
- //qDebug() << rows << " in group " << m_groupMaps[groupIndex];
return rows;
} else {
QModelIndex originalIndex = mapToSource( index );
int rowCount = sourceModel()->rowCount( originalIndex );
- //qDebug() << "original item: rowCount == " << rowCount;
return rowCount;
}
}
@@ -447,7 +431,7 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const {
if( !index.isValid() )
return QVariant();
- // qDebug() << __FUNCTION__ << index << " role: " << role;
+
int row = index.row();
int column = index.column();
if( isGroup( index ) )
@@ -495,11 +479,9 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const }
}
- //qDebug() << __FUNCTION__ << "is a group";
//use cached or precalculated data
if( m_groupMaps[row][column].contains( Qt::DisplayRole ) )
{
- // qDebug() << "Using cached data for " << row << "x" << column << ": " << m_groupMaps[row][column].value(Qt::DisplayRole).toString();
if ((m_flags & FLAG_NOGROUPNAME) != 0) {
QModelIndex parentIndex = this->index( row, 0, index.parent() );
QModelIndex childIndex = this->index( 0, column, parentIndex );
@@ -526,18 +508,17 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const function = mapToSource(childIndex).data(m_aggregateRole).toInt();
}
- //qDebug() << __FUNCTION__ << "childCount: " << childCount;
//Need a parentIndex with column == 0 because only those have children.
QModelIndex parentIndex = this->index( row, 0, index.parent() );
for( int childRow = 0; childRow < childCount; childRow++ )
{
QModelIndex childIndex = this->index( childRow, column, parentIndex );
QVariant data = mapToSource( childIndex ).data( role );
- //qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type());
+
if( data.isValid() && !variantsOfChildren.contains( data ) )
variantsOfChildren << data;
}
- //qDebug() << "gathered this data from children: " << variantsOfChildren;
+
//saving in cache
ItemData roleMap = m_groupMaps[row].value( column );
foreach( const QVariant &variant, variantsOfChildren )
@@ -547,8 +528,6 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const }
}
- //qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role];
-
if( variantsOfChildren.count() == 0 )
return QVariant();
@@ -621,34 +600,30 @@ QtGroupingProxy::isGroup( const QModelIndex &index ) const QModelIndex
QtGroupingProxy::mapToSource( const QModelIndex &index ) const
{
- //qDebug() << "mapToSource: " << index;
if( !index.isValid() ) {
return m_rootNode;
}
if( isGroup( index ) )
{
- //qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString();
return m_rootNode;
}
QModelIndex proxyParent = index.parent();
- //qDebug() << "parent: " << proxyParent;
QModelIndex originalParent = mapToSource( proxyParent );
- //qDebug() << "originalParent: " << originalParent;
+
int originalRow = index.row();
if( originalParent == m_rootNode )
{
int indexInGroup = index.row();
if( !proxyParent.isValid() )
indexInGroup -= m_groupMaps.count();
- //qDebug() << "indexInGroup" << indexInGroup;
+
QList<int> childRows = m_groupHash.value( proxyParent.row() );
if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 )
return QModelIndex();
originalRow = childRows.at( indexInGroup );
- //qDebug() << "originalRow: " << originalRow;
}
return sourceModel()->index( originalRow, index.column(), originalParent );
}
@@ -674,7 +649,7 @@ QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const QModelIndex proxyParent;
QModelIndex sourceParent = idx.parent();
- //qDebug() << "sourceParent: " << sourceParent;
+
int proxyRow = idx.row();
int sourceRow = idx.row();
@@ -708,15 +683,12 @@ QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const proxyParent = QModelIndex();
// if the proxy item is not in a group it will be below the groups.
int groupLength = m_groupMaps.count();
- //qDebug() << "groupNames length: " << groupLength;
int i = m_groupHash.value( std::numeric_limits<quint32>::max() ).indexOf( sourceRow );
- //qDebug() << "index in hash: " << i;
+
proxyRow = groupLength + i;
}
}
- //qDebug() << "proxyParent: " << proxyParent;
- //qDebug() << "proxyRow: " << proxyRow;
return this->index( proxyRow, idx.column(), proxyParent );
}
@@ -731,9 +703,9 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const return 0;
}
+
//only if the grouped column has the editable flag set allow the
//actions leading to setData on the source (edit & drop)
- // qDebug() << idx;
if( isGroup( idx ) )
{
// dumpGroups();
@@ -749,7 +721,7 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const m_rootNode.parent() );
if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 )
{
- qDebug("row %d is not checkable", originalRow);
+ log::debug("row {} is not checkable", originalRow);
checkable = false;
}
}
@@ -892,9 +864,7 @@ QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int star if( parent != m_rootNode )
{
//an item will be added to an original index, remap and pass it on
- // qDebug() << parent;
QModelIndex proxyParent = mapFromSource( parent );
- // qDebug() << proxyParent;
beginInsertRows( proxyParent, start, end );
}
}
@@ -914,7 +884,12 @@ QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int en {
//an item was added to an original index, remap and pass it on
QModelIndex proxyParent = mapFromSource( parent );
- qDebug() << proxyParent;
+
+ QString s;
+ QDebug debug(&s);
+ debug << proxyParent;
+ log::debug("{}", s);
+
//beginInsertRows had to be called in modelRowsAboutToBeInserted()
endInsertRows();
}
@@ -951,9 +926,7 @@ QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start else
{
//child item(s) of an original item will be removed, remap and pass it on
- // qDebug() << parent;
QModelIndex proxyParent = mapFromSource( parent );
- // qDebug() << proxyParent;
beginRemoveRows( proxyParent, start, end );
}
}
@@ -1044,16 +1017,24 @@ QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const void
QtGroupingProxy::dumpGroups() const
{
- qDebug() << "m_groupHash: ";
+ QString s;
+ QDebug debug(&s);
+
+ debug << "m_groupHash:\n";
for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ )
{
- qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex );
+ debug << groupIndex << " : " << m_groupHash.value( groupIndex ) << "\n";
}
- qDebug() << "m_groupMaps: ";
+ debug << "m_groupMaps:\n";
for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ )
- qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex );
- qDebug() << m_groupHash.value( std::numeric_limits<quint32>::max() );
+ {
+ debug << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex ) << "\n";
+ }
+
+ debug << m_groupHash.value( std::numeric_limits<quint32>::max() );
+
+ log::debug("{}", s);
}
diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index e967b27c..0ca39b19 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -150,23 +150,23 @@ void SelfUpdater::testForUpdate() VersionInfo newestVer(newest["tag_name"].toString());
if (newestVer > this->m_MOVersion) {
m_UpdateCandidate = newest;
- qDebug("update available: %s -> %s",
- qUtf8Printable(this->m_MOVersion.displayString(3)),
- qUtf8Printable(newestVer.displayString(3)));
+ log::debug("update available: {} -> {}",
+ this->m_MOVersion.displayString(3),
+ newestVer.displayString(3));
emit updateAvailable();
} else if (newestVer < this->m_MOVersion) {
// this could happen if the user switches from using prereleases to
// stable builds. Should we downgrade?
- qDebug("This version is newer than the latest released one: %s -> %s",
- qUtf8Printable(this->m_MOVersion.displayString(3)),
- qUtf8Printable(newestVer.displayString(3)));
+ log::debug("This version is newer than the latest released one: {} -> {}",
+ this->m_MOVersion.displayString(3),
+ newestVer.displayString(3));
}
}
});
}
//Catch all is bad by design, should be improved
catch (...) {
- qDebug("Unable to connect to github.com to check version");
+ log::debug("Unable to connect to github.com to check version");
}
}
@@ -230,7 +230,7 @@ void SelfUpdater::closeProgress() void SelfUpdater::openOutputFile(const QString &fileName)
{
QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName;
- qDebug("downloading to %s", qUtf8Printable(outputPath));
+ log::debug("downloading to {}", outputPath);
m_UpdateFile.setFileName(outputPath);
m_UpdateFile.open(QIODevice::WriteOnly);
}
@@ -312,7 +312,7 @@ void SelfUpdater::downloadFinished() return;
}
- qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData());
+ log::debug("download: {}", m_UpdateFile.fileName());
try {
installUpdate();
diff --git a/src/settings.cpp b/src/settings.cpp index 5cb2524f..5ad066b2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -54,7 +54,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QPalette> #include <Qt> // for Qt::UserRole, etc -#include <QtDebug> // for qDebug, qWarning #include <Windows.h> // For ShellExecuteW, HINSTANCE, etc #include <wincred.h> // For storage @@ -165,8 +164,9 @@ void Settings::registerPlugin(IPlugin *plugin) for (const PluginSetting &setting : plugin->settings()) { QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); if (!temp.convert(setting.defaultValue.type())) { - qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", - qUtf8Printable(temp.toString()), qUtf8Printable(setting.key), qUtf8Printable(plugin->name())); + log::warn( + "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", + temp.toString(), setting.key, plugin->name()); temp = setting.defaultValue; } m_PluginSettings[plugin->name()][setting.key] = temp; @@ -220,9 +220,7 @@ QString Settings::deObfuscate(const QString key) } else { const auto e = GetLastError(); if (e != ERROR_NOT_FOUND) { - qCritical().nospace() - << "Retrieving encrypted data failed: " - << formatSystemMessageQ(e); + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); } } delete[] keyData; @@ -367,11 +365,7 @@ bool Settings::setNexusApiKey(const QString& apiKey) { if (!obfuscate("APIKEY", apiKey)) { const auto e = GetLastError(); - - qCritical().nospace() - << "Storing API key failed: " - << formatSystemMessageQ(e); - + log::error("Storing API key failed: {}", formatSystemMessage(e)); return false; } @@ -415,9 +409,14 @@ bool Settings::offlineMode() const return m_Settings.value("Settings/offline_mode", false).toBool(); } -int Settings::logLevel() const +log::Levels Settings::logLevel() const +{ + return static_cast<log::Levels>(m_Settings.value("Settings/log_level").toInt()); +} + +void Settings::setLogLevel(log::Levels level) { - return m_Settings.value("Settings/log_level", static_cast<int>(LogLevel::Info)).toInt(); + m_Settings.setValue("Settings/log_level", static_cast<int>(level)); } int Settings::crashDumpsType() const @@ -487,9 +486,7 @@ void Settings::setSteamLogin(QString username, QString password) } if (!obfuscate("steam_password", password)) { const auto e = GetLastError(); - qCritical().nospace() - << "Storing or deleting password failed: " - << formatSystemMessageQ(e); + log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); } } @@ -637,7 +634,7 @@ void Settings::updateServers(const QList<ServerInfo> &servers) QVariantMap val = m_Settings.value(key).toMap(); QDate lastSeen = val["lastSeen"].toDate(); if (lastSeen.daysTo(now) > 30) { - qDebug("removing server %s since it hasn't been available for downloads in over a month", qUtf8Printable(key)); + log::debug("removing server {} since it hasn't been available for downloads in over a month", key); m_Settings.remove(key); } } @@ -760,10 +757,10 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) { if (first_update) { - qDebug("Changed settings:"); + log::debug("Changed settings:"); first_update = false; } - qDebug(" %s=%s", k.toUtf8().data(), m_Settings.value(k).toString().toUtf8().data()); + log::debug(" {}={}", k, m_Settings.value(k).toString()); } m_Settings.endGroup(); } @@ -1000,7 +997,7 @@ Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_d , m_dumpsMaxEdit(m_dialog.findChild<QSpinBox *>("dumpsMaxEdit")) , m_diagnosticsExplainedLabel(m_dialog.findChild<QLabel *>("diagnosticsExplainedLabel")) { - m_logLevelBox->setCurrentIndex(m_parent->logLevel()); + setLevelsBox(); m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() @@ -1016,11 +1013,28 @@ Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_d void Settings::DiagnosticsTab::update() { - m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex()); + m_Settings.setValue("Settings/log_level", m_logLevelBox->currentData().toInt()); m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex()); m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); } +void Settings::DiagnosticsTab::setLevelsBox() +{ + m_logLevelBox->clear(); + + m_logLevelBox->addItem(tr("Debug"), log::Debug); + m_logLevelBox->addItem(tr("Info (recommended)"), log::Info); + m_logLevelBox->addItem(tr("Warning"), log::Warning); + m_logLevelBox->addItem(tr("Error"), log::Error); + + for (int i=0; i<m_logLevelBox->count(); ++i) { + if (m_logLevelBox->itemData(i) == m_parent->logLevel()) { + m_logLevelBox->setCurrentIndex(i); + break; + } + } +} + Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) : Settings::SettingsTab(parent, dialog) , m_offlineBox(dialog.findChild<QCheckBox *>("offlineBox")) diff --git a/src/settings.h b/src/settings.h index bccd1e81..c66eb94c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #define SETTINGS_H #include "loadmechanism.h" +#include <log.h> #include <QList> #include <QMap> @@ -232,7 +233,12 @@ public: /** * @return the configured log level */ - int logLevel() const; + MOBase::log::Levels logLevel() const; + + /** + * sets the log level setting + */ + void setLogLevel(MOBase::log::Levels level); /** * @return the configured crash dumps type @@ -481,6 +487,8 @@ private: QComboBox *m_dumpsTypeBox; QSpinBox *m_dumpsMaxEdit; QLabel *m_diagnosticsExplainedLabel; + + void setLevelsBox(); }; /** Display/store the configuration in the 'nexus' tab of the settings dialogue */ diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index fccc8be0..1e94bcde 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1426,26 +1426,6 @@ programs you are intentionally running.</string> "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. </string> </property> - <item> - <property name="text"> - <string>Debug</string> - </property> - </item> - <item> - <property name="text"> - <string>Info (recommended)</string> - </property> - </item> - <item> - <property name="text"> - <string>Warning</string> - </property> - </item> - <item> - <property name="text"> - <string>Error</string> - </property> - </item> </widget> </item> </layout> diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index bde515a9..2cdbac74 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "windows_error.h"
#include "leaktrace.h"
#include "error_report.h"
+#include <log.h>
#include <bsatk.h>
#include <boost/bind.hpp>
#include <boost/scoped_array.hpp>
@@ -35,6 +36,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. namespace MOShared {
+namespace log = MOBase::log;
+
static const int MAXPATH_UNICODE = 32767;
class OriginConnection {
@@ -103,7 +106,7 @@ public: m_OriginsNameMap.erase(iter);
m_OriginsNameMap[newName] = idx;
} else {
- log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str());
+ log::error("failed to change name lookup from {} to {}", oldName, newName);
}
}
@@ -714,14 +717,14 @@ void DirectoryEntry::removeFile(FileEntry::Index index) if (iter != m_Files.end()) {
m_Files.erase(iter);
} else {
- log("file \"%ls\" not in directory \"%ls\"",
- m_FileRegister->getFile(index)->getName().c_str(),
- this->getName().c_str());
+ log::error(
+ "file \"{}\" not in directory \"{}\"",
+ m_FileRegister->getFile(index)->getName(), this->getName());
}
} else {
- log("file \"%ls\" not in directory \"%ls\", directory empty",
- m_FileRegister->getFile(index)->getName().c_str(),
- this->getName().c_str());
+ log::error(
+ "file \"{}\" not in directory \"{}\", directory empty",
+ m_FileRegister->getFile(index)->getName(), this->getName());
}
}
@@ -844,7 +847,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry *temp = findSubDirectory(pathComponent);
if (temp != nullptr) {
if (len >= path.size()) {
- log("unexpected end of path");
+ log::error("unexpected end of path");
return FileEntry::Ptr();
}
return temp->searchFile(path.substr(len + 1), directory);
@@ -988,7 +991,7 @@ bool FileRegister::removeFile(FileEntry::Index index) m_Files.erase(index);
return true;
} else {
- log("invalid file index for remove: %lu", index);
+ log::error("invalid file index for remove: {}", index);
return false;
}
}
@@ -1002,7 +1005,7 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) m_Files.erase(iter);
}
} else {
- log("invalid file index for remove (for origin): %lu", index);
+ log::error("invalid file index for remove (for origin): {}", index);
}
}
diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp index 6d091630..4185b544 100644 --- a/src/shared/error_report.cpp +++ b/src/shared/error_report.cpp @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. namespace MOShared {
-
void reportError(LPCSTR format, ...)
{
char buffer[1025];
@@ -52,48 +51,4 @@ void reportError(LPCWSTR format, ...) MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR);
}
-
-std::string getCurrentErrorStringA()
-{
- LPSTR buffer = nullptr;
-
- DWORD errorCode = ::GetLastError();
-
- if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) {
- ::SetLastError(errorCode);
- return std::string();
- } else {
- LPSTR lastChar = buffer + strlen(buffer) - 2;
- *lastChar = '\0';
-
- std::string result(buffer);
-
- LocalFree(buffer);
- ::SetLastError(errorCode);
- return result;
- }
-}
-
-std::wstring getCurrentErrorStringW()
-{
- LPWSTR buffer = nullptr;
-
- DWORD errorCode = ::GetLastError();
-
- if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, nullptr) == 0) {
- ::SetLastError(errorCode);
- return std::wstring();
- } else {
- LPWSTR lastChar = buffer + wcslen(buffer) - 2;
- *lastChar = '\0';
-
- std::wstring result(buffer);
-
- LocalFree(buffer);
- ::SetLastError(errorCode);
- return result;
- }
-}
} // namespace MOShared
diff --git a/src/shared/error_report.h b/src/shared/error_report.h index c09ad75b..17b25645 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -24,28 +24,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <Windows.h>
#include <string>
-namespace std {
-#ifdef UNICODE
-typedef wstring tstring;
-#else
-typedef string tstring;
-#endif
-}
-
-extern void log(const char* format, ...);
-
namespace MOShared {
void reportError(LPCSTR format, ...);
void reportError(LPCWSTR format, ...);
-std::string getCurrentErrorStringA();
-std::wstring getCurrentErrorStringW();
-
-#ifdef UNICODE
-#define getCurrentErrorString getCurrentErrorStringW
-#else
-#define getCurrentErrorString getCurrentErrorStringA
-#endif
-
} // namespace MOShared
diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 17df3b92..07983e12 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,34 +19,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "util.h"
#include "windows_error.h"
-#include "error_report.h"
-#include "executableslist.h"
-#include "instancemanager.h"
-#include <utility.h>
-
-#include <sstream>
-#include <locale>
-#include <algorithm>
-#include <set>
-#include <filesystem>
-
-#include <DbgHelp.h>
-#include <boost/scoped_array.hpp>
-#include <QApplication>
-
-#include <comdef.h>
-#include <Wbemidl.h>
-#include <wscapi.h>
-#include <netfw.h>
-
-#pragma comment(lib, "Wbemuuid.lib")
-
-using MOBase::formatSystemMessage;
-using MOBase::formatSystemMessageQ;
-namespace fs = std::filesystem;
-
-namespace MOShared {
+namespace MOShared
+{
bool FileExists(const std::string &filename)
{
@@ -268,1808 +243,4 @@ MOBase::VersionInfo createVersionInfo() }
}
-
-namespace env
-{
-
-struct HandleCloser
-{
- using pointer = HANDLE;
-
- void operator()(HANDLE h)
- {
- if (h != INVALID_HANDLE_VALUE) {
- ::CloseHandle(h);
- }
- }
-};
-
-using HandlePtr = std::unique_ptr<HANDLE, HandleCloser>;
-
-
-struct LibraryFreer
-{
- using pointer = HINSTANCE;
-
- void operator()(HINSTANCE h)
- {
- if (h != 0) {
- ::FreeLibrary(h);
- }
- }
-};
-
-struct COMReleaser
-{
- void operator()(IUnknown* p)
- {
- if (p) {
- p->Release();
- }
- }
-};
-
-
-template <class T>
-using COMPtr = std::unique_ptr<T, COMReleaser>;
-
-
-class ShellLinkException
-{
-public:
- ShellLinkException(QString s)
- : m_what(std::move(s))
- {
- }
-
- const QString& what() const
- {
- return m_what;
- }
-
-private:
- QString m_what;
-};
-
-// just a wrapper around IShellLink operations that throws ShellLinkException
-// on errors
-//
-class ShellLinkWrapper
-{
-public:
- ShellLinkWrapper()
- {
- m_link = createShellLink();
- m_file = createPersistFile();
- }
-
- void setPath(const QString& s)
- {
- if (s.isEmpty()) {
- throw ShellLinkException("path cannot be empty");
- }
-
- const auto r = m_link->SetPath(s.toStdWString().c_str());
- throwOnFail(r, QString("failed to set target path '%1'").arg(s));
- }
-
- void setArguments(const QString& s)
- {
- const auto r = m_link->SetArguments(s.toStdWString().c_str());
- throwOnFail(r, QString("failed to set arguments '%1'").arg(s));
- }
-
- void setDescription(const QString& s)
- {
- if (s.isEmpty()) {
- return;
- }
-
- const auto r = m_link->SetDescription(s.toStdWString().c_str());
- throwOnFail(r, QString("failed to set description '%1'").arg(s));
- }
-
- void setIcon(const QString& file, int i)
- {
- if (file.isEmpty()) {
- return;
- }
-
- const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i);
- throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i));
- }
-
- void setWorkingDirectory(const QString& s)
- {
- if (s.isEmpty()) {
- return;
- }
-
- const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str());
- throwOnFail(r, QString("failed to set working directory '%1'").arg(s));
- }
-
- void save(const QString& path)
- {
- const auto r = m_file->Save(path.toStdWString().c_str(), TRUE);
- throwOnFail(r, QString("failed to save link '%1'").arg(path));
- }
-
-private:
- COMPtr<IShellLink> m_link;
- COMPtr<IPersistFile> m_file;
-
- void throwOnFail(HRESULT r, const QString& s)
- {
- if (FAILED(r)) {
- throw ShellLinkException(QString("%1, %2")
- .arg(s)
- .arg(formatSystemMessageQ(r)));
- }
- }
-
- COMPtr<IShellLink> createShellLink()
- {
- void* link = nullptr;
-
- const auto r = CoCreateInstance(
- CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
- IID_IShellLink, &link);
-
- throwOnFail(r, "failed to create IShellLink instance");
-
- if (!link) {
- throw ShellLinkException("creating IShellLink worked, pointer is null");
- }
-
- return COMPtr<IShellLink>(static_cast<IShellLink*>(link));
- }
-
- COMPtr<IPersistFile> createPersistFile()
- {
- void* file = nullptr;
-
- const auto r = m_link->QueryInterface(IID_IPersistFile, &file);
- throwOnFail(r, "failed to get IPersistFile interface");
-
- if (!file) {
- throw ShellLinkException("querying IPersistFile worked, pointer is null");
- }
-
- return COMPtr<IPersistFile>(static_cast<IPersistFile*>(file));
- }
-};
-
-
-Shortcut::Shortcut()
- : m_iconIndex(0)
-{
-}
-
-Shortcut::Shortcut(const Executable& exe)
- : Shortcut()
-{
- m_name = exe.title();
- m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath();
-
- m_arguments = QString("\"moshortcut://%1:%2\"")
- .arg(InstanceManager::instance().currentInstance())
- .arg(exe.title());
-
- m_description = QString("Run %1 with ModOrganizer").arg(exe.title());
-
- if (exe.usesOwnIcon()) {
- m_icon = exe.binaryInfo().absoluteFilePath();
- }
-
- m_workingDirectory = qApp->applicationDirPath();
-}
-
-Shortcut& Shortcut::name(const QString& s)
-{
- m_name = s;
- return *this;
-}
-
-Shortcut& Shortcut::target(const QString& s)
-{
- m_target = s;
- return *this;
-}
-
-Shortcut& Shortcut::arguments(const QString& s)
-{
- m_arguments = s;
- return *this;
-}
-
-Shortcut& Shortcut::description(const QString& s)
-{
- m_description = s;
- return *this;
-}
-
-Shortcut& Shortcut::icon(const QString& s, int index)
-{
- m_icon = s;
- m_iconIndex = index;
- return *this;
-}
-
-Shortcut& Shortcut::workingDirectory(const QString& s)
-{
- m_workingDirectory = s;
- return *this;
-}
-
-bool Shortcut::exists(Locations loc) const
-{
- const auto path = shortcutPath(loc);
- if (path.isEmpty()) {
- return false;
- }
-
- return QFileInfo(path).exists();
-}
-
-bool Shortcut::toggle(Locations loc)
-{
- if (exists(loc)) {
- return remove(loc);
- } else {
- return add(loc);
- }
-}
-
-bool Shortcut::add(Locations loc)
-{
- debug()
- << "adding shortcut to " << toString(loc) << ":\n"
- << " . name: '" << m_name << "'\n"
- << " . target: '" << m_target << "'\n"
- << " . arguments: '" << m_arguments << "'\n"
- << " . description: '" << m_description << "'\n"
- << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n"
- << " . working directory: '" << m_workingDirectory << "'";
-
- if (m_target.isEmpty()) {
- critical() << "target is empty";
- return false;
- }
-
- const auto path = shortcutPath(loc);
- if (path.isEmpty()) {
- return false;
- }
-
- debug() << "shorcut file will be saved at '" << path << "'";
-
- try
- {
- ShellLinkWrapper link;
-
- link.setPath(m_target);
- link.setArguments(m_arguments);
- link.setDescription(m_description);
- link.setIcon(m_icon, m_iconIndex);
- link.setWorkingDirectory(m_workingDirectory);
-
- link.save(path);
-
- return true;
- }
- catch(ShellLinkException& e)
- {
- critical() << e.what() << "\nshortcut file was not saved";
- }
-
- return false;
-}
-
-bool Shortcut::remove(Locations loc)
-{
- debug() << "removing shortcut for '" << m_name << "' from " << toString(loc);
-
- const auto path = shortcutPath(loc);
- if (path.isEmpty()) {
- return false;
- }
-
- debug() << "path to shortcut file is '" << path << "'";
-
- if (!QFile::exists(path)) {
- critical() << "can't remove '" << path << "', file not found";
- return false;
- }
-
- if (!MOBase::shellDelete({path})) {
- const auto e = ::GetLastError();
-
- critical()
- << "failed to remove '" << path << "', "
- << formatSystemMessageQ(e);
-
- return false;
- }
-
- return true;
-}
-
-QString Shortcut::shortcutPath(Locations loc) const
-{
- const auto dir = shortcutDirectory(loc);
- if (dir.isEmpty()) {
- return {};
- }
-
- const auto file = shortcutFilename();
- if (file.isEmpty()) {
- return {};
- }
-
- return dir + QDir::separator() + file;
-}
-
-QString Shortcut::shortcutDirectory(Locations loc) const
-{
- QString dir;
-
- try
- {
- switch (loc)
- {
- case Desktop:
- dir = MOBase::getDesktopDirectory();
- break;
-
- case StartMenu:
- dir = MOBase::getStartMenuDirectory();
- break;
-
- case None:
- default:
- critical() << "bad location " << loc;
- break;
- }
- }
- catch(std::exception&)
- {
- }
-
- return QDir::toNativeSeparators(dir);
-}
-
-QString Shortcut::shortcutFilename() const
-{
- if (m_name.isEmpty()) {
- critical() << "name is empty";
- return {};
- }
-
- return m_name + ".lnk";
-}
-
-QDebug Shortcut::debug() const
-{
- return qDebug().noquote().nospace() << "system shortcut: ";
-}
-
-QDebug Shortcut::critical() const
-{
- return qCritical().noquote().nospace() << "system shortcut: ";
-}
-
-
-QString toString(Shortcut::Locations loc)
-{
- switch (loc)
- {
- case Shortcut::None:
- return "none";
-
- case Shortcut::Desktop:
- return "desktop";
-
- case Shortcut::StartMenu:
- return "start menu";
-
- default:
- return QString("? (%1)").arg(static_cast<int>(loc));
- }
-}
-
-
-
-class WMI
-{
-public:
- class failed {};
-
- WMI(const std::string& ns)
- {
- try
- {
- createLocator();
- createService(ns);
- setSecurity();
- }
- catch(failed&)
- {
- }
- }
-
- template <class F>
- void query(const std::string& q, F&& f)
- {
- if (!m_locator || !m_service) {
- return;
- }
-
- auto enumerator = getEnumerator(q);
- if (!enumerator) {
- return;
- }
-
- for (;;)
- {
- COMPtr<IWbemClassObject> object;
-
- {
- IWbemClassObject* rawObject = nullptr;
- ULONG count = 0;
- auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count);
-
- if (count == 0 || !rawObject) {
- break;
- }
-
- if (FAILED(ret)) {
- qCritical()
- << "enumerator->next() failed, " << formatSystemMessageQ(ret);
- break;
- }
-
- object.reset(rawObject);
- }
-
- f(object.get());
- }
- }
-
-private:
- COMPtr<IWbemLocator> m_locator;
- COMPtr<IWbemServices> m_service;
-
- void createLocator()
- {
- void* rawLocator = nullptr;
-
- const auto ret = CoCreateInstance(
- CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER,
- IID_IWbemLocator, &rawLocator);
-
- if (FAILED(ret) || !rawLocator) {
- qCritical()
- << "CoCreateInstance for WbemLocator failed, "
- << formatSystemMessageQ(ret);
-
- throw failed();
- }
-
- m_locator.reset(static_cast<IWbemLocator*>(rawLocator));
- }
-
- void createService(const std::string& ns)
- {
- IWbemServices* rawService = nullptr;
-
- const auto res = m_locator->ConnectServer(
- _bstr_t(ns.c_str()),
- nullptr, nullptr, nullptr, 0, nullptr, nullptr,
- &rawService);
-
- if (FAILED(res) || !rawService) {
- qCritical()
- << "locator->ConnectServer() failed for namespace "
- << "'" << QString::fromStdString(ns) << "', "
- << formatSystemMessageQ(res);
-
- throw failed();
- }
-
- m_service.reset(rawService);
- }
-
- void setSecurity()
- {
- auto ret = CoSetProxyBlanket(
- m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
- RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE);
-
- if (FAILED(ret))
- {
- qCritical()
- << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret);
-
- throw failed();
- }
- }
-
- COMPtr<IEnumWbemClassObject> getEnumerator(
- const std::string& query)
- {
- IEnumWbemClassObject* rawEnumerator = NULL;
-
- auto ret = m_service->ExecQuery(
- bstr_t("WQL"),
- bstr_t(query.c_str()),
- WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
- NULL,
- &rawEnumerator);
-
- if (FAILED(ret) || !rawEnumerator)
- {
- qCritical()
- << "query '" << QString::fromStdString(query) << "' failed, "
- << formatSystemMessageQ(ret);
-
- return {};
- }
-
- return COMPtr<IEnumWbemClassObject>(rawEnumerator);
- }
-};
-
-
-Environment::Environment()
-{
- m_modules = getLoadedModules();
- m_security = getSecurityProducts();
-}
-
-const std::vector<Module>& Environment::loadedModules()
-{
- return m_modules;
-}
-
-const WindowsInfo& Environment::windowsInfo() const
-{
- return m_windows;
-}
-
-const std::vector<SecurityProduct>& Environment::securityProducts() const
-{
- return m_security;
-}
-
-std::vector<Module> Environment::getLoadedModules() const
-{
- HandlePtr snapshot(CreateToolhelp32Snapshot(
- TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId()));
-
- if (snapshot.get() == INVALID_HANDLE_VALUE)
- {
- const auto e = GetLastError();
-
- qCritical().nospace().noquote()
- << "CreateToolhelp32Snapshot() failed, "
- << formatSystemMessageQ(e);
-
- return {};
- }
-
- MODULEENTRY32 me = {};
- me.dwSize = sizeof(me);
-
- // first module, this shouldn't fail because there's at least the executable
- if (!Module32First(snapshot.get(), &me))
- {
- const auto e = GetLastError();
-
- qCritical().nospace().noquote()
- << "Module32First() failed, " << formatSystemMessageQ(e);
-
- return {};
- }
-
- std::vector<Module> v;
-
- for (;;)
- {
- const auto path = QString::fromWCharArray(me.szExePath);
- if (!path.isEmpty()) {
- v.push_back(Module(path, me.modBaseSize));
- }
-
- // next module
- if (!Module32Next(snapshot.get(), &me)) {
- const auto e = GetLastError();
-
- // no more modules is not an error
- if (e != ERROR_NO_MORE_FILES) {
- qCritical().nospace().noquote()
- << "Module32Next() failed, " << formatSystemMessageQ(e);
- }
-
- break;
- }
- }
-
- // sorting by display name
- std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) {
- return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0);
- });
-
- return v;
-}
-
-std::vector<SecurityProduct> Environment::getSecurityProducts() const
-{
- std::vector<SecurityProduct> v;
-
- {
- auto fromWMI = getSecurityProductsFromWMI();
- v.insert(
- v.end(),
- std::make_move_iterator(fromWMI.begin()),
- std::make_move_iterator(fromWMI.end()));
- }
-
- if (auto p=getWindowsFirewall()) {
- v.push_back(std::move(*p));
- }
-
- return v;
-}
-
-std::vector<SecurityProduct> Environment::getSecurityProductsFromWMI() const
-{
- // some products may be present in multiple queries, such as a product marked
- // as both antivirus and antispyware, but they'll have the same GUID, so use
- // that to avoid duplicating entries
- std::map<QUuid, SecurityProduct> map;
-
- auto handleProduct = [&](auto* o) {
- VARIANT prop;
-
- // display name
- auto ret = o->Get(L"displayName", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- qCritical()
- << "failed to get displayName, "
- << formatSystemMessageQ(ret);
-
- return;
- }
-
- if (prop.vt != VT_BSTR) {
- qCritical() << "displayName is a " << prop.vt << ", not a bstr";
- return;
- }
-
- const std::wstring name = prop.bstrVal;
- VariantClear(&prop);
-
- // product state
- ret = o->Get(L"productState", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- qCritical()
- << "failed to get productState, "
- << formatSystemMessageQ(ret);
-
- return;
- }
-
- if (prop.vt != VT_UI4 && prop.vt != VT_I4) {
- qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4";
- return;
- }
-
- DWORD state = 0;
- if (prop.vt == VT_I4) {
- state = prop.lVal;
- } else {
- state = prop.ulVal;
- }
-
- VariantClear(&prop);
-
- // guid
- ret = o->Get(L"instanceGuid", 0, &prop, 0, 0);
- if (FAILED(ret)) {
- qCritical()
- << "failed to get instanceGuid, "
- << formatSystemMessageQ(ret);
-
- return;
- }
-
- if (prop.vt != VT_BSTR) {
- qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr";
- return;
- }
-
- const QUuid guid(QString::fromWCharArray(prop.bstrVal));
- VariantClear(&prop);
-
- const auto provider = static_cast<int>((state >> 16) & 0xff);
- const auto scanner = (state >> 8) & 0xff;
- const auto definitions = state & 0xff;
-
- const bool active = ((scanner & 0x10) != 0);
- const bool upToDate = (definitions == 0);
-
- map.insert({
- guid,
- {QString::fromStdWString(name), provider, active, upToDate}});
- };
-
- {
- WMI wmi("root\\SecurityCenter2");
- wmi.query("select * from AntivirusProduct", handleProduct);
- wmi.query("select * from FirewallProduct", handleProduct);
- wmi.query("select * from AntiSpywareProduct", handleProduct);
- }
-
- {
- WMI wmi("root\\SecurityCenter");
- wmi.query("select * from AntivirusProduct", handleProduct);
- wmi.query("select * from FirewallProduct", handleProduct);
- wmi.query("select * from AntiSpywareProduct", handleProduct);
- }
-
- std::vector<SecurityProduct> v;
-
- for (auto&& p : map) {
- v.push_back(p.second);
- }
-
- return v;
-}
-
-std::optional<SecurityProduct> Environment::getWindowsFirewall() const
-{
- HRESULT hr = 0;
-
- COMPtr<INetFwPolicy2> policy;
-
- {
- void* rawPolicy = nullptr;
-
- hr = CoCreateInstance(
- __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER,
- __uuidof(INetFwPolicy2), &rawPolicy);
-
- if (FAILED(hr) || !rawPolicy) {
- qCritical()
- << "CoCreateInstance for NetFwPolicy2 failed, "
- << formatSystemMessageQ(hr);
-
- return {};
- }
-
- policy.reset(static_cast<INetFwPolicy2*>(rawPolicy));
- }
-
- VARIANT_BOOL enabledVariant;
-
- if (policy) {
- hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant);
- if (FAILED(hr))
- {
- qCritical()
- << "get_FirewallEnabled failed, "
- << formatSystemMessageQ(hr);
-
- return {};
- }
- }
-
- const auto enabled = (enabledVariant != VARIANT_FALSE);
- if (!enabled) {
- return {};
- }
-
- return SecurityProduct(
- "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true);
-}
-
-
-Module::Module(QString path, std::size_t fileSize)
- : m_path(std::move(path)), m_fileSize(fileSize)
-{
- const auto fi = getFileInfo();
-
- m_version = getVersion(fi.ffi);
- m_timestamp = getTimestamp(fi.ffi);
- m_versionString = fi.fileDescription;
- m_md5 = getMD5();
-}
-
-const QString& Module::path() const
-{
- return m_path;
-}
-
-QString Module::displayPath() const
-{
- return QDir::fromNativeSeparators(m_path.toLower());
-}
-
-std::size_t Module::fileSize() const
-{
- return m_fileSize;
-}
-
-const QString& Module::version() const
-{
- return m_version;
-}
-
-const QString& Module::versionString() const
-{
- return m_versionString;
-}
-
-const QDateTime& Module::timestamp() const
-{
- return m_timestamp;
-}
-
-const QString& Module::md5() const
-{
- return m_md5;
-}
-
-QString Module::timestampString() const
-{
- if (!m_timestamp.isValid()) {
- return "(no timestamp)";
- }
-
- return m_timestamp.toString(Qt::DateFormat::ISODate);
-}
-
-QString Module::toString() const
-{
- QStringList sl;
-
- // file size
- sl.push_back(displayPath());
- sl.push_back(QString("%1 B").arg(m_fileSize));
-
- // version
- if (m_version.isEmpty() && m_versionString.isEmpty()) {
- sl.push_back("(no version)");
- } else {
- if (!m_version.isEmpty()) {
- sl.push_back(m_version);
- }
-
- if (!m_versionString.isEmpty() && m_versionString != m_version) {
- sl.push_back(versionString());
- }
- }
-
- // timestamp
- if (m_timestamp.isValid()) {
- sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate));
- } else {
- sl.push_back("(no timestamp)");
- }
-
- // md5
- if (!m_md5.isEmpty()) {
- sl.push_back(m_md5);
- }
-
- return sl.join(", ");
-}
-
-Module::FileInfo Module::getFileInfo() const
-{
- const auto wspath = m_path.toStdWString();
-
- // getting version info size
- DWORD dummy = 0;
- const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy);
-
- if (size == 0) {
- const auto e = GetLastError();
-
- if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) {
- // not an error, no version information built into that module
- return {};
- }
-
- qCritical().nospace().noquote()
- << "GetFileVersionInfoSizeW() failed on '" << m_path << "', "
- << formatSystemMessageQ(e);
-
- return {};
- }
-
- // getting version info
- auto buffer = std::make_unique<std::byte[]>(size);
-
- if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) {
- const auto e = GetLastError();
-
- qCritical().nospace().noquote()
- << "GetFileVersionInfoW() failed on '" << m_path << "', "
- << formatSystemMessageQ(e);
-
- return {};
- }
-
- // the version info has two major parts: a fixed version and a localizable
- // set of strings
-
- FileInfo fi;
- fi.ffi = getFixedFileInfo(buffer.get());
- fi.fileDescription = getFileDescription(buffer.get());
-
- return fi;
-}
-
-VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const
-{
- void* valuePointer = nullptr;
- unsigned int valueSize = 0;
-
- // the fixed version info is in the root
- const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize);
-
- if (!ret || !valuePointer || valueSize == 0) {
- // not an error, no fixed file info
- return {};
- }
-
- const auto* fi = static_cast<VS_FIXEDFILEINFO*>(valuePointer);
-
- // signature is always 0xfeef04bd
- if (fi->dwSignature != 0xfeef04bd) {
- qCritical().nospace().noquote()
- << "bad file info signature 0x" << hex << fi->dwSignature << " for "
- << "'" << m_path << "'";
-
- return {};
- }
-
- return *fi;
-}
-
-QString Module::getFileDescription(std::byte* buffer) const
-{
- struct LANGANDCODEPAGE
- {
- WORD wLanguage;
- WORD wCodePage;
- };
-
- void* valuePointer = nullptr;
- unsigned int valueSize = 0;
-
- // getting list of available languages
- auto ret = VerQueryValueW(
- buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize);
-
- if (!ret || !valuePointer || valueSize == 0) {
- qCritical().nospace().noquote()
- << "VerQueryValueW() for translations failed on '" << m_path << "'";
-
- return {};
- }
-
- // number of languages
- const auto count = valueSize / sizeof(LANGANDCODEPAGE);
- if (count == 0) {
- return {};
- }
-
- // using the first language in the list to get FileVersion
- const auto* lcp = static_cast<LANGANDCODEPAGE*>(valuePointer);
-
- const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion")
- .arg(lcp->wLanguage, 4, 16, QChar('0'))
- .arg(lcp->wCodePage, 4, 16, QChar('0'));
-
- ret = VerQueryValueW(
- buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize);
-
- if (!ret || !valuePointer || valueSize == 0) {
- // not an error, no file version
- return {};
- }
-
- // valueSize includes the null terminator
- return QString::fromWCharArray(
- static_cast<wchar_t*>(valuePointer), valueSize - 1);
-}
-
-QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const
-{
- if (fi.dwSignature == 0) {
- return {};
- }
-
- const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff;
- const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff;
- const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff;
- const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff;
-
- if (major == 0 && minor == 0 && maintenance == 0 && build == 0) {
- return {};
- }
-
- return QString("%1.%2.%3.%4")
- .arg(major).arg(minor).arg(maintenance).arg(build);
-}
-
-QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const
-{
- FILETIME ft = {};
-
- if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) {
- // if the file info is invalid or doesn't have a date, use the creation
- // time on the file
-
- // opening the file
- HandlePtr h(CreateFileW(
- m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
- OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
-
- if (h.get() == INVALID_HANDLE_VALUE) {
- const auto e = GetLastError();
-
- qCritical().nospace().noquote()
- << "can't open file '" << m_path << "' for timestamp, "
- << formatSystemMessageQ(e);
-
- return {};
- }
-
- // getting the file time
- if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) {
- const auto e = GetLastError();
- qCritical().nospace().noquote()
- << "can't get file time for '" << m_path << "', "
- << formatSystemMessageQ(e);
-
- return {};
- }
- } else {
- // use the time from the file info
- ft.dwHighDateTime = fi.dwFileDateMS;
- ft.dwLowDateTime = fi.dwFileDateLS;
- }
-
-
- // converting to SYSTEMTIME
- SYSTEMTIME utc = {};
-
- if (!FileTimeToSystemTime(&ft, &utc)) {
- qCritical().nospace().noquote()
- << "FileTimeToSystemTime() failed on timestamp "
- << "high=0x" << hex << ft.dwHighDateTime << " "
- << "low=0x" << hex << ft.dwLowDateTime << " for "
- << "'" << m_path << "'";
-
- return {};
- }
-
- return QDateTime(
- QDate(utc.wYear, utc.wMonth, utc.wDay),
- QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds));
-}
-
-QString Module::getMD5() const
-{
- if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) {
- // don't calculate md5 for system files, it's not really relevant and
- // it takes a while
- return {};
- }
-
- // opening the file
- QFile f(m_path);
-
- if (!f.open(QFile::ReadOnly)) {
- qCritical().nospace().noquote()
- << "failed to open file '" << m_path << "' for md5";
-
- return {};
- }
-
- // hashing
- QCryptographicHash hash(QCryptographicHash::Md5);
- if (!hash.addData(&f)) {
- qCritical().nospace().noquote()
- << "failed to calculate md5 for '" << m_path << "'";
-
- return {};
- }
-
- return hash.result().toHex();
-}
-
-
-WindowsInfo::WindowsInfo()
-{
- // loading ntdll.dll, the functions will be found with GetProcAddress()
- std::unique_ptr<HINSTANCE, LibraryFreer> ntdll(LoadLibraryW(L"ntdll.dll"));
-
- if (!ntdll) {
- qCritical() << "failed to load ntdll.dll while getting version";
- return;
- } else {
- m_reported = getReportedVersion(ntdll.get());
- m_real = getRealVersion(ntdll.get());
- }
-
- m_release = getRelease();
- m_elevated = getElevated();
-}
-
-bool WindowsInfo::compatibilityMode() const
-{
- if (m_real == Version()) {
- // don't know the real version, can't guess compatibility mode
- return false;
- }
-
- return (m_real != m_reported);
-}
-
-const WindowsInfo::Version& WindowsInfo::reportedVersion() const
-{
- return m_reported;
-}
-
-const WindowsInfo::Version& WindowsInfo::realVersion() const
-{
- return m_real;
-}
-
-const WindowsInfo::Release& WindowsInfo::release() const
-{
- return m_release;
-}
-
-std::optional<bool> WindowsInfo::isElevated() const
-{
- return m_elevated;
-}
-
-QString WindowsInfo::toString() const
-{
- QStringList sl;
-
- const QString reported = m_reported.toString();
- const QString real = m_real.toString();
-
- // version
- sl.push_back("version: " + reported);
-
- // real version if different
- if (compatibilityMode()) {
- sl.push_back("real version: " + real);
- }
-
- // build.UBR, such as 17763.557
- if (m_release.UBR != 0) {
- DWORD build = 0;
-
- if (compatibilityMode()) {
- build = m_real.build;
- } else {
- build = m_reported.build;
- }
-
- sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR));
- }
-
- // release ID
- if (!m_release.ID.isEmpty()) {
- sl.push_back("release " + m_release.ID);
- }
-
- // buildlab string
- if (!m_release.buildLab.isEmpty()) {
- sl.push_back(m_release.buildLab);
- }
-
- // product name
- if (!m_release.productName.isEmpty()) {
- sl.push_back(m_release.productName);
- }
-
- // elevated
- QString elevated = "?";
- if (m_elevated.has_value()) {
- elevated = (*m_elevated ? "yes" : "no");
- }
-
- sl.push_back("elevated: " + elevated);
-
- return sl.join(", ");
-}
-
-WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const
-{
- // windows has been deprecating pretty much all the functions having to do
- // with getting version information because apparently, people keep misusing
- // them for feature detection
- //
- // there's still RtlGetVersion() though
-
- using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW);
-
- auto* RtlGetVersion = reinterpret_cast<RtlGetVersionType*>(
- GetProcAddress(ntdll, "RtlGetVersion"));
-
- if (!RtlGetVersion) {
- qCritical() << "RtlGetVersion() not found in ntdll.dll";
- return {};
- }
-
- OSVERSIONINFOEX vi = {};
- vi.dwOSVersionInfoSize = sizeof(vi);
-
- // this apparently never fails
- RtlGetVersion((RTL_OSVERSIONINFOW*)&vi);
-
- return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber};
-}
-
-WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const
-{
- // getting the actual windows version is more difficult because all the
- // functions are lying when running in compatibility mode
- //
- // RtlGetNtVersionNumbers() is an undocumented function that seems to work
- // fine, but it might not in the future
-
- using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*);
-
- auto* RtlGetNtVersionNumbers = reinterpret_cast<RtlGetNtVersionNumbersType*>(
- GetProcAddress(ntdll, "RtlGetNtVersionNumbers"));
-
- if (!RtlGetNtVersionNumbers) {
- qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll";
- return {};
- }
-
- DWORD major=0, minor=0, build=0;
- RtlGetNtVersionNumbers(&major, &minor, &build);
-
- // for whatever reason, the build number has 0xf0000000 set
- build = 0x0fffffff & build;
-
- return {major, minor, build};
-}
-
-WindowsInfo::Release WindowsInfo::getRelease() const
-{
- // there are several interesting items in the registry, but most of them
- // are undocumented, not always available, and localizable
- //
- // most of them are used to provide as much information as possible in case
- // any of the other versions fail to work
-
- QSettings settings(
- R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
- QSettings::NativeFormat);
-
- Release r;
-
- // buildlab seems to be an internal name from the build system
- r.buildLab = settings.value("BuildLabEx", "").toString();
- if (r.buildLab.isEmpty()) {
- r.buildLab = settings.value("BuildLab", "").toString();
- if (r.buildLab.isEmpty()) {
- r.buildLab = settings.value("BuildBranch", "").toString();
- }
- }
-
- // localized name of windows, such as "Windows 10 Pro"
- r.productName = settings.value("ProductName", "").toString();
-
- // release ID, such as 1803
- r.ID = settings.value("ReleaseId", "").toString();
-
- // some other build number, shown in winver.exe
- r.UBR = settings.value("UBR", 0).toUInt();
-
- return r;
-}
-
-std::optional<bool> WindowsInfo::getElevated() const
-{
- HandlePtr token;
-
- {
- HANDLE rawToken = 0;
-
- if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) {
- const auto e = GetLastError();
-
- qCritical()
- << "while trying to check if process is elevated, "
- << "OpenProcessToken() failed: " << formatSystemMessageQ(e);
-
- return {};
- }
-
- token.reset(rawToken);
- }
-
- TOKEN_ELEVATION e = {};
- DWORD size = sizeof(TOKEN_ELEVATION);
-
- if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) {
- const auto e = GetLastError();
-
- qCritical()
- << "while trying to check if process is elevated, "
- << "GetTokenInformation() failed: " << formatSystemMessageQ(e);
-
- return {};
- }
-
- return (e.TokenIsElevated != 0);
-}
-
-
-SecurityProduct::SecurityProduct(
- QString name, int provider,
- bool active, bool upToDate) :
- m_name(std::move(name)), m_provider(provider),
- m_active(active), m_upToDate(upToDate)
-{
-}
-
-const QString& SecurityProduct::name() const
-{
- return m_name;
-}
-
-int SecurityProduct::provider() const
-{
- return m_provider;
-}
-
-bool SecurityProduct::active() const
-{
- return m_active;
-}
-
-bool SecurityProduct::upToDate() const
-{
- return m_upToDate;
-}
-
-QString SecurityProduct::toString() const
-{
- QString s;
-
- s += m_name + " ";
-
-
- QStringList ps;
- if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) {
- ps.push_back("firewall");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) {
- ps.push_back("autoupdate");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) {
- ps.push_back("antivirus");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) {
- ps.push_back("antispyware");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) {
- ps.push_back("settings");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) {
- ps.push_back("uac");
- }
-
- if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) {
- ps.push_back("service");
- }
-
- if (ps.empty()) {
- s += "(doesn't provide anything)";
- } else {
- s += "(" + ps.join("|") + ")";
- }
-
- if (m_active) {
- s += ", active";
- } else {
- s += ", inactive";
- }
-
- if (!m_upToDate) {
- s += ", definitions outdated";
- }
-
- return s;
-}
-
-
-struct Process
-{
- std::wstring filename;
- DWORD pid;
-
- Process(std::wstring f, DWORD id)
- : filename(std::move(f)), pid(id)
- {
- }
-};
-
-// returns the filename of the given process or the current one
-//
-std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE)
-{
- // double the buffer size 10 times
- const int MaxTries = 10;
-
- DWORD bufferSize = MAX_PATH;
-
- for (int tries=0; tries<MaxTries; ++tries)
- {
- auto buffer = std::make_unique<wchar_t[]>(bufferSize + 1);
- std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0);
-
- DWORD writtenSize = 0;
-
- if (process == INVALID_HANDLE_VALUE) {
- // query this process
- writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize);
- } else {
- // query another process
- writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize);
- }
-
- if (writtenSize == 0) {
- // hard failure
- const auto e = GetLastError();
- std::wcerr << formatSystemMessage(e) << L"\n";
- break;
- } else if (writtenSize >= bufferSize) {
- // buffer is too small, try again
- bufferSize *= 2;
- } else {
- // if GetModuleFileName() works, `writtenSize` does not include the null
- // terminator
- const std::wstring s(buffer.get(), writtenSize);
- const fs::path path(s);
-
- return path.filename().native();
- }
- }
-
- // something failed or the path is way too long to make sense
-
- std::wstring what;
- if (process == INVALID_HANDLE_VALUE) {
- what = L"the current process";
- } else {
- what = L"pid " + std::to_wstring(reinterpret_cast<std::uintptr_t>(process));
- }
-
- std::wcerr << L"failed to get filename for " << what << L"\n";
- return {};
-}
-
-std::vector<DWORD> runningProcessesIds()
-{
- // double the buffer size 10 times
- const int MaxTries = 10;
-
- // initial size of 300 processes, unlikely to be more than that
- std::size_t size = 300;
-
- for (int tries=0; tries<MaxTries; ++tries) {
- auto ids = std::make_unique<DWORD[]>(size);
- std::fill(ids.get(), ids.get() + size, 0);
-
- DWORD bytesGiven = static_cast<DWORD>(size * sizeof(ids[0]));
- DWORD bytesWritten = 0;
-
- if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten))
- {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to enumerate processes, "
- << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- if (bytesWritten == bytesGiven) {
- // no way to distinguish between an exact fit and not enough space,
- // just try again
- size *= 2;
- continue;
- }
-
- const auto count = bytesWritten / sizeof(ids[0]);
- return std::vector<DWORD>(ids.get(), ids.get() + count);
- }
-
- std::cerr << L"too many processes to enumerate";
- return {};
-}
-
-std::vector<Process> runningProcesses()
-{
- const auto pids = runningProcessesIds();
- std::vector<Process> v;
-
- for (const auto& pid : pids) {
- if (pid == 0) {
- // the idle process has pid 0 and seems to be picked up by EnumProcesses()
- continue;
- }
-
- HandlePtr h(OpenProcess(
- PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
-
- if (!h) {
- const auto e = GetLastError();
-
- if (e != ERROR_ACCESS_DENIED) {
- // don't log access denied, will happen a lot for system processes, even
- // when elevated
- std::wcerr
- << L"failed to open process " << pid << L", "
- << formatSystemMessage(e) << L"\n";
- }
-
- continue;
- }
-
- auto filename = processFilename(h.get());
- if (!filename.empty()) {
- v.emplace_back(std::move(filename), pid);
- }
- }
-
- return v;
-}
-
-DWORD findOtherPid()
-{
- const std::wstring defaultName = L"ModOrganizer.exe";
-
- std::wclog << L"looking for the other process...\n";
-
- // used to skip the current process below
- const auto thisPid = GetCurrentProcessId();
- std::wclog << L"this process id is " << thisPid << L"\n";
-
- // getting the filename for this process, assumes the other process has the
- // smae one
- auto filename = processFilename();
- if (filename.empty()) {
- std::wcerr
- << L"can't get current process filename, defaulting to "
- << defaultName << L"\n";
-
- filename = defaultName;
- } else {
- std::wclog << L"this process filename is " << filename << L"\n";
- }
-
- // getting all running processes
- const auto processes = runningProcesses();
- std::wclog << L"there are " << processes.size() << L" processes running\n";
-
- // going through processes, trying to find one with the same name and a
- // different pid than this process has
- for (const auto& p : processes) {
- if (p.filename == filename) {
- if (p.pid != thisPid) {
- return p.pid;
- }
- }
- }
-
- std::wclog
- << L"no process with this filename\n"
- << L"MO may not be running, or it may be running as administrator\n"
- << L"you can try running this again as administrator\n";
-
- return 0;
-}
-
-std::wstring tempDir()
-{
- const DWORD bufferSize = MAX_PATH + 1;
- wchar_t buffer[bufferSize + 1] = {};
-
- const auto written = GetTempPathW(bufferSize, buffer);
- if (written == 0) {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to get temp path, " << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- // `written` does not include the null terminator
- return std::wstring(buffer, buffer + written);
-}
-
-HandlePtr tempFile(const std::wstring dir)
-{
- // maximum tries of incrementing the counter
- const int MaxTries = 100;
-
- // UTC time and date will be in the filename
- const auto now = std::time(0);
- const auto tm = std::gmtime(&now);
-
- // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where
- // i can go until MaxTries
- std::wostringstream oss;
- oss
- << L"ModOrganizer-"
- << std::setw(4) << (1900 + tm->tm_year)
- << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1)
- << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T"
- << std::setw(2) << std::setfill(L'0') << tm->tm_hour
- << std::setw(2) << std::setfill(L'0') << tm->tm_min
- << std::setw(2) << std::setfill(L'0') << tm->tm_sec;
-
- const std::wstring prefix = oss.str();
- const std::wstring ext = L".dmp";
-
- // first path to try, without counter in it
- std::wstring path = dir + L"\\" + prefix + ext;
-
- for (int i=0; i<MaxTries; ++i) {
- std::wclog << L"trying file '" << path << L"'\n";
-
- HandlePtr h (CreateFileW(
- path.c_str(), GENERIC_WRITE, 0, nullptr,
- CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
-
- if (h.get() != INVALID_HANDLE_VALUE) {
- // worked
- return h;
- }
-
- const auto e = GetLastError();
-
- if (e != ERROR_FILE_EXISTS) {
- // probably no write access
- std::wcerr
- << L"failed to create dump file, " << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- // try again with "-i"
- path = dir + L"\\" + prefix + L"-" + std::to_wstring(i + 1) + ext;
- }
-
- std::wcerr << L"can't create dump file, ran out of filenames\n";
- return {};
-}
-
-HandlePtr dumpFile()
-{
- // try the current directory
- HandlePtr h = tempFile(L".");
- if (h.get() != INVALID_HANDLE_VALUE) {
- return h;
- }
-
- std::wclog << L"cannot write dump file in current directory\n";
-
- // try the temp directory
- const auto dir = tempDir();
-
- if (!dir.empty()) {
- h = tempFile(dir.c_str());
- if (h.get() != INVALID_HANDLE_VALUE) {
- return h;
- }
- }
-
- return {};
-}
-
-bool createMiniDump(HANDLE process, CoreDumpTypes type)
-{
- const DWORD pid = GetProcessId(process);
-
- const HandlePtr file = dumpFile();
- if (!file) {
- std::wcerr << L"nowhere to write the dump file\n";
- return false;
- }
-
- auto flags = _MINIDUMP_TYPE(
- MiniDumpNormal |
- MiniDumpWithHandleData |
- MiniDumpWithUnloadedModules |
- MiniDumpWithProcessThreadData);
-
- if (type == CoreDumpTypes::Data) {
- std::wclog << L"writing minidump with data\n";
- flags = _MINIDUMP_TYPE(flags | MiniDumpWithDataSegs);
- } else if (type == CoreDumpTypes::Full) {
- std::wclog << L"writing full minidump\n";
- flags = _MINIDUMP_TYPE(flags | MiniDumpWithFullMemory);
- } else {
- std::wclog << L"writing mini minidump\n";
- }
-
- const auto ret = MiniDumpWriteDump(
- process, pid, file.get(), flags, nullptr, nullptr, nullptr);
-
- if (!ret) {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to write mini dump, " << formatSystemMessage(e) << L"\n";
-
- return false;
- }
-
- std::wclog << L"minidump written correctly\n";
- return true;
-}
-
-
-bool coredump(CoreDumpTypes type)
-{
- std::wclog << L"creating minidump for the current process\n";
- return createMiniDump(GetCurrentProcess(), type);
-}
-
-bool coredumpOther(CoreDumpTypes type)
-{
- std::wclog << L"creating minidump for an running process\n";
-
- const auto pid = findOtherPid();
- if (pid == 0) {
- std::wcerr << L"no other process found\n";
- return false;
- }
-
- std::wclog << L"found other process with pid " << pid << L"\n";
-
- HandlePtr handle(OpenProcess(
- PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
-
- if (!handle) {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to open process " << pid << L", "
- << formatSystemMessage(e) << L"\n";
-
- return false;
- }
-
- return createMiniDump(handle.get(), type);
-}
-
-} // namespace env
-
} // namespace MOShared
diff --git a/src/shared/util.h b/src/shared/util.h index c4a2ed7d..7bae96f2 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -20,13 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #ifndef UTIL_H
#define UTIL_H
-
#include <string>
-#include <optional>
-
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-
#include <versioninfo.h>
class Executable;
@@ -50,394 +44,6 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs);
-
-namespace env
-{
-
-// an application shortcut that can be either on the desktop or the start menu
-//
-class Shortcut
-{
-public:
- // location of a shortcut
- //
- enum Locations
- {
- None = 0,
-
- // on the desktop
- Desktop,
-
- // in the start menu
- StartMenu
- };
-
-
- // empty shortcut
- //
- Shortcut();
-
- // shortcut from an executable
- //
- explicit Shortcut(const Executable& exe);
-
- // sets the name of the shortcut, shown on icons and start menu entries
- //
- Shortcut& name(const QString& s);
-
- // the program to start
- //
- Shortcut& target(const QString& s);
-
- // arguments to pass
- //
- Shortcut& arguments(const QString& s);
-
- // shows in the status bar of explorer, for example
- //
- Shortcut& description(const QString& s);
-
- // path to a binary that contains the icon and its index
- //
- Shortcut& icon(const QString& s, int index=0);
-
- // "start in" option for this shortcut
- //
- Shortcut& workingDirectory(const QString& s);
-
-
- // returns whether this shortcut already exists at the given location; this
- // does not check whether the shortcut parameters are different, it merely if
- // the .lnk file exists
- //
- bool exists(Locations loc) const;
-
- // calls remove() if exists(), or add()
- //
- bool toggle(Locations loc);
-
- // adds the shortcut to the given location
- //
- bool add(Locations loc);
-
- // removes the shortcut from the given location
- //
- bool remove(Locations loc);
-
-private:
- QString m_name;
- QString m_target;
- QString m_arguments;
- QString m_description;
- QString m_icon;
- int m_iconIndex;
- QString m_workingDirectory;
-
- // returns a qCritical() logger with a prefix already logged
- //
- QDebug critical() const;
-
- // returns a qDebug() logger with a prefix already logged
- //
- QDebug debug() const;
-
-
- // returns the path where the shortcut file should be saved
- //
- QString shortcutPath(Locations loc) const;
-
- // returns the directory where the shortcut file should be saved
- //
- QString shortcutDirectory(Locations loc) const;
-
- // returns the filename of the shortcut file that should be used when saving
- //
- QString shortcutFilename() const;
-};
-
-
-// returns a string representation of the given location
-//
-QString toString(Shortcut::Locations loc);
-
-
-// represents one module
-//
-class Module
-{
-public:
- explicit Module(QString path, std::size_t fileSize);
-
- // returns the module's path
- //
- const QString& path() const;
-
- // returns the module's path in lowercase and using forward slashes
- //
- QString displayPath() const;
-
- // returns the size in bytes, may be 0
- //
- std::size_t fileSize() const;
-
- // returns the x.x.x.x version embedded from the version info, may be empty
- //
- const QString& version() const;
-
- // returns the FileVersion entry from the resource file, returns
- // "(no version)" if not available
- //
- const QString& versionString() const;
-
- // returns the build date from the version info, or the creation time of the
- // file on the filesystem, may be empty
- //
- const QDateTime& timestamp() const;
-
- // returns the md5 of the file, may be empty for system files
- //
- const QString& md5() const;
-
- // converts timestamp() to a string for display, returns "(no timestamp)" if
- // not available
- //
- QString timestampString() const;
-
- // returns a string with all the above information on one line
- //
- QString toString() const;
-
-private:
- // contains the information from the version resource
- //
- struct FileInfo
- {
- VS_FIXEDFILEINFO ffi;
- QString fileDescription;
- };
-
- QString m_path;
- std::size_t m_fileSize;
- QString m_version;
- QDateTime m_timestamp;
- QString m_versionString;
- QString m_md5;
-
- // returns information from the version resource
- //
- FileInfo getFileInfo() const;
-
- // uses VS_FIXEDFILEINFO to build the version string
- //
- QString getVersion(const VS_FIXEDFILEINFO& fi) const;
-
- // uses the file date from VS_FIXEDFILEINFO if available, or gets the
- // creation date on the file
- //
- QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const;
-
- // returns the md5 hash unless the path contains "\windows\"
- //
- QString getMD5() const;
-
- // gets VS_FIXEDFILEINFO from the file version info buffer
- //
- VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const;
-
- // gets FileVersion from the file version info buffer
- //
- QString getFileDescription(std::byte* buffer) const;
-};
-
-
-// a variety of information on windows
-//
-class WindowsInfo
-{
-public:
- struct Version
- {
- DWORD major=0, minor=0, build=0;
-
- QString toString() const
- {
- return QString("%1.%2.%3").arg(major).arg(minor).arg(build);
- }
-
- friend bool operator==(const Version& a, const Version& b)
- {
- return
- a.major == b.major &&
- a.minor == b.minor &&
- a.build == b.build;
- }
-
- friend bool operator!=(const Version& a, const Version& b)
- {
- return !(a == b);
- }
- };
-
- struct Release
- {
- // the BuildLab entry from the registry, may be empty
- QString buildLab;
-
- // product name such as "Windows 10 Pro", may not be in English, may be
- // empty
- QString productName;
-
- // release ID such as 1809, may be mepty
- QString ID;
-
- // some sub-build number, undocumented, may be empty
- DWORD UBR;
-
- Release()
- : UBR(0)
- {
- }
- };
-
-
- WindowsInfo();
-
- // tries to guess whether this process is running in compatibility mode
- //
- bool compatibilityMode() const;
-
- // returns the Windows version, may not correspond to the actual version
- // if the process is running in compatibility mode
- //
- const Version& reportedVersion() const;
-
- // tries to guess the real Windows version that's running, can be empty
- //
- const Version& realVersion() const;
-
- // various information about the current release
- //
- const Release& release() const;
-
- // whether this process is running as administrator, may be empty if the
- // information is not available
- std::optional<bool> isElevated() const;
-
- // returns a string with all the above information on one line
- //
- QString toString() const;
-
-private:
- Version m_reported, m_real;
- Release m_release;
- std::optional<bool> m_elevated;
-
- // uses RtlGetVersion() to get the version number as reported by Windows
- //
- Version getReportedVersion(HINSTANCE ntdll) const;
-
- // uses RtlGetNtVersionNumbers() to get the real version number
- //
- Version getRealVersion(HINSTANCE ntdll) const;
-
- // gets various information from the registry
- //
- Release getRelease() const;
-
- // gets whether the process is elevated
- //
- std::optional<bool> getElevated() const;
-};
-
-
-// represents a security product, such as an antivirus or a firewall
-//
-class SecurityProduct
-{
-public:
- SecurityProduct(
- QString name, int provider,
- bool active, bool upToDate);
-
- // display name of the product
- //
- const QString& name() const;
-
- // a bunch of _WSC_SECURITY_PROVIDER flags
- //
- int provider() const;
-
- // whether the product is active
- //
- bool active() const;
-
- // whether its definitions are up-to-date
- //
- bool upToDate() const;
-
- // string representation of the above
- //
- QString toString() const;
-
-private:
- QString m_name;
- int m_provider;
- bool m_active;
- bool m_upToDate;
-};
-
-
-// represents the process's environment
-//
-class Environment
-{
-public:
- Environment();
-
- // list of loaded modules in the current process
- //
- const std::vector<Module>& loadedModules();
-
- // information about the operating system
- //
- const WindowsInfo& windowsInfo() const;
-
- // information about the installed security products
- //
- const std::vector<SecurityProduct>& securityProducts() const;
-
-private:
- std::vector<Module> m_modules;
- WindowsInfo m_windows;
- std::vector<SecurityProduct> m_security;
-
- std::vector<Module> getLoadedModules() const;
- std::vector<SecurityProduct> getSecurityProducts() const;
-
- std::vector<SecurityProduct> getSecurityProductsFromWMI() const;
- std::optional<SecurityProduct> getWindowsFirewall() const;
-};
-
-
-enum class CoreDumpTypes
-{
- Mini = 1,
- Data,
- Full
-};
-
-// creates a minidump file for the given process
-//
-bool coredump(CoreDumpTypes type);
-
-// finds another process with the same name as this one and creates a minidump
-// file for it
-//
-bool coredumpOther(CoreDumpTypes type);
-
-} // namespace env
-
-
MOBase::VersionInfo createVersionInfo();
} // namespace MOShared
diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index 4ee4716e..b1643b2d 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "ui_syncoverwritedialog.h"
#include <utility.h>
#include <report.h>
+#include <log.h>
#include <QDir>
#include <QDirIterator>
@@ -86,7 +87,7 @@ void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *director if (subDir != nullptr) {
readTree(fileInfo.absoluteFilePath(), subDir, newItem);
} else {
- qCritical("no directory structure for %s?", qUtf8Printable(file));
+ log::error("no directory structure for {}?", file);
delete newItem;
newItem = nullptr;
}
diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 130cd76f..0c0eb1cc 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -1,7 +1,10 @@ #include "texteditor.h" #include "utility.h" +#include <log.h> #include <QSplitter> +using namespace MOBase; + TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr), @@ -249,7 +252,7 @@ QWidget* TextEditor::wrapEditWidget() auto index = splitter->indexOf(this); if (index == -1) { - qCritical( + log::error( "TextEditor: cannot wrap edit widget to display a toolbar, " "parent is a splitter, but widget isn't in it"); @@ -260,7 +263,7 @@ QWidget* TextEditor::wrapEditWidget() } else { // unknown parent - qCritical( + log::error( "TextEditor: cannot wrap edit widget to display a toolbar, " "no parent or parent has no layout"); diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index 451f56d4..6b8f7bd0 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "isavegame.h"
#include "savegameinfo.h"
#include <utility.h>
+#include <log.h>
#include <QtDebug>
#include <QDateTime>
@@ -186,7 +187,7 @@ void TransferSavesDialog::on_moveToLocalBtn_clicked() [this](const QString &source, const QString &destination) -> bool {
return shellMove(source, destination, this);
},
- "Failed to move %s to %s")) {
+ "Failed to move {} to {}")) {
refreshGlobalSaves();
refreshGlobalCharacters();
refreshLocalSaves();
@@ -203,7 +204,7 @@ void TransferSavesDialog::on_copyToLocalBtn_clicked() [this](const QString &source, const QString &destination) -> bool {
return shellCopy(source, destination, this);
},
- "Failed to copy %s to %s")) {
+ "Failed to copy {} to {}")) {
refreshLocalSaves();
refreshLocalCharacters();
}
@@ -218,7 +219,7 @@ void TransferSavesDialog::on_moveToGlobalBtn_clicked() [this](const QString &source, const QString &destination) -> bool {
return shellMove(source, destination, this);
},
- "Failed to move %s to %s")) {
+ "Failed to move {} to {}")) {
refreshGlobalSaves();
refreshGlobalCharacters();
refreshLocalSaves();
@@ -235,7 +236,7 @@ void TransferSavesDialog::on_copyToGlobalBtn_clicked() [this](const QString &source, const QString &destination) -> bool {
return shellCopy(source, destination, this);
},
- "Failed to copy %s to %s")) {
+ "Failed to copy {} to {}")) {
refreshGlobalSaves();
refreshGlobalCharacters();
}
@@ -340,9 +341,7 @@ bool TransferSavesDialog::transferCharacters( }
if (!method(sourceFile.absoluteFilePath(), destinationFile)) {
- qCritical(errmsg,
- sourceFile.absoluteFilePath().toUtf8().constData(),
- qUtf8Printable(destinationFile));
+ log::error(errmsg, sourceFile.absoluteFilePath(), destinationFile);
}
}
}
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index b752667d..b5e6edb1 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -32,7 +32,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <qstandardpaths.h> static const char SHMID[] = "mod_organizer_instance"; - +using namespace MOBase; std::string to_hex(void *bufferIn, size_t bufferSize) { @@ -60,8 +60,7 @@ LogWorker::LogWorker() "yyyy-MM-dd_hh-mm-ss"))) { m_LogFile.open(QIODevice::WriteOnly); - qDebug("usvfs log messages are written to %s", - qUtf8Printable(m_LogFile.fileName())); + log::debug("usvfs log messages are written to {}", m_LogFile.fileName()); } LogWorker::~LogWorker() @@ -90,15 +89,16 @@ void LogWorker::exit() m_QuitRequested = true; } -LogLevel logLevel(int level) +LogLevel toUsvfsLogLevel(log::Levels level) { - switch (static_cast<LogLevel>(level)) { - case LogLevel::Info: + switch (level) { + case log::Info: return LogLevel::Info; - case LogLevel::Warning: + case log::Warning: return LogLevel::Warning; - case LogLevel::Error: + case log::Error: return LogLevel::Error; + case log::Debug: // fall-through default: return LogLevel::Debug; } @@ -118,17 +118,67 @@ CrashDumpsType crashDumpsType(int type) } } +QString toString(LogLevel lv) +{ + switch (lv) + { + case LogLevel::Debug: + return "debug"; + + case LogLevel::Info: + return "info"; + + case LogLevel::Warning: + return "warning"; + + case LogLevel::Error: + return "error"; + + default: + return QString("%1").arg(static_cast<int>(lv)); + } +} + +QString toString(CrashDumpsType t) +{ + switch (t) + { + case CrashDumpsType::None: + return "none"; + + case CrashDumpsType::Mini: + return "mini"; + + case CrashDumpsType::Data: + return "data"; + + case CrashDumpsType::Full: + return "full"; + + default: + return QString("%1").arg(static_cast<int>(t)); + } +} + UsvfsConnector::UsvfsConnector() { USVFSParameters params; - LogLevel level = logLevel(Settings::instance().logLevel()); + LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel()); CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType()); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); InitLogging(false); - qDebug("Initializing VFS <%s, %d, %d, %s>", params.instanceName, params.logLevel, params.crashDumpsType, params.crashDumpsPath); + log::debug( + "initializing usvfs:\n" + " . instance: {}\n" + " . log: {}\n" + " . dump: {} ({})", + params.instanceName, + toString(params.logLevel), + params.crashDumpsPath, + toString(params.crashDumpsType)); CreateVFS(¶ms); @@ -167,7 +217,7 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) int files = 0; int dirs = 0; - qDebug("Updating VFS mappings..."); + log::debug("Updating VFS mappings..."); ClearVirtualMappings(); @@ -195,19 +245,13 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) } } - qDebug("VFS mappings updated <linked %d dirs, %d files>", dirs, files); - /* - size_t dumpSize = 0; - CreateVFSDump(nullptr, &dumpSize); - std::unique_ptr<char[]> buffer(new char[dumpSize]); - CreateVFSDump(buffer.get(), &dumpSize); - qDebug(buffer.get()); - */ + log::debug("VFS mappings updated <linked {} dirs, {} files>", dirs, files); } -void UsvfsConnector::updateParams(int logLevel, int crashDumpsType, QString executableBlacklist) +void UsvfsConnector::updateParams( + MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist) { - USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType)); + USVFSUpdateParams(toUsvfsLogLevel(logLevel), ::crashDumpsType(crashDumpsType)); ClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { std::wstring buf = exec.toStdWString(); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index 8a88bde5..b0bd320c 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDebug> #include <QList> #include <usvfsparameters.h> +#include <log.h> #include "executableinfo.h" @@ -84,7 +85,11 @@ public: ~UsvfsConnector(); void updateMapping(const MappingType &mapping); - void updateParams(int logLevel, int crashDumpsType, QString executableBlacklist); + + void updateParams( + MOBase::log::Levels logLevel, int crashDumpsType, + QString executableBlacklist); + void updateForcedLibraries(const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries); private: |
