From 8d6c715672023577dca5a6528ff49081dede1052 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 11 Apr 2018 17:46:59 -0500 Subject: Initial archive conflict parsing Squashed commit: Basic archive conflict parsing - pass 1 Merge fixes for archive parsing Basic archive conflict parsing - pass 1 Merge fixes for archive parsing Should fix conflict detection for archive files --- src/modinfo.h | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'src/modinfo.h') diff --git a/src/modinfo.h b/src/modinfo.h index 5a69185d..15876a23 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -72,6 +72,9 @@ public: FLAG_CONFLICT_OVERWRITTEN, FLAG_CONFLICT_MIXED, FLAG_CONFLICT_REDUNDANT, + FLAG_ARCHIVE_CONFLICT_OVERWRITE, + FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, + FLAG_ARCHIVE_CONFLICT_MIXED, FLAG_PLUGIN_SELECTED, FLAG_ALTERNATE_GAME }; @@ -90,7 +93,6 @@ public: CONTENT_MCM, CONTENT_INI, CONTENT_MODGROUP - }; static const int NUM_CONTENT_TYPES = CONTENT_MODGROUP + 1; @@ -625,6 +627,26 @@ public: */ virtual std::set getModOverwritten() { return std::set(); } + /** + * @return retrieve list of mods (as mod index) with archives that are overwritten by this one. Updates may be delayed + */ + virtual std::set getModArchiveOverwrite() { return std::set(); } + + /** + * @return list of mods (as mod index) with archives that overwrite this one. Updates may be delayed + */ + virtual std::set getModArchiveOverwritten() { return std::set(); } + + /** + * @return retrieve list of mods (as mod index) with archives that are overwritten by this one. Updates may be delayed + */ + virtual std::set getModArchiveOverwrite() { return std::set(); } + + /** + * @return list of mods (as mod index) with archives that overwrite this one. Updates may be delayed + */ + virtual std::set getModArchiveOverwritten() { return std::set(); } + /** * @brief update conflict information */ -- cgit v1.3.1 From 01e1232cf85f09efde780537bbbca40fd6f0c097 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Thu, 2 Aug 2018 15:33:27 +0200 Subject: Added new "loose files overwrite archive" icon and all the flags. The logic to show it is still missing until the other issues are fixed. --- src/modflagicondelegate.cpp | 1 + src/modinfo.h | 1 + src/modlist.cpp | 7 ++++--- src/modlistsortproxy.cpp | 3 ++- src/resources.qrc | 3 ++- src/resources/conflict-overwrite-blue.png | Bin 0 -> 964 bytes 6 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 src/resources/conflict-overwrite-blue.png (limited to 'src/modinfo.h') diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index 4404ce60..f7f85a18 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -45,6 +45,7 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const case ModInfo::FLAG_NOTENDORSED: return ":/MO/gui/emblem_notendorsed"; case ModInfo::FLAG_NOTES: return ":/MO/gui/emblem_notes"; case ModInfo::FLAG_CONFLICT_OVERWRITE: return ":/MO/gui/emblem_conflict_overwrite"; + case ModInfo::FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE: return ":/MO/gui/emblem_conflict_loose_overwrite_archive"; case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return ":/MO/gui/emblem_conflict_overwritten"; case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; case ModInfo::FLAG_CONFLICT_REDUNDANT: return ":MO/gui/emblem_conflict_redundant"; diff --git a/src/modinfo.h b/src/modinfo.h index 15876a23..c763e69b 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -69,6 +69,7 @@ public: FLAG_NOTENDORSED, FLAG_NOTES, FLAG_CONFLICT_OVERWRITE, + FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE, FLAG_CONFLICT_OVERWRITTEN, FLAG_CONFLICT_MIXED, FLAG_CONFLICT_REDUNDANT, diff --git a/src/modlist.cpp b/src/modlist.cpp index e9a12862..876e8958 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -158,9 +158,10 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const output << QString("%1").arg(modInfo->notes()).replace("\n", "
"); return output.join("
"); } - case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites files"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten files"); - case ModInfo::FLAG_CONFLICT_MIXED: return tr("Overwrites & Overwritten"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); + case ModInfo::FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE: return tr("Loose files overwrite another archive"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); + case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); case ModInfo::FLAG_ALTERNATE_GAME: return tr("Alternate game source"); case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 6f6983ba..c34985b6 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -243,7 +243,8 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) (flag == ModInfo::FLAG_CONFLICT_REDUNDANT) || (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE) || (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED)) { + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE)) { return true; } } diff --git a/src/resources.qrc b/src/resources.qrc index 1dccce0b..dff1ad72 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -47,7 +47,8 @@ resources/conflict-mixed.png resources/conflict-overwrite.png resources/conflict-overwritten.png - resources/conflict-redundant.png + resources/conflict-redundant.png + resources/conflict-overwrite-blue.png resources/accessories-text-editor.png resources/x-office-calendar.png resources/dialog-warning_16.png diff --git a/src/resources/conflict-overwrite-blue.png b/src/resources/conflict-overwrite-blue.png new file mode 100644 index 00000000..3247e7b7 Binary files /dev/null and b/src/resources/conflict-overwrite-blue.png differ -- cgit v1.3.1 From f9f70b224e25c2d5bff017fd08d32521d39d5b08 Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 2 Aug 2018 11:54:48 -0500 Subject: Add loose file/archive conflict icons --- src/mainwindow.cpp | 9 +++ src/modflagicondelegate.cpp | 6 +- src/modinfo.h | 14 ++++- src/modinfowithconflictinfo.cpp | 86 ++++++++++++++++++++-------- src/modinfowithconflictinfo.h | 16 +++++- src/modlist.cpp | 42 +++++++++----- src/modlist.h | 3 + src/modlistsortproxy.cpp | 16 +++--- src/resources.qrc | 10 ++-- src/resources/conflict-mixed-blue.png | Bin 0 -> 692 bytes src/resources/conflict-overwritten-blue.png | Bin 0 -> 577 bytes 11 files changed, 151 insertions(+), 51 deletions(-) create mode 100644 src/resources/conflict-mixed-blue.png create mode 100644 src/resources/conflict-overwritten-blue.png (limited to 'src/modinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 832f42d0..d7ae18fb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2202,10 +2202,17 @@ void MainWindow::modorder_changed() for (int i : modInfo->getModArchiveOverwritten()) { ModInfo::getByIndex(i)->clearCaches(); } + for (int i : modInfo->getModArchiveLooseOverwrite()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModArchiveLooseOverwritten()) { + ModInfo::getByIndex(i)->clearCaches(); + } // update conflict check on the moved mod modInfo->doConflictCheck(); m_OrganizerCore.modList()->setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); m_OrganizerCore.modList()->setArchiveOverwriteMarkers(modInfo->getModArchiveOverwrite(), modInfo->getModArchiveOverwritten()); + m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(modInfo->getModArchiveLooseOverwrite(), modInfo->getModArchiveLooseOverwritten()); if (m_ModListSortProxy != nullptr) { m_ModListSortProxy->invalidate(); } @@ -2442,9 +2449,11 @@ void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QMode ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); m_OrganizerCore.modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); m_OrganizerCore.modList()->setArchiveOverwriteMarkers(selectedMod->getModArchiveOverwrite(), selectedMod->getModArchiveOverwritten()); + m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(selectedMod->getModArchiveLooseOverwrite(), selectedMod->getModArchiveLooseOverwritten()); } else { m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); m_OrganizerCore.modList()->setArchiveOverwriteMarkers(std::set(), std::set()); + m_OrganizerCore.modList()->setArchiveLooseOverwriteMarkers(std::set(), std::set()); } /* if ((m_ModListSortProxy != nullptr) && !m_ModListSortProxy->beingInvalidated()) { diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index f7f85a18..5584dea1 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -1,4 +1,4 @@ - #include "modflagicondelegate.h" +#include "modflagicondelegate.h" #include @@ -44,15 +44,19 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const case ModInfo::FLAG_INVALID: return ":/MO/gui/problem"; case ModInfo::FLAG_NOTENDORSED: return ":/MO/gui/emblem_notendorsed"; case ModInfo::FLAG_NOTES: return ":/MO/gui/emblem_notes"; + case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; case ModInfo::FLAG_CONFLICT_OVERWRITE: return ":/MO/gui/emblem_conflict_overwrite"; case ModInfo::FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE: return ":/MO/gui/emblem_conflict_loose_overwrite_archive"; case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return ":/MO/gui/emblem_conflict_overwritten"; case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; case ModInfo::FLAG_CONFLICT_REDUNDANT: return ":MO/gui/emblem_conflict_redundant"; case ModInfo::FLAG_ALTERNATE_GAME: return ":MO/gui/alternate_game"; + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return ":/MO/gui/archive_loose_conflict_overwrite"; + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return ":/MO/gui/archive_loose_conflict_overwritten"; case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return ":/MO/gui/archive_conflict_mixed"; case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return ":/MO/gui/archive_conflict_winner"; case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return ":/MO/gui/archive_conflict_loser"; + case ModInfo::FLAG_ALTERNATE_GAME: return ":MO/gui/alternate_game"; default: return QString(); } } diff --git a/src/modinfo.h b/src/modinfo.h index c763e69b..99ce35b5 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -69,10 +69,12 @@ public: FLAG_NOTENDORSED, FLAG_NOTES, FLAG_CONFLICT_OVERWRITE, - FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE, FLAG_CONFLICT_OVERWRITTEN, FLAG_CONFLICT_MIXED, FLAG_CONFLICT_REDUNDANT, + FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, + FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, + FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED, FLAG_ARCHIVE_CONFLICT_OVERWRITE, FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, FLAG_ARCHIVE_CONFLICT_MIXED, @@ -648,6 +650,16 @@ public: */ virtual std::set getModArchiveOverwritten() { return std::set(); } + /** + * @return retrieve list of mods (as mod index) with archives that are overwritten by thos mod's loose files. Updates may be delayed + */ + virtual std::set getModArchiveLooseOverwrite() { return std::set(); } + + /** + * @return list of mods (as mod index) with loose files that overwrite this one's archive files. Updates may be delayed + */ + virtual std::set getModArchiveLooseOverwritten() { return std::set(); } + /** * @brief update conflict information */ diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 67006261..68b5b265 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -7,7 +7,7 @@ using namespace MOBase; using namespace MOShared; ModInfoWithConflictInfo::ModInfoWithConflictInfo(PluginContainer *pluginContainer, DirectoryEntry **directoryStructure) - : ModInfo(pluginContainer), m_DirectoryStructure(directoryStructure) {} + : ModInfo(pluginContainer), m_DirectoryStructure(directoryStructure), m_HasLooseOverwrite(false) {} void ModInfoWithConflictInfo::clearCaches() { @@ -32,6 +32,18 @@ std::vector ModInfoWithConflictInfo::getFlags() const } break; default: { /* NOP */ } } + switch (isLooseArchiveConflicted()) { + case CONFLICT_MIXED: { + result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED); + } break; + case CONFLICT_OVERWRITE: { + result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); + } break; + case CONFLICT_OVERWRITTEN: { + result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); + } break; + default: { /* NOP */ } + } switch (isArchiveConflicted()) { case CONFLICT_MIXED: { result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED); @@ -54,6 +66,8 @@ void ModInfoWithConflictInfo::doConflictCheck() const m_OverwrittenList.clear(); m_ArchiveOverwriteList.clear(); m_ArchiveOverwrittenList.clear(); + m_ArchiveLooseOverwriteList.clear(); + m_ArchiveLooseOverwrittenList.clear(); bool providesAnything = false; @@ -66,6 +80,7 @@ void ModInfoWithConflictInfo::doConflictCheck() const m_CurrentConflictState = CONFLICT_NONE; m_ArchiveConflictState = CONFLICT_NONE; + m_ArchiveConflictLooseState = CONFLICT_NONE; if ((*m_DirectoryStructure)->originExists(name)) { FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); @@ -77,18 +92,7 @@ void ModInfoWithConflictInfo::doConflictCheck() const // no alternatives -> no conflict providesAnything = true; } else { - if (file->getOrigin() != origin.getID()) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(file->getOrigin()); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - if (file->getArchive().first.size() == 0) - m_OverwrittenList.insert(altIndex); - else - m_ArchiveOverwrittenList.insert(altIndex); - } else { - providesAnything = true; - } - - // for all non-providing alternative origins + // Get the archive data for the current mod bool found = file->getOrigin() == origin.getID(); std::pair archiveData; if (found) @@ -101,6 +105,23 @@ void ModInfoWithConflictInfo::doConflictCheck() const } } } + + // If this is not the origin then determine the correct overwrite + if (file->getOrigin() != origin.getID()) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(file->getOrigin()); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + if (file->getArchive().first.size() == 0) + if (archiveData.first.size() == 0) + m_OverwrittenList.insert(altIndex); + else + m_ArchiveLooseOverwrittenList.insert(altIndex); + else + m_ArchiveOverwrittenList.insert(altIndex); + } else { + providesAnything = true; + } + + // Sort out the alternatives for (auto altInfo : alternatives) { if ((altInfo.first != dataID) && (altInfo.first != origin.getID())) { FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altInfo.first); @@ -113,11 +134,11 @@ void ModInfoWithConflictInfo::doConflictCheck() const m_OverwrittenList.insert(altIndex); } } else { - m_ArchiveOverwrittenList.insert(altIndex); + m_ArchiveLooseOverwrittenList.insert(altIndex); } } else { if (archiveData.first.size() == 0) { - m_ArchiveOverwrittenList.insert(altIndex); + m_ArchiveLooseOverwriteList.insert(altIndex); } else { if (archiveData.second > altInfo.second.second) { m_ArchiveOverwriteList.insert(altIndex); @@ -141,14 +162,21 @@ void ModInfoWithConflictInfo::doConflictCheck() const m_CurrentConflictState = CONFLICT_OVERWRITE; else if (!m_OverwrittenList.empty()) m_CurrentConflictState = CONFLICT_OVERWRITTEN; - } - if (!m_ArchiveOverwriteList.empty() && !m_ArchiveOverwrittenList.empty()) + if (!m_ArchiveOverwriteList.empty() && !m_ArchiveOverwrittenList.empty()) m_ArchiveConflictState = CONFLICT_MIXED; - else if (!m_ArchiveOverwriteList.empty()) + else if (!m_ArchiveOverwriteList.empty()) m_ArchiveConflictState = CONFLICT_OVERWRITE; - else if (!m_ArchiveOverwrittenList.empty()) + else if (!m_ArchiveOverwrittenList.empty()) m_ArchiveConflictState = CONFLICT_OVERWRITTEN; + + if (!m_ArchiveLooseOverwrittenList.empty() && !m_ArchiveLooseOverwriteList.empty()) + m_ArchiveConflictLooseState = CONFLICT_MIXED; + else if (!m_ArchiveLooseOverwrittenList.empty()) + m_ArchiveConflictLooseState = CONFLICT_OVERWRITTEN; + else if (!m_ArchiveLooseOverwriteList.empty()) + m_ArchiveConflictLooseState = CONFLICT_OVERWRITE; + } } } @@ -165,12 +193,22 @@ ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() c ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isArchiveConflicted() const { - QTime now = QTime::currentTime(); - if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { - doConflictCheck(); - } + QTime now = QTime::currentTime(); + if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { + doConflictCheck(); + } + + return m_ArchiveConflictState; +} + +ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isLooseArchiveConflicted() const +{ + QTime now = QTime::currentTime(); + if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { + doConflictCheck(); + } - return m_ArchiveConflictState; + return m_ArchiveConflictLooseState; } diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h index 5cf2d24e..13d87d25 100644 --- a/src/modinfowithconflictinfo.h +++ b/src/modinfowithconflictinfo.h @@ -27,6 +27,10 @@ public: virtual std::set getModArchiveOverwritten() { return m_ArchiveOverwrittenList; } + virtual std::set getModArchiveLooseOverwrite() { return m_ArchiveLooseOverwriteList; } + + virtual std::set getModArchiveLooseOverwritten() { return m_ArchiveLooseOverwrittenList; } + virtual void doConflictCheck() const; private: @@ -36,7 +40,8 @@ private: CONFLICT_OVERWRITE, CONFLICT_OVERWRITTEN, CONFLICT_MIXED, - CONFLICT_REDUNDANT + CONFLICT_REDUNDANT, + CONFLICT_CROSS }; private: @@ -51,6 +56,11 @@ private: */ EConflictType isArchiveConflicted() const; + /** + * @return true if there are archive conflicts with loose files in this mod + */ + EConflictType isLooseArchiveConflicted() const; + /** * @return true if this mod is completely replaced by others */ @@ -62,12 +72,16 @@ private: mutable EConflictType m_CurrentConflictState; mutable EConflictType m_ArchiveConflictState; + mutable EConflictType m_ArchiveConflictLooseState; + mutable bool m_HasLooseOverwrite; mutable QTime m_LastConflictCheck; mutable std::set m_OverwriteList; // indices of mods overritten by this mod mutable std::set m_OverwrittenList; // indices of mods overwriting this mod mutable std::set m_ArchiveOverwriteList; // indices of mods with archive files overritten by this mod mutable std::set m_ArchiveOverwrittenList; // indices of mods with archive files overwriting this mod + mutable std::set m_ArchiveLooseOverwriteList; // indices of mods with archives being overwritten by this mod's loose files + mutable std::set m_ArchiveLooseOverwrittenList; // indices of mods with loose files overwriting this mod's archive files }; diff --git a/src/modlist.cpp b/src/modlist.cpp index 876e8958..fd6da43b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -163,10 +163,13 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); - case ModInfo::FLAG_ALTERNATE_GAME: return tr("Alternate game source"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED: return tr("Contains archive files overwritten by loose files and loose files overwriting an archive"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file"); case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); + case ModInfo::FLAG_ALTERNATE_GAME: return tr("Alternate game source"); default: return ""; } } @@ -403,20 +406,26 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } else if ((role == Qt::BackgroundRole) || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + bool overwrite = m_Overwrite.find(modIndex) != m_Overwrite.end(); + bool archiveOverwrite = m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end(); + bool archiveLooseOverwrite = m_ArchiveLooseOverwrite.find(modIndex) != m_ArchiveLooseOverwrite.end(); + bool overwritten = m_Overwritten.find(modIndex) != m_Overwritten.end(); + bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end(); + bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end(); if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { return Settings::instance().modlistContainsPluginColor(); - } else if (m_Overwrite.find(modIndex) != m_Overwrite.end() && m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end()) { - return QColor(255, 0, 255, 32); //TODO: Make configurable - } else if (m_Overwrite.find(modIndex) != m_Overwrite.end() && m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end()) { - return QColor(255, 0, 255, 32); //TODO: Make configurable - } else if (m_Overwritten.find(modIndex) != m_Overwritten.end() && m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end()) { + } else if ((overwrite && (archiveOverwritten || archiveLooseOverwritten)) || + (overwritten && (archiveOverwrite || archiveLooseOverwrite)) || + (archiveOverwrite && (overwritten || archiveLooseOverwritten)) || + (archiveOverwritten && (overwrite || archiveLooseOverwrite)) || + (archiveLooseOverwrite && (overwritten || archiveOverwritten)) || + (archiveLooseOverwritten && (overwrite || archiveLooseOverwrite)) + ) { return QColor(255, 0, 255, 32); //TODO: Make configurable - } else if (m_Overwritten.find(modIndex) != m_Overwritten.end() && m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end()) { - return QColor(255, 0, 255, 32); //TODO: Make configurable - } else if (m_Overwrite.find(modIndex) != m_Overwrite.end() || m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end()) { + } else if (overwrite || archiveOverwrite || archiveLooseOverwrite) { return Settings::instance().modlistOverwrittenLooseColor(); } - } else if (m_Overwritten.find(modIndex) != m_Overwritten.end() || m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end()) { + } else if (overwritten || archiveOverwritten || archiveLooseOverwritten) { return Settings::instance().modlistOverwritingLooseColor(); } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid() @@ -767,9 +776,16 @@ void ModList::setOverwriteMarkers(const std::set &overwrite, const void ModList::setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) { - m_ArchiveOverwrite = overwrite; - m_ArchiveOverwritten = overwritten; - notifyChange(0, rowCount() - 1); + m_ArchiveOverwrite = overwrite; + m_ArchiveOverwritten = overwritten; + notifyChange(0, rowCount() - 1); +} + +void ModList::setArchiveLooseOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) +{ + m_ArchiveLooseOverwrite = overwrite; + m_ArchiveLooseOverwritten = overwritten; + notifyChange(0, rowCount() - 1); } void ModList::setPluginContainer(PluginContainer *pluginContianer) diff --git a/src/modlist.h b/src/modlist.h index 5841b9f2..a8263a76 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -113,6 +113,7 @@ public: void setPluginContainer(PluginContainer *pluginContainer); void setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); + void setArchiveLooseOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); void setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); @@ -337,6 +338,8 @@ private: std::set m_Overwritten; std::set m_ArchiveOverwrite; std::set m_ArchiveOverwritten; + std::set m_ArchiveLooseOverwrite; + std::set m_ArchiveLooseOverwritten; TModInfoChange m_ChangeInfo; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index c34985b6..88a073e0 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -238,13 +238,15 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) { for (ModInfo::EFlag flag : flags) { if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || - (flag == ModInfo::FLAG_CONFLICT_REDUNDANT) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED) || - (flag == ModInfo::FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE)) { + (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || + (flag == ModInfo::FLAG_CONFLICT_REDUNDANT) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN)) { return true; } } diff --git a/src/resources.qrc b/src/resources.qrc index dff1ad72..9e4c9192 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -47,8 +47,10 @@ resources/conflict-mixed.png resources/conflict-overwrite.png resources/conflict-overwritten.png - resources/conflict-redundant.png - resources/conflict-overwrite-blue.png + resources/conflict-redundant.png + resources/conflict-mixed-blue.png + resources/conflict-overwrite-blue.png + resources/conflict-overwritten-blue.png resources/accessories-text-editor.png resources/x-office-calendar.png resources/dialog-warning_16.png @@ -94,8 +96,8 @@ resources/contents/conversation.png resources/contents/locked-chest.png resources/contents/config.png - resources/contents/feather-and-scroll.png - resources/contents/xedit.png + resources/contents/feather-and-scroll.png + resources/contents/xedit.png qt.conf diff --git a/src/resources/conflict-mixed-blue.png b/src/resources/conflict-mixed-blue.png new file mode 100644 index 00000000..2afbb304 Binary files /dev/null and b/src/resources/conflict-mixed-blue.png differ diff --git a/src/resources/conflict-overwritten-blue.png b/src/resources/conflict-overwritten-blue.png new file mode 100644 index 00000000..3a032a55 Binary files /dev/null and b/src/resources/conflict-overwritten-blue.png differ -- cgit v1.3.1 From 49159de5d7084bf1ac159e3e37e2b406cdd87dbf Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 14 Aug 2018 11:35:20 -0500 Subject: Improved refresh performance by replacing std::transform() with CharLowerBuffer(). Fixed padding to four spaces and removed references to FLAG_ARCHIVE_LOOSE_CONFLICTS_MIXED that isn't used. --- src/modflagicondelegate.cpp | 32 +++++++++++++++++++------------- src/modflagicondelegate.h | 2 +- src/modinfo.h | 1 - src/modlist.cpp | 1 - src/modlistsortproxy.cpp | 1 - src/shared/util.cpp | 12 ++++++++---- 6 files changed, 28 insertions(+), 21 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index a0a0a86d..48d71313 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -7,8 +7,7 @@ ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLIC , ModInfo::FLAG_CONFLICT_OVERWRITTEN , ModInfo::FLAG_CONFLICT_REDUNDANT }; -ModInfo::EFlag ModFlagIconDelegate::m_ArchiveLooseConflictFlags[3] = { ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED - , ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE +ModInfo::EFlag ModFlagIconDelegate::m_ArchiveLooseConflictFlags[2] = { ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE , ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN }; ModInfo::EFlag ModFlagIconDelegate::m_ArchiveConflictFlags[3] = { ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED @@ -39,26 +38,33 @@ QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const { } } - { // insert archive vs loose conflicts second - int pad = 2; + { // insert loose vs archive overwrite second + auto iter = std::find(flags.begin(), flags.end(), + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } + else { + result.append(QString()); + } + } + + { // insert loose vs archive overwritten third auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveLooseConflictFlags, m_ArchiveLooseConflictFlags + 4); - while (iter != flags.end()) { + m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2); + if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); - pad--; - iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveLooseConflictFlags, m_ArchiveLooseConflictFlags + 4); } - while (pad > 0) { + else { result.append(QString()); - pad--; } } { // insert archive conflicts last auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveConflictFlags, m_ArchiveConflictFlags + 4); + m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3); if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); @@ -87,7 +93,7 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return ":/MO/gui/emblem_conflict_overwritten"; case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; case ModInfo::FLAG_CONFLICT_REDUNDANT: return ":MO/gui/emblem_conflict_redundant"; - case ModInfo::FLAG_ALTERNATE_GAME: return ":MO/gui/alternate_game"; + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED: return ":/MO/gui/archive_loose_conflict_mixed"; case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return ":/MO/gui/archive_loose_conflict_overwrite"; case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return ":/MO/gui/archive_loose_conflict_overwritten"; case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return ":/MO/gui/archive_conflict_mixed"; diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h index 7324d516..0f257c7d 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -15,7 +15,7 @@ private: QString getFlagIcon(ModInfo::EFlag flag) const; private: static ModInfo::EFlag m_ConflictFlags[4]; - static ModInfo::EFlag m_ArchiveLooseConflictFlags[3]; + static ModInfo::EFlag m_ArchiveLooseConflictFlags[2]; static ModInfo::EFlag m_ArchiveConflictFlags[3]; }; diff --git a/src/modinfo.h b/src/modinfo.h index 99ce35b5..00859209 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -74,7 +74,6 @@ public: FLAG_CONFLICT_REDUNDANT, FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, - FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED, FLAG_ARCHIVE_CONFLICT_OVERWRITE, FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, FLAG_ARCHIVE_CONFLICT_MIXED, diff --git a/src/modlist.cpp b/src/modlist.cpp index fd6da43b..8c070667 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -163,7 +163,6 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED: return tr("Contains archive files overwritten by loose files and loose files overwriting an archive"); case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 88a073e0..8cf4bb9c 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -244,7 +244,6 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE) || (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN) || (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED) || - (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED) || (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE) || (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN)) { return true; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 102565f5..b65ed071 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -108,27 +108,31 @@ static auto locToLower = [] (char in) -> char { std::string &ToLower(std::string &text) { - std::transform(text.begin(), text.end(), text.begin(), locToLower); + //std::transform(text.begin(), text.end(), text.begin(), locToLower); + CharLowerBuffA(const_cast(text.c_str()), static_cast(text.size())); return text; } std::string ToLower(const std::string &text) { std::string result(text); - std::transform(result.begin(), result.end(), result.begin(), locToLower); + //std::transform(result.begin(), result.end(), result.begin(), locToLower); + CharLowerBuffA(const_cast(result.c_str()), static_cast(result.size())); return result; } std::wstring &ToLower(std::wstring &text) { - std::transform(text.begin(), text.end(), text.begin(), locToLowerW); + //std::transform(text.begin(), text.end(), text.begin(), locToLowerW); + CharLowerBuffW(const_cast(text.c_str()), static_cast(text.size())); return text; } std::wstring ToLower(const std::wstring &text) { std::wstring result(text); - std::transform(result.begin(), result.end(), result.begin(), locToLowerW); + //std::transform(result.begin(), result.end(), result.begin(), locToLowerW); + CharLowerBuffW(const_cast(result.c_str()), static_cast(result.size())); return result; } -- cgit v1.3.1 From 4436d376a8d426867f217b03838570a424b09c5f Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 12 Dec 2018 20:26:35 -0600 Subject: Clean up and fix merge issues --- src/directoryrefresher.cpp | 3 +- src/mainwindow.cpp | 77 ++- src/mainwindow.ui | 15 +- src/modflagicondelegate.cpp | 3 - src/modinfo.h | 10 - src/modinfodialog.h | 3 +- src/modinfowithconflictinfo.cpp | 2 +- src/modlist.cpp | 18 +- src/modlist.h | 2 - src/organizer_en.ts | 1124 ++++++++++++++++++++------------------- src/organizercore.cpp | 18 +- src/settings.cpp | 4 +- 12 files changed, 652 insertions(+), 627 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index 82eb093b..e6e6a8e0 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -85,7 +85,6 @@ void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure int priority, const QString &directory, const QStringList &archives) { std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); - //QStringList loadOrder = QStringList(); IPluginGame *game = qApp->property("managed_game").value(); GamePlugins *gamePlugins = game->feature(); @@ -95,7 +94,7 @@ void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure for (const QString &archive : archives) { QFileInfo fileInfo(archive); if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) { - + int order = -1; for (auto plugin : loadOrder) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cdd53dc9..73a40f46 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -313,8 +313,7 @@ MainWindow::MainWindow(QSettings &initSettings ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(ui->espList)); ui->espList->installEventFilter(m_OrganizerCore.pluginList()); - //bsaList converted to normal QTreeWidget since we can't sort BSAs anymore. - //ui->bsaList->setLocalMoveOnly(true); + ui->bsaList->setLocalMoveOnly(true); bool pluginListAdjusted = registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header()); @@ -451,15 +450,15 @@ MainWindow::MainWindow(QSettings &initSettings if (m_OrganizerCore.getArchiveParsing()) { - ui->showArchiveDataCheckBox->setCheckState(Qt::Checked); - ui->showArchiveDataCheckBox->setEnabled(true); - m_showArchiveData = true; + ui->showArchiveDataCheckBox->setCheckState(Qt::Checked); + ui->showArchiveDataCheckBox->setEnabled(true); + m_showArchiveData = true; } else { - ui->showArchiveDataCheckBox->setCheckState(Qt::Unchecked); - ui->showArchiveDataCheckBox->setEnabled(false); - m_showArchiveData = false; + ui->showArchiveDataCheckBox->setCheckState(Qt::Unchecked); + ui->showArchiveDataCheckBox->setEnabled(false); + m_showArchiveData = false; } refreshExecutablesList(); @@ -1230,7 +1229,7 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director directoryChild->setData(0, Qt::DecorationRole, (new QFileIconProvider())->icon(QFileIconProvider::Folder)); directoryChild->setData(0, Qt::UserRole + 3, isDirectory); - if (conflictsOnly) { + if (conflictsOnly || !m_showArchiveData) { updateTo(directoryChild, temp.str(), **current, conflictsOnly); if (directoryChild->childCount() != 0) { subTree->addChild(directoryChild); @@ -1264,9 +1263,9 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director if (conflictsOnly && (current->getAlternatives().size() == 0)) { continue; } - + bool isArchive = false; - int originID = current->getOrigin(isArchive); + int originID = current->getOrigin(isArchive); if (!m_showArchiveData && isArchive) { continue; } @@ -2304,10 +2303,10 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; WIN32_FIND_DATAW findData; - HANDLE hFind; - hFind = ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData); + HANDLE hFind; + hFind = ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData); filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L"", -1); - FindClose(hFind); + FindClose(hFind); } if (m_OrganizerCore.directoryStructure()->originExists(ToWString(oldOriginName))) { FilesOrigin &oldOrigin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldOriginName)); @@ -4372,22 +4371,22 @@ void MainWindow::on_actionSettings_triggered() const auto state = settings.archiveParsing(); if (state != m_OrganizerCore.getArchiveParsing()) { - m_OrganizerCore.setArchiveParsing(state); - if (!state) - { - ui->showArchiveDataCheckBox->setCheckState(Qt::Unchecked); - ui->showArchiveDataCheckBox->setEnabled(false); - m_showArchiveData = false; - } - else - { - ui->showArchiveDataCheckBox->setCheckState(Qt::Checked); - ui->showArchiveDataCheckBox->setEnabled(true); - m_showArchiveData = true; - } - m_OrganizerCore.refreshModList(); - m_OrganizerCore.refreshDirectoryStructure(); - m_OrganizerCore.refreshLists(); + m_OrganizerCore.setArchiveParsing(state); + if (!state) + { + ui->showArchiveDataCheckBox->setCheckState(Qt::Unchecked); + ui->showArchiveDataCheckBox->setEnabled(false); + m_showArchiveData = false; + } + else + { + ui->showArchiveDataCheckBox->setCheckState(Qt::Checked); + ui->showArchiveDataCheckBox->setEnabled(true); + m_showArchiveData = true; + } + m_OrganizerCore.refreshModList(); + m_OrganizerCore.refreshDirectoryStructure(); + m_OrganizerCore.refreshLists(); } if (settings.getCacheDirectory() != oldCacheDirectory) { @@ -6027,14 +6026,14 @@ void MainWindow::sendSelectedModsToSeparator_clicked() void MainWindow::on_showArchiveDataCheckBox_toggled(const bool checked) { - if (m_OrganizerCore.getArchiveParsing() && checked) - { - m_showArchiveData = checked; - } - else - { - m_showArchiveData = false; - } - refreshDataTree(); + if (m_OrganizerCore.getArchiveParsing() && checked) + { + m_showArchiveData = checked; + } + else + { + m_showArchiveData = false; + } + refreshDataTree(); } diff --git a/src/mainwindow.ui b/src/mainwindow.ui index eb88a855..c6b43be0 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -831,6 +831,9 @@ p, li { white-space: pre-wrap; } + + true + Sort @@ -1045,7 +1048,7 @@ p, li { white-space: pre-wrap; } - + Qt::CustomContextMenu @@ -1061,19 +1064,19 @@ p, li { white-space: pre-wrap; } false - + false - + false - + 20 - + true - + 1 diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index 48d71313..eb6945c4 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -89,11 +89,8 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const case ModInfo::FLAG_NOTES: return ":/MO/gui/emblem_notes"; case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; case ModInfo::FLAG_CONFLICT_OVERWRITE: return ":/MO/gui/emblem_conflict_overwrite"; - case ModInfo::FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE: return ":/MO/gui/emblem_conflict_loose_overwrite_archive"; case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return ":/MO/gui/emblem_conflict_overwritten"; - case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; case ModInfo::FLAG_CONFLICT_REDUNDANT: return ":MO/gui/emblem_conflict_redundant"; - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_MIXED: return ":/MO/gui/archive_loose_conflict_mixed"; case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return ":/MO/gui/archive_loose_conflict_overwrite"; case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return ":/MO/gui/archive_loose_conflict_overwritten"; case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return ":/MO/gui/archive_conflict_mixed"; diff --git a/src/modinfo.h b/src/modinfo.h index 00859209..350b3a0d 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -639,16 +639,6 @@ public: */ virtual std::set getModArchiveOverwritten() { return std::set(); } - /** - * @return retrieve list of mods (as mod index) with archives that are overwritten by this one. Updates may be delayed - */ - virtual std::set getModArchiveOverwrite() { return std::set(); } - - /** - * @return list of mods (as mod index) with archives that overwrite this one. Updates may be delayed - */ - virtual std::set getModArchiveOverwritten() { return std::set(); } - /** * @return retrieve list of mods (as mod index) with archives that are overwritten by thos mod's loose files. Updates may be delayed */ diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6a315a93..52768649 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -78,7 +78,8 @@ public: * @param modInfo info structure about the mod to display * @param parent parend widget **/ - explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent = 0); + explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent = 0); + ~ModInfoDialog(); /** diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 79896cc3..12cbf416 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -35,7 +35,7 @@ std::vector ModInfoWithConflictInfo::getFlags() const switch (isLooseArchiveConflicted()) { case CONFLICT_MIXED: { result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); - result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); + result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); } break; case CONFLICT_OVERWRITE: { result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); diff --git a/src/modlist.cpp b/src/modlist.cpp index 8c070667..757fedb2 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -159,7 +159,6 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const return output.join("
"); } case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); - case ModInfo::FLAG_CONFLICT_LOOSE_OVERWRITE_ARCHIVE: return tr("Loose files overwrite another archive"); case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); @@ -420,13 +419,13 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const (archiveLooseOverwrite && (overwritten || archiveOverwritten)) || (archiveLooseOverwritten && (overwrite || archiveLooseOverwrite)) ) { - return QColor(255, 0, 255, 32); //TODO: Make configurable - } else if (overwrite || archiveOverwrite || archiveLooseOverwrite) { return Settings::instance().modlistOverwrittenLooseColor(); } - } else if (overwritten || archiveOverwritten || archiveLooseOverwritten) { + else if (overwrite || archiveOverwrite || archiveLooseOverwrite) { return Settings::instance().modlistOverwritingLooseColor(); - } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) + } else if (overwritten || archiveOverwritten || archiveLooseOverwritten) { + return QColor(255, 0, 0, 64); //TODO: Make configurable + } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid() && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) || Settings::instance().colorSeparatorScrollbar())) { @@ -789,14 +788,7 @@ void ModList::setArchiveLooseOverwriteMarkers(const std::set &over void ModList::setPluginContainer(PluginContainer *pluginContianer) { - m_PluginContainer = pluginContianer; -} - -void ModList::setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) -{ - m_ArchiveOverwrite = overwrite; - m_ArchiveOverwritten = overwritten; - notifyChange(0, rowCount() - 1); + m_PluginContainer = pluginContianer; } bool ModList::modInfoAboutToChange(ModInfo::Ptr info) diff --git a/src/modlist.h b/src/modlist.h index a8263a76..49627d68 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -115,8 +115,6 @@ public: void setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); void setArchiveLooseOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); - void setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); - bool modInfoAboutToChange(ModInfo::Ptr info); void modInfoChanged(ModInfo::Ptr info); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 44a386d6..550f7d5c 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -279,12 +279,12 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to parse bsa %1: %2 - + failed to read mod (%1): %2 @@ -1620,7 +1620,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1657,7 +1657,7 @@ p, li { white-space: pre-wrap; } - + Namefilter @@ -1783,9 +1783,9 @@ p, li { white-space: pre-wrap; } - - - + + + Refresh @@ -1805,23 +1805,34 @@ p, li { white-space: pre-wrap; } - - - Filter the above list so that only conflicts are displayed. + + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - + + + Filters the above list so that files from archives are not shown + + + + + Show files from Archives + + + + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1832,160 +1843,160 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Tool Bar - + Install Mod - + Install &Mod - + Install a new mod from an archive - + Ctrl+M - + Profiles - + &Profiles - + Configure Profiles - + Ctrl+P - + Executables - + &Executables - + Configure the executables that can be started through Mod Organizer - + Ctrl+E - - + + Tools - + &Tools - + Ctrl+I - + Settings - + &Settings - + Configure settings and workarounds - + Ctrl+S - + Nexus - + Search nexus network for more mods - + Ctrl+N - - + + Update - + Mod Organizer is up-to-date - - + + No Problems - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1993,830 +2004,830 @@ Right now this has very limited functionality - - + + Help - + Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Change Game - + Open the Instance selection dialog to manage a different Game - + Toolbar - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Problems - + There are potential problems with your setup - + Everything seems to be in order - + Help on UI - + Documentation Wiki - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previous version (%2).<br>The GUI may not downgrade gracefully, so you may experience oddities.<br>However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + You need to be logged in with Nexus to resume a download - - + + You need to be logged in with Nexus to endorse - + Failed to display overwrite dialog: %1 - + Nexus ID for this Mod is unknown - + Web page for this mod is unknown - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - - + + Are you sure? - + About to recursively delete: - + Not logged in, endorsement information will be wrong - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check all for update - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - - + + Endorse - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit web page - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -2824,12 +2835,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2837,22 +2848,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -2860,335 +2871,335 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Select binary - + Binary - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3649,243 +3660,243 @@ p, li { white-space: pre-wrap; } - - + + Save changes? - - + + Save changes to "%1"? - + File Exists - + A file with that name exists, please enter a new one - + failed to move file - + failed to create directory "optional" - - + + Info requested, please wait - + Main - + Update - + Optional - + Old - + Misc - + Unknown - + Current Version: %1 - + No update available - + (description incomplete, please visit nexus) - + <a href="%1">Visit on Nexus</a> - + Failed to delete %1 - - - - + + + + Confirm - - + + Are sure you want to delete "%1"? - - + + Are sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - - + + failed to rename %1 to %2 - + There already is a visible version of this file. Replace it? - + Select binary - + Binary - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Un-Hide - + Hide - - + + Open/Execute - - + + Preview - + Name - + Please enter a name - - + + Error - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -4037,182 +4048,197 @@ p, li { white-space: pre-wrap; } - Overwrites files + Overwrites loose files - Overwritten files + Loose files overwrite another archive - Overwrites & Overwritten + Overwritten loose files - Redundant + Loose files Overwrites & Overwritten - Alternate game source + Redundant - - Non-MO + + Overwrites an archive with loose files - - invalid + + Archive is overwritten by loose files - - installed version: "%1", newest version: "%2" + + Overwrites another archive file - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + + Overwritten by another archive file - - Categories: <br> + + Archive files overwrites & overwritten + + + + + Alternate game source + + + + + Non-MO - + + invalid + + + + Invalid name - + Name is already in use by another mod - + drag&drop failed: %1 - + Confirm - + Are you sure you want to remove "%1"? - + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + Source Game - + Nexus ID - + Installation - + Notes - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Depicts the content of the mod:<br><table cellspacing=7><tr><td><img src=":/MO/gui/content/plugin" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr><tr><td><img src=":/MO/gui/content/interface" width=32/></td><td>Interface</td></tr><tr><td><img src=":/MO/gui/content/mesh" width=32/></td><td>Meshes</td></tr><tr><td><img src=":/MO/gui/content/bsa" width=32/></td><td>BSA</td></tr><tr><td><img src=":/MO/gui/content/texture" width=32/></td><td>Textures</td></tr><tr><td><img src=":/MO/gui/content/sound" width=32/></td><td>Sounds</td></tr><tr><td><img src=":/MO/gui/content/music" width=32/></td><td>Music</td></tr><tr><td><img src=":/MO/gui/content/string" width=32/></td><td>Strings</td></tr><tr><td><img src=":/MO/gui/content/script" width=32/></td><td>Scripts (Papyrus)</td></tr><tr><td><img src=":/MO/gui/content/skse" width=32/></td><td>Script Extender plugins</td></tr><tr><td><img src=":/MO/gui/content/skyproc" width=32/></td><td>SkyProc Patcher</td></tr><tr><td><img src=":/MO/gui/content/menu" width=32/></td><td>Mod Configuration Menu</td></tr><tr><td><img src=":/MO/gui/content/inifile" width=32/></td><td>INI files</td></tr><tr><td><img src=":/MO/gui/content/modgroup" width=32/></td><td>ModGroup files</td></tr></table> - + Time this mod was installed - + User notes about the mod @@ -4220,7 +4246,7 @@ p, li { white-space: pre-wrap; } ModListSortProxy - + Drag&Drop is only supported when sorting by priority @@ -4350,171 +4376,171 @@ p, li { white-space: pre-wrap; } - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod "%1" not found - - + + Installation cancelled - - + + The mod was not installed completely. - + Executable "%1" not found - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Error - + Windows Event Log Error - + The Windows Event Log service is disabled and/or not running. This prevents USVFS from running properly. Your mods may not be working in the executable that you are launching. Note that you may have to restart MO and/or your PC after the service is fixed. Continue launching %1? - + Blacklisted Executable - + The executable you are attempted to launch is blacklisted in the virtual file system. This will likely prevent the executable, and any executables that are launched by this one, from seeing any mods. This could extend to INI files, save games and any other virtualized files. Continue launching %1? - + No profile set - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. @@ -4661,94 +4687,94 @@ Continue? - + failed to update esp info for file %1 (source id: %2), error: %3 - + esp not found: %1 - - + + Confirm - + Really enable all plugins? - + Really disable all plugins? - + The file containing locked plugin indices is broken - - + + <b>Origin</b>: %1 - + <br><b><i>This plugin can't be disabled (enforced by the game).</i></b> - + Author - + Description - + Missing Masters - + Enabled Masters - + Loads Archives - + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + Loads INI settings - + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. - + This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. - + failed to restore load order for %1 @@ -5490,7 +5516,7 @@ If the folder was still in use, restart MO and try again. - + Mod Organizer @@ -5505,18 +5531,18 @@ If the folder was still in use, restart MO and try again. - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -5552,12 +5578,12 @@ If the folder was still in use, restart MO and try again. - + failed to access %1 - + failed to set file time %1 @@ -5567,12 +5593,12 @@ If the folder was still in use, restart MO and try again. - + Script Extender - + Proxy DLL @@ -5763,12 +5789,12 @@ Select Show Details option to see the full change-log. - + Failed to start %1: %2 - + Error @@ -5786,28 +5812,28 @@ Select Show Details option to see the full change-log. - - + + attempt to store setting for unknown plugin "%1" - + Error - + Failed to create "%1", you may not have the necessary permission. path remains unchanged. - + Restart Mod Organizer? - + In order to reset the window geometries, MO must be restarted. Restart it now? @@ -6351,75 +6377,90 @@ programs you are intentionally running. + Enable parsing of Archives Archives, has effects on perfromance. + + + - Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. + + By default Mod Organizer will parse Archive files to calculate conflicts between themselves and loose files, this process has a noticeable cost in performance. + This feature should not be confused from the one offered by ModOrganizer 1, ModOrganizer 2 will only show conflicts with archives NOT load them into the game or program. + + If you disable this feature, MO will only display conflicts with Loose files. + - - Reset Window Geometries + + Enable parsing of Archives - - These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. + + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - - Diagnostics + + Reset Window Geometries - - Log Level + + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - - Decides the amount of data printed to "ModOrganizer.log" + + Diagnostics - - - Decides the amount of data printed to "ModOrganizer.log". - "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 regluar use. On the "Error" level the log file usually remains empty. - + + Max Dumps To Keep - - Debug + + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - - Info (recommended) + + + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + Set "Crash Dumps" above to None to disable crash dump collection. + - - Warning + + Hint: right click link and copy link location - - Error + + + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> + and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. + Sending logs and/or crash dumps to the developers can help investigate issues. + It is recommended to compress large log and dmp files before sending. + - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -6430,56 +6471,61 @@ programs you are intentionally running. - + None - + Mini (recommended) - + Data - + Full - - Max Dumps To Keep + + Log Level - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + + Decides the amount of data printed to "ModOrganizer.log" - + - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - Set "Crash Dumps" above to None to disable crash dump collection. + Decides the amount of data printed to "ModOrganizer.log". + "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 regluar use. On the "Error" level the log file usually remains empty. - - Hint: right click link and copy link location + + Debug - - - Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> - and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. - Sending logs and/or crash dumps to the developers can help investigate issues. - It is recommended to compress large log and dmp files before sending. - + + Info (recommended) + + + + + Warning + + + + + Error diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d21fa844..acce25dc 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -788,12 +788,12 @@ std::wstring OrganizerCore::crashDumpsPath() { bool OrganizerCore::getArchiveParsing() const { - return m_ArchiveParsing; + return m_ArchiveParsing; } void OrganizerCore::setArchiveParsing(const bool archiveParsing) { - m_ArchiveParsing = archiveParsing; + m_ArchiveParsing = archiveParsing; } void OrganizerCore::setCurrentProfile(const QString &profileName) @@ -1936,13 +1936,13 @@ std::vector OrganizerCore::enabledArchives() { std::vector result; if (m_ArchiveParsing) { - QFile archiveFile(m_CurrentProfile->getArchivesFileName()); - if (archiveFile.open(QIODevice::ReadOnly)) { - while (!archiveFile.atEnd()) { - result.push_back(QString::fromUtf8(archiveFile.readLine()).trimmed()); - } - archiveFile.close(); - } + QFile archiveFile(m_CurrentProfile->getArchivesFileName()); + if (archiveFile.open(QIODevice::ReadOnly)) { + while (!archiveFile.atEnd()) { + result.push_back(QString::fromUtf8(archiveFile.readLine()).trimmed()); + } + archiveFile.close(); + } } return result; } diff --git a/src/settings.cpp b/src/settings.cpp index eb5b372f..74c95ebf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -476,7 +476,7 @@ uint Settings::getMotDHash() const bool Settings::archiveParsing() const { - return m_Settings.value("Settings/archive_parsing", true).toBool(); + return m_Settings.value("Settings/archive_parsing", true).toBool(); } QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const @@ -1126,7 +1126,7 @@ Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, m_displayForeignBox->setChecked(m_parent->displayForeign()); m_lockGUIBox->setChecked(m_parent->lockGUI()); m_enableArchiveParsingBox->setChecked(m_parent->archiveParsing()); - + m_dialog.setExecutableBlacklist(m_parent->executablesBlacklist()); } -- cgit v1.3.1 From a40613f885c18a8a9f090cd88c595bcb960d97c9 Mon Sep 17 00:00:00 2001 From: Al Date: Tue, 22 Jan 2019 17:58:34 +0100 Subject: Cache list of archives contained in a mod instead of accessing disk each time --- src/modinfo.h | 2 +- src/modinfoforeign.h | 2 +- src/modinfooverwrite.cpp | 2 +- src/modinfooverwrite.h | 2 +- src/modinforegular.cpp | 21 ++++++++++++++------- src/modinforegular.h | 4 +++- 6 files changed, 21 insertions(+), 12 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/modinfo.h b/src/modinfo.h index 5a69185d..b6d7209c 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -534,7 +534,7 @@ public: /** * @return a list of archives belonging to this mod (as absolute file paths) */ - virtual QStringList archives() const = 0; + virtual QStringList archives(bool checkOnDisk = false) = 0; /* *@return the color choosen by the user for the mod/separator diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 93061294..45fe7689 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -49,7 +49,7 @@ public: virtual QDateTime getLastNexusQuery() const { return QDateTime(); } virtual QString getNexusDescription() const { return QString(); } virtual int getFixedPriority() const { return INT_MIN; } - virtual QStringList archives() const { return m_Archives; } + virtual QStringList archives(bool checkOnDisk = false) { return m_Archives; } virtual QStringList stealFiles() const { return m_Archives + QStringList(m_ReferenceFile); } virtual bool alwaysEnabled() const { return true; } virtual void addInstalledFile(int, int) {} diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 742ae384..0e428483 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -49,7 +49,7 @@ QString ModInfoOverwrite::getDescription() const "modified (i.e. by the construction kit)"); } -QStringList ModInfoOverwrite::archives() const +QStringList ModInfoOverwrite::archives(bool checkOnDisk) { QStringList result; QDir dir(this->absolutePath()); diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index 4b98d715..cfd662af 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -51,7 +51,7 @@ public: virtual QString getDescription() const; virtual QDateTime getLastNexusQuery() const { return QDateTime(); } virtual QString getNexusDescription() const { return QString(); } - virtual QStringList archives() const; + virtual QStringList archives(bool checkOnDisk = false); virtual void addInstalledFile(int, int) {} private: diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 0f1f93dc..f29d3725 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -45,6 +45,10 @@ ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGa if (!game->primarySources().contains(m_GameName, Qt::CaseInsensitive)) m_IsAlternate = true; + //populate m_Archives + m_Archives = QStringList(); + archives(true); + connect(&m_NexusBridge, SIGNAL(descriptionAvailable(QString,int,QVariant,QVariant)) , this, SLOT(nxmDescriptionAvailable(QString,int,QVariant,QVariant))); connect(&m_NexusBridge, SIGNAL(endorsementToggled(QString,int,QVariant,QVariant)) @@ -627,15 +631,18 @@ QString ModInfoRegular::getURL() const -QStringList ModInfoRegular::archives() const +QStringList ModInfoRegular::archives(bool checkOnDisk) { - QStringList result; - QDir dir(this->absolutePath()); - QStringList bsaList = dir.entryList(QStringList({ "*.bsa", "*.ba2" })); - for (const QString &archive : bsaList) { - result.append(this->absolutePath() + "/" + archive); + if (checkOnDisk) { + QStringList result; + QDir dir(this->absolutePath()); + QStringList bsaList = dir.entryList(QStringList({ "*.bsa", "*.ba2" })); + for (const QString &archive : bsaList) { + result.append(this->absolutePath() + "/" + archive); + } + m_Archives = result; } - return result; + return m_Archives; } void ModInfoRegular::addInstalledFile(int modId, int fileId) diff --git a/src/modinforegular.h b/src/modinforegular.h index 33b26998..093b4e5b 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -320,7 +320,7 @@ public: */ virtual QDateTime getLastNexusQuery() const; - virtual QStringList archives() const; + virtual QStringList archives(bool checkOnDisk = false); virtual void setColor(QColor color); @@ -371,6 +371,8 @@ private: QString m_URL; QString m_GameName; + mutable QStringList m_Archives; + QDateTime m_CreationTime; QDateTime m_LastNexusQuery; -- cgit v1.3.1 From f07f6b8904f69a92cd24e3c52eaf1fc6db8e60dd Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 29 Jan 2019 20:10:53 -0600 Subject: Update Process Improvements * 5 minute batch auto-update of up to 10 mods - Still able to force an update of all 'unchecked' mods - Prioritizes mods with oldest update 'age' * Implemented 1-hour wait between update checks per mod * Fixed issues with update progress display * Only enable update filter automatically if 'force update' * Improved display of version update status in mod list - Italic text when ready to perform update check - Tooltip indicates when next update is available * Fixed remaining issues with update fallback check * Only trigger one update API request for duplicate sources --- src/mainwindow.cpp | 80 +- src/mainwindow.h | 3 + src/modinfo.cpp | 49 +- src/modinfo.h | 1433 ++++++++++++------------ src/modinfodialog.cpp | 2 +- src/modinfoforeign.h | 3 + src/modinfooverwrite.h | 3 + src/modinforegular.cpp | 42 +- src/modinforegular.h | 18 +- src/modinfoseparator.h | 4 +- src/modlist.cpp | 2808 ++++++++++++++++++++++++------------------------ 11 files changed, 2288 insertions(+), 2157 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 094a44af..b57c62f8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -400,7 +400,7 @@ MainWindow::MainWindow(QSettings &initSettings connect(NexusInterface::instance(&pluginContainer), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int))); connect(NexusInterface::instance(&pluginContainer), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(validateFailed(QString)), this, SLOT(validationFailed(QString))); - connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool)), + connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool, std::tuple)), this, SLOT(updateWindowTitle(const QString&, bool))); connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool, std::tuple)), NexusInterface::instance(&m_PluginContainer), SLOT(setRateMax(const QString&, bool, std::tuple))); @@ -439,6 +439,10 @@ MainWindow::MainWindow(QSettings &initSettings connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); + m_ModUpdateTimer.setSingleShot(false); + connect(&m_ModUpdateTimer, SIGNAL(timeout()), this, SLOT(modUpdateCheck())); + m_ModUpdateTimer.start(300 * 1000); + setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); FileDialogMemory::restore(initSettings); @@ -491,6 +495,8 @@ MainWindow::MainWindow(QSettings &initSettings updatePluginCount(); updateModCount(); + + modUpdateCheck(); } @@ -3996,6 +4002,14 @@ void MainWindow::checkModsForUpdates() m_ModsToUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this); } } + + m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); + for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { + if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + break; + } + } } void MainWindow::changeVersioningScheme() { @@ -4399,7 +4413,7 @@ void MainWindow::initModListContextMenu(QMenu *menu) menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); - menu->addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates())); + menu->addAction(tr("Force update check"), this, SLOT(checkModsForUpdates())); menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh())); menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); } @@ -5427,21 +5441,32 @@ void MainWindow::updateDownloadView() void MainWindow::modDetailsUpdated(bool) { - if (--m_ModsToUpdate == 0) { + if (m_ModsToUpdate <= 0) { statusBar()->hide(); - m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); - break; - } - } m_RefreshProgress->setVisible(false); } else { m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); } } +void MainWindow::modUpdateCheck() +{ + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + m_ModsToUpdate += ModInfo::autoUpdateCheck(&m_PluginContainer, this); + m_RefreshProgress->setRange(0, m_ModsToUpdate); + } else { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + m_OrganizerCore.doAfterLogin([this]() { this->checkModsForUpdates(); }); + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { // otherwise there will be no endorsement info + MessageDialog::showMessage(tr("Not logged in, endorsement information will be wrong"), + this, true); + m_ModsToUpdate += ModInfo::autoUpdateCheck(&m_PluginContainer, this); + } + } +} + void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) { QVariantMap resultInfo = resultData.toMap(); @@ -5458,12 +5483,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD } } std::vector modsList = ModInfo::getByModID(gameNameReal, modID); - // Not clear to me what this is accomplishing? - //if (sameNexus) { - // std::vector mainInfo = ModInfo::getByModID(m_OrganizerCore.managedGame()->gameShortName(), modID); - // info.reserve(info.size() + mainInfo.size()); - // info.insert(info.end(), mainInfo.begin(), mainInfo.end()); - //} + for (auto mod : modsList) { QString installedFile = mod->getInstallationFile(); for (auto update : fileUpdates) { @@ -5511,6 +5531,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD if (foundUpdate) { // Just get the standard data updates for endorsements and descriptions + mod->setLastNexusUpdate(QDateTime::currentDateTimeUtc()); mod->updateNXMInfo(); } else { // Scrape mod data here so we can use the mod version if no file update was located @@ -5518,29 +5539,9 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD } } - // Old endorsement and mod info updater - //for - // if (updateData["old_file_id"].toInt() == mod->readMeta()) - // (*iter)->setNewestVersion(result["version"].toString()); - //(*iter)->setNexusDescription(result["description"].toString()); - //if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated() && - // result.contains("voted_by_user") && - // Settings::instance().endorsementIntegration()) { - // // don't use endorsement info if we're not logged in or if the response doesn't contain it - // (*iter)->setIsEndorsed(result["voted_by_user"].toBool()); - //} - - if (m_ModsToUpdate <= 0) { + if (--m_ModsToUpdate <= 0) { statusBar()->hide(); - m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); - break; - } - } - } - else { + } else { m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); } } @@ -5570,9 +5571,8 @@ void MainWindow::nxmDescriptionAvailable(QString gameName, int modID, QVariant u else mod->setIsEndorsed(false); } + mod->saveMeta(); } - disconnect(sender(), SIGNAL(nxmDescriptionAvailable(QString, int, QVariant, QVariant, int)), - this, SLOT(nxmDescriptionAvailable(QString, int, QVariant, QVariant, int))); } void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int) diff --git a/src/mainwindow.h b/src/mainwindow.h index b3490d07..65ca99dd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -348,6 +348,7 @@ private: QTimer m_CheckBSATimer; QTimer m_SaveMetaTimer; QTimer m_UpdateProblemsTimer; + QTimer m_ModUpdateTimer; QFuture m_MetaSave; @@ -508,6 +509,8 @@ private slots: void modInstalled(const QString &modName); + void modUpdateCheck(); + void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index e7b7657b..5d5cb57e 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -295,20 +295,63 @@ int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiv // NexusInterface::instance(pluginContainer)->requestUpdates(game->nexusModOrganizerID(), receiver, QVariant(), game->gameShortName(), QString()); //} - std::multimap> organizedGames; + std::multimap organizedGames; for (auto mod : s_Collection) { if (mod->canBeUpdated()) { - organizedGames.insert(std::pair>(mod->getGameName(), mod)); + organizedGames.insert(std::make_pair(mod->getGameName(), mod->getNexusID())); } } + result = organizedGames.size(); + for (auto game : organizedGames) { - NexusInterface::instance(pluginContainer)->requestUpdates(game.second->getNexusID(), receiver, QVariant(), game.first, QString()); + NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); + } + + return result; +} + + +int ModInfo::autoUpdateCheck(PluginContainer *pluginContainer, QObject *receiver) +{ + qInfo("Initializing periodic update check."); + int result = 0; + + std::vector> sortedMods; + std::multimap organizedGames; + for (auto mod : s_Collection) { + if (mod->canBeUpdated()) { + sortedMods.push_back(mod); + } + } + + std::sort(sortedMods.begin(), sortedMods.end(), [](QSharedPointer a, QSharedPointer b) -> bool { + return a->getLastNexusUpdate() > b->getLastNexusUpdate(); + }); + + if (sortedMods.size() > 10) + sortedMods.resize(10); + + result = sortedMods.size(); + + if (sortedMods.size()) { + qInfo("Checking updates for %d mods...", sortedMods.size()); + + for (auto mod : sortedMods) { + organizedGames.insert(std::make_pair(mod->getGameName(), mod->getNexusID())); + } + + for (auto game : organizedGames) { + NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); + } + } else { + qInfo("No mods require updates at this time."); } return result; } + void ModInfo::setVersion(const VersionInfo &version) { m_Version = version; diff --git a/src/modinfo.h b/src/modinfo.h index 6b7c714e..9c753752 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -1,710 +1,723 @@ -/* -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 . -*/ - -#ifndef MODINFO_H -#define MODINFO_H - -#include "imodinterface.h" -#include "versioninfo.h" - -class PluginContainer; - -class QDateTime; -class QDir; -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -namespace MOBase { class IPluginGame; } -namespace MOShared { class DirectoryEntry; } - -/** - * @brief Represents meta information about a single mod. - * - * Represents meta information about a single mod. The class interface is used - * to manage the mod collection - * - **/ -class ModInfo : public QObject, public MOBase::IModInterface -{ - - Q_OBJECT - -public: - - typedef QSharedPointer Ptr; - - static QString s_HiddenExt; - - enum EFlag { - FLAG_INVALID, - FLAG_BACKUP, - FLAG_SEPARATOR, - FLAG_OVERWRITE, - FLAG_FOREIGN, - FLAG_NOTENDORSED, - FLAG_NOTES, - FLAG_CONFLICT_OVERWRITE, - FLAG_CONFLICT_OVERWRITTEN, - FLAG_CONFLICT_MIXED, - FLAG_CONFLICT_REDUNDANT, - FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, - FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, - FLAG_ARCHIVE_CONFLICT_OVERWRITE, - FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, - FLAG_ARCHIVE_CONFLICT_MIXED, - FLAG_PLUGIN_SELECTED, - FLAG_ALTERNATE_GAME - }; - - enum EContent { - CONTENT_PLUGIN, - CONTENT_TEXTURE, - CONTENT_MESH, - CONTENT_BSA, - CONTENT_INTERFACE, - CONTENT_SOUND, - CONTENT_SCRIPT, - CONTENT_SKSE, - CONTENT_SKSEFILES, - CONTENT_SKYPROC, - CONTENT_MCM, - CONTENT_INI, - CONTENT_MODGROUP - }; - - static const int NUM_CONTENT_TYPES = CONTENT_MODGROUP + 1; - - enum EHighlight { - HIGHLIGHT_NONE = 0, - HIGHLIGHT_INVALID = 1, - HIGHLIGHT_CENTER = 2, - HIGHLIGHT_IMPORTANT = 4, - HIGHLIGHT_PLUGIN = 8 - }; - - enum EEndorsedState { - ENDORSED_FALSE, - ENDORSED_TRUE, - ENDORSED_UNKNOWN, - ENDORSED_NEVER - }; - - enum EModType { - MOD_DEFAULT, - MOD_DLC, - MOD_CC - }; - - -public: - - /** - * @brief read the mod directory and Mod ModInfo objects for all subdirectories - **/ - static void updateFromDisc(const QString &modDirectory, - MOShared::DirectoryEntry **directoryStructure, - PluginContainer *pluginContainer, - bool displayForeign, - MOBase::IPluginGame const *game); - - static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); } - - /** - * @brief retrieve the number of mods - * - * @return number of mods - **/ - static unsigned int getNumMods(); - - /** - * @brief retrieve a ModInfo object based on its index - * - * @param index the index to look up. the maximum is getNumMods() - 1 - * @return a reference counting pointer to the mod info. - * @note since the pointer is reference counting, the pointer remains valid even if the collection is refreshed in a different thread - **/ - static ModInfo::Ptr getByIndex(unsigned int index); - - /** - * @brief retrieve a ModInfo object based on its nexus mod id - * - * @param modID the nexus mod id to look up - * @return a reference counting pointer to the mod info - * @todo in its current form, this function is broken! There may be multiple mods with the same nexus id, - * this function will return only one of them - **/ - static std::vector getByModID(QString game, int modID); - - /** - * @brief remove a mod by index - * - * this physically deletes the specified mod from the disc and updates the ModInfo collection - * but not other structures that reference mods - * @param index index of the mod to delete - * @return true if removal was successful, fals otherwise - **/ - static bool removeMod(unsigned int index); - - /** - * @brief retrieve the mod index by the mod name - * - * @param name name of the mod to look up - * @return the index of the mod. If the mod doesn't exist, UINT_MAX is returned - **/ - static unsigned int getIndex(const QString &name); - - /** - * @brief find the first mod that fulfills the filter function (after no particular order) - * @param filter a function to filter by. should return true for a match - * @return index of the matching mod or UINT_MAX if there wasn't a match - */ - static unsigned int findMod(const boost::function &filter); - - /** - * @brief check a bunch of mods for updates - * @param modIDs list of mods (Nexus Mod IDs) to check for updates - * @return - */ - static void checkChunkForUpdate(PluginContainer *pluginContainer, const std::vector &modIDs, QObject *receiver, QString gameName); - - /** - * @brief query nexus information for every mod and update the "newest version" information - **/ - static int checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); - - /** - * @brief create a new mod from the specified directory and add it to the collection - * @param dir directory to create from - * @return pointer to the info-structure of the newly created/added mod - */ - static ModInfo::Ptr createFrom(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &dir, MOShared::DirectoryEntry **directoryStructure); - - /** - * @brief create a new "foreign-managed" mod from a tuple of plugin and archives - * @param espName name of the plugin - * @param bsaNames names of archives - * @return a new mod - */ - static ModInfo::Ptr createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, ModInfo::EModType modType, MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer); - - /** - * @brief retieve a name for one of the CONTENT_ enums - * @param contentType the content value - * @return a display string - */ - static QString getContentTypeName(int contentType); - - virtual bool isRegular() const { return false; } - - virtual bool isEmpty() const { return false; } - - /** - * @brief test if there is a newer version of the mod - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if there is a newer version - **/ - virtual bool updateAvailable() const = 0; - - /** - * @return true if the update currently available is ignored - */ - virtual bool updateIgnored() const = 0; - - /** - * @brief test if the "newest" version of the mod is older than the installed version - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if the newest version is older than the installed one - **/ - virtual bool downgradeAvailable() const = 0; - - /** - * @brief request an update of nexus description for this mod. - * - * This requests mod information from the nexus. This is an asynchronous request, - * so there is no immediate effect of this call. - * Right now, Mod Organizer interprets the "newest version" and "description" from the - * response, though the description is only stored in memory - * - **/ - virtual bool updateNXMInfo() = 0; - - /** - * @brief assign or unassign the specified category - * - * Every mod can have an arbitrary number of categories assigned to it - * - * @param categoryID id of the category to set - * @param active determines wheter the category is assigned or unassigned - * @note this function does not test whether categoryID actually identifies a valid category - **/ - virtual void setCategory(int categoryID, bool active) = 0; - - /** - * @brief changes the comments (manually set information displayed in the mod list) for this mod - * @param comments new comments - */ - virtual void setComments(const QString &comments) = 0; - - /** - * @brief change the notes (manually set information) for this mod - * @param notes new notes - */ - virtual void setNotes(const QString ¬es) = 0; - - /** - * @brief set/change the source game of this mod - * - * @param gameName the source game shortName - */ - virtual void setGameName(QString gameName) = 0; - - /** - * @brief set/change the nexus mod id of this mod - * - * @param modID the nexus mod id - **/ - virtual void setNexusID(int modID) = 0; - - /** - * @brief set/change the version of this mod - * @param version new version of the mod - */ - virtual void setVersion(const MOBase::VersionInfo &version); - - /** - * @brief Controls if mod should be highlighted based on plugin selection - * @param isSelected whether or not the plugin has a selected mod - **/ - virtual void setPluginSelected(const bool &isSelected); - - /** - * @brief set the newest version of this mod on the nexus - * - * this can be used to overwrite the version of a mod without actually - * updating the mod - * - * @param version the new version to use - * @todo this function should be made obsolete. All queries for mod information should go through - * this class so no public function for this change is required - **/ - virtual void setNewestVersion(const MOBase::VersionInfo &version) = 0; - - /** - * @brief sets the repository that was used to download the mod - */ - virtual void setRepository(const QString &) {} - - /** - * @brief changes/updates the nexus description text - * @param description the current description text - */ - virtual void setNexusDescription(const QString &description) = 0; - - /** - * @brief sets the file this mod was installed from - * @param fileName name of the file - */ - virtual void setInstallationFile(const QString &fileName) = 0; - - /** - * @brief sets the category id from a nexus category id. Conversion to MO id happens internally - * @param categoryID the nexus category id - * @note if a mapping is not possible, the category is set to the default value - */ - virtual void addNexusCategory(int categoryID) = 0; - - virtual void addCategory(const QString &categoryName) override; - virtual bool removeCategory(const QString &categoryName) override; - virtual QStringList categories() override; - - /** - * update the endorsement state for the mod. This only changes the - * buffered state, it does not sync with Nexus - * @param endorsed the new endorsement state - */ - virtual void setIsEndorsed(bool endorsed) = 0; - - /** - * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed - */ - virtual void setNeverEndorse() = 0; - - /** - * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices - * @return true if the mod was successfully removed - **/ - virtual bool remove() = 0; - - /** - * @brief endorse or un-endorse the mod. This will sync with nexus! - * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. - * @note if doEndorse doesn't differ from the current value, nothing happens. - */ - virtual void endorse(bool doEndorse) = 0; - - /** - * @brief clear all caches held for this mod - */ - virtual void clearCaches() {} - - /** - * @brief getter for the mod name - * - * @return the mod name - **/ - virtual QString name() const = 0; - - /** - * @brief getter for an internal name. This is usually the same as the regular name, but with special mod types it might be - * this is used to distinguish between mods that have the same visible name - * @return internal mod name - */ - virtual QString internalName() const { return name(); } - - /** - * @brief getter for the mod path - * - * @return the (absolute) path to the mod - **/ - virtual QString absolutePath() const = 0; - - /** - * @brief getter for the installation file - * - * @return file used to install this mod from - */ - virtual QString getInstallationFile() const = 0; - - /** - * @return version object for machine based comparisons - **/ - virtual MOBase::VersionInfo getVersion() const { return m_Version; } - - /** - * @brief getter for the newest version number of this mod - * - * @return newest version of the mod - **/ - virtual MOBase::VersionInfo getNewestVersion() const = 0; - - /** - * @return the repository from which the file was downloaded. Only relevant regular mods - */ - virtual QString repository() const { return ""; } - - /** - * @brief ignore the newest version for updates - */ - virtual void ignoreUpdate(bool ignore) = 0; - - /** - * @brief getter for the nexus mod id - * - * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist - **/ - virtual int getNexusID() const = 0; - - /** - * @brief getter for the source game repository - * - * @return the source game repository. should default to the active game. - **/ - virtual QString getGameName() const = 0; - - /** - * @return the fixed priority of mods of this type or INT_MIN if the priority of mods - * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods - * or INT_MAX to force priority above all user-modifiables - */ - virtual int getFixedPriority() const = 0; - - /** - * @return true if the mod is always enabled - */ - virtual bool alwaysEnabled() const { return false; } - - /** - * @return true if the mod can be updated - */ - virtual bool canBeUpdated() const { return false; } - - /** - * @return true if the mod can be enabled/disabled - */ - virtual bool canBeEnabled() const { return false; } - - /** - * @return a list of flags for this mod - */ - virtual std::vector getFlags() const = 0; - - /** - * @return a list of content types contained in a mod - */ - virtual std::vector getContents() const { return std::vector(); } - - /** - * @brief test if the specified flag is set for this mod - * @param flag the flag to test - * @return true if the flag is set, false otherwise - */ - bool hasFlag(EFlag flag) const; - - /** - * @brief test if the mods contains the specified content - * @param content the content to test - * @return true if the content is there, false otherwise - */ - bool hasContent(ModInfo::EContent content) const; - - /** - * @return an indicator if and how this mod should be highlighted by the UI - */ - virtual int getHighlight() const { return HIGHLIGHT_NONE; } - - /** - * @return list of names of ini tweaks - **/ - virtual std::vector getIniTweaks() const = 0; - - /** - * @return a description about the mod, to be displayed in the ui - */ - virtual QString getDescription() const = 0; - - /** - * @return comments for this mod - */ - virtual QString comments() const = 0; - - /** - * @return notes for this mod - */ - virtual QString notes() const = 0; - - /** - * @return creation time of this mod - */ - virtual QDateTime creationTime() const = 0; - - /** - * @return nexus description of the mod (html) - */ - virtual QString getNexusDescription() const = 0; - - /** - * @return last time nexus was queried for infos on this mod - */ - virtual QDateTime getLastNexusQuery() const = 0; - - /** - * @return a list of files that, if they exist in the data directory are treated as files in THIS mod - */ - virtual QStringList stealFiles() const { return QStringList(); } - - /** - * @return a list of archives belonging to this mod (as absolute file paths) - */ - virtual QStringList archives(bool checkOnDisk = false) = 0; - - /* - *@return the color choosen by the user for the mod/separator - */ - virtual QColor getColor() { return QColor(); } - - /* - *@return true if the color has been set successfully. - */ - virtual void setColor(QColor color) { } - - /** - * @brief adds the information that a file has been installed into this mod - * @param modId id of the mod installed - * @param fileId id of the file installed - */ - virtual void addInstalledFile(int modId, int fileId) = 0; - - /** - * @brief test if the mod belongs to the specified category - * - * @param categoryID the category to test for. - * @return true if the mod belongs to the specified category - * @note this does not verify the id actually identifies a category - **/ - bool categorySet(int categoryID) const; - - /** - * @brief retrive the whole list of categories (as ids) this mod belongs to - * - * @return list of categories - **/ - const std::set &getCategories() const { return m_Categories; } - - /** - * @return id of the primary category of this mod - */ - int getPrimaryCategory() const { return m_PrimaryCategory; } - - /** - * @brief sets the new primary category of the mod - * @param categoryID the category to set - */ - virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; } - - /** - * @return true if this mod is considered "valid", that is: it contains data used by the game - **/ - virtual bool isValid() const { return m_Valid; } - - /** - * @return true if the file has been endorsed on nexus - */ - virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; } - - /** - * @brief updates the valid-flag for this mod - */ - void testValid(); - - /** - * @brief updates the mod to flag it as converted in order to ignore the alternate game warning - */ - virtual void markConverted(bool converted) {} - - /** - * @brief updates the mod to flag it as valid in order to ignore the invalid game data flag - */ - virtual void markValidated(bool validated) {} - - /** - * @brief reads meta information from disk - */ - virtual void readMeta() {} - - /** - * @brief stores meta information back to disk - */ - virtual void saveMeta() {} - - /** - * @return retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed - */ - virtual std::set getModOverwrite() { return std::set(); } - - /** - * @return list of mods (as mod index) that overwrite this one. Updates may be delayed - */ - virtual std::set getModOverwritten() { return std::set(); } - - /** - * @return retrieve list of mods (as mod index) with archives that are overwritten by this one. Updates may be delayed - */ - virtual std::set getModArchiveOverwrite() { return std::set(); } - - /** - * @return list of mods (as mod index) with archives that overwrite this one. Updates may be delayed - */ - virtual std::set getModArchiveOverwritten() { return std::set(); } - - /** - * @return retrieve list of mods (as mod index) with archives that are overwritten by thos mod's loose files. Updates may be delayed - */ - virtual std::set getModArchiveLooseOverwrite() { return std::set(); } - - /** - * @return list of mods (as mod index) with loose files that overwrite this one's archive files. Updates may be delayed - */ - virtual std::set getModArchiveLooseOverwritten() { return std::set(); } - - /** - * @brief update conflict information - */ - virtual void doConflictCheck() const {} - - /** - * @brief set the URL for a mod - */ - virtual void setURL(QString const &) {} - - /** - * @returns the URL for a mod - */ - virtual QString getURL() const { return ""; } - -signals: - - /** - * @brief emitted whenever the information of a mod changes - * - * @param success true if the mod details were updated successfully, false if not - **/ - void modDetailsUpdated(bool success); - -protected: - - ModInfo(PluginContainer *pluginContainer); - - static void updateIndices(); - static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS); - -private: - - static void createFromOverwrite(PluginContainer *pluginContainer); - -protected: - - static std::vector s_Collection; - static std::map s_ModsByName; - - int m_PrimaryCategory; - std::set m_Categories; - - MOBase::VersionInfo m_Version; - - bool m_PluginSelected = false; - -private: - - static QMutex s_Mutex; - static std::map, std::vector > s_ModsByModID; - static int s_NextID; - - bool m_Valid; - -}; - - -#endif // MODINFO_H +/* +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 . +*/ + +#ifndef MODINFO_H +#define MODINFO_H + +#include "imodinterface.h" +#include "versioninfo.h" + +class PluginContainer; + +class QDateTime; +class QDir; +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace MOBase { class IPluginGame; } +namespace MOShared { class DirectoryEntry; } + +/** + * @brief Represents meta information about a single mod. + * + * Represents meta information about a single mod. The class interface is used + * to manage the mod collection + * + **/ +class ModInfo : public QObject, public MOBase::IModInterface +{ + + Q_OBJECT + +public: + + typedef QSharedPointer Ptr; + + static QString s_HiddenExt; + + enum EFlag { + FLAG_INVALID, + FLAG_BACKUP, + FLAG_SEPARATOR, + FLAG_OVERWRITE, + FLAG_FOREIGN, + FLAG_NOTENDORSED, + FLAG_NOTES, + FLAG_CONFLICT_OVERWRITE, + FLAG_CONFLICT_OVERWRITTEN, + FLAG_CONFLICT_MIXED, + FLAG_CONFLICT_REDUNDANT, + FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, + FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, + FLAG_ARCHIVE_CONFLICT_OVERWRITE, + FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, + FLAG_ARCHIVE_CONFLICT_MIXED, + FLAG_PLUGIN_SELECTED, + FLAG_ALTERNATE_GAME + }; + + enum EContent { + CONTENT_PLUGIN, + CONTENT_TEXTURE, + CONTENT_MESH, + CONTENT_BSA, + CONTENT_INTERFACE, + CONTENT_SOUND, + CONTENT_SCRIPT, + CONTENT_SKSE, + CONTENT_SKSEFILES, + CONTENT_SKYPROC, + CONTENT_MCM, + CONTENT_INI, + CONTENT_MODGROUP + }; + + static const int NUM_CONTENT_TYPES = CONTENT_MODGROUP + 1; + + enum EHighlight { + HIGHLIGHT_NONE = 0, + HIGHLIGHT_INVALID = 1, + HIGHLIGHT_CENTER = 2, + HIGHLIGHT_IMPORTANT = 4, + HIGHLIGHT_PLUGIN = 8 + }; + + enum EEndorsedState { + ENDORSED_FALSE, + ENDORSED_TRUE, + ENDORSED_UNKNOWN, + ENDORSED_NEVER + }; + + enum EModType { + MOD_DEFAULT, + MOD_DLC, + MOD_CC + }; + + +public: + + /** + * @brief read the mod directory and Mod ModInfo objects for all subdirectories + **/ + static void updateFromDisc(const QString &modDirectory, + MOShared::DirectoryEntry **directoryStructure, + PluginContainer *pluginContainer, + bool displayForeign, + MOBase::IPluginGame const *game); + + static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); } + + /** + * @brief retrieve the number of mods + * + * @return number of mods + **/ + static unsigned int getNumMods(); + + /** + * @brief retrieve a ModInfo object based on its index + * + * @param index the index to look up. the maximum is getNumMods() - 1 + * @return a reference counting pointer to the mod info. + * @note since the pointer is reference counting, the pointer remains valid even if the collection is refreshed in a different thread + **/ + static ModInfo::Ptr getByIndex(unsigned int index); + + /** + * @brief retrieve a ModInfo object based on its nexus mod id + * + * @param modID the nexus mod id to look up + * @return a reference counting pointer to the mod info + * @todo in its current form, this function is broken! There may be multiple mods with the same nexus id, + * this function will return only one of them + **/ + static std::vector getByModID(QString game, int modID); + + /** + * @brief remove a mod by index + * + * this physically deletes the specified mod from the disc and updates the ModInfo collection + * but not other structures that reference mods + * @param index index of the mod to delete + * @return true if removal was successful, fals otherwise + **/ + static bool removeMod(unsigned int index); + + /** + * @brief retrieve the mod index by the mod name + * + * @param name name of the mod to look up + * @return the index of the mod. If the mod doesn't exist, UINT_MAX is returned + **/ + static unsigned int getIndex(const QString &name); + + /** + * @brief find the first mod that fulfills the filter function (after no particular order) + * @param filter a function to filter by. should return true for a match + * @return index of the matching mod or UINT_MAX if there wasn't a match + */ + static unsigned int findMod(const boost::function &filter); + + /** + * @brief run a limited batch of mod update checks for "newest version" information + */ + static int autoUpdateCheck(PluginContainer *pluginContainer, QObject *receiver); + + /** + * @brief query nexus information for every mod and update the "newest version" information + **/ + static int checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); + + /** + * @brief create a new mod from the specified directory and add it to the collection + * @param dir directory to create from + * @return pointer to the info-structure of the newly created/added mod + */ + static ModInfo::Ptr createFrom(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &dir, MOShared::DirectoryEntry **directoryStructure); + + /** + * @brief create a new "foreign-managed" mod from a tuple of plugin and archives + * @param espName name of the plugin + * @param bsaNames names of archives + * @return a new mod + */ + static ModInfo::Ptr createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, ModInfo::EModType modType, MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer); + + /** + * @brief retieve a name for one of the CONTENT_ enums + * @param contentType the content value + * @return a display string + */ + static QString getContentTypeName(int contentType); + + virtual bool isRegular() const { return false; } + + virtual bool isEmpty() const { return false; } + + /** + * @brief test if there is a newer version of the mod + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if there is a newer version + **/ + virtual bool updateAvailable() const = 0; + + /** + * @return true if the update currently available is ignored + */ + virtual bool updateIgnored() const = 0; + + /** + * @brief test if the "newest" version of the mod is older than the installed version + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if the newest version is older than the installed one + **/ + virtual bool downgradeAvailable() const = 0; + + /** + * @brief request an update of nexus description for this mod. + * + * This requests mod information from the nexus. This is an asynchronous request, + * so there is no immediate effect of this call. + * Right now, Mod Organizer interprets the "newest version" and "description" from the + * response, though the description is only stored in memory + * + **/ + virtual bool updateNXMInfo() = 0; + + /** + * @brief assign or unassign the specified category + * + * Every mod can have an arbitrary number of categories assigned to it + * + * @param categoryID id of the category to set + * @param active determines wheter the category is assigned or unassigned + * @note this function does not test whether categoryID actually identifies a valid category + **/ + virtual void setCategory(int categoryID, bool active) = 0; + + /** + * @brief changes the comments (manually set information displayed in the mod list) for this mod + * @param comments new comments + */ + virtual void setComments(const QString &comments) = 0; + + /** + * @brief change the notes (manually set information) for this mod + * @param notes new notes + */ + virtual void setNotes(const QString ¬es) = 0; + + /** + * @brief set/change the source game of this mod + * + * @param gameName the source game shortName + */ + virtual void setGameName(QString gameName) = 0; + + /** + * @brief set/change the nexus mod id of this mod + * + * @param modID the nexus mod id + **/ + virtual void setNexusID(int modID) = 0; + + /** + * @brief set/change the version of this mod + * @param version new version of the mod + */ + virtual void setVersion(const MOBase::VersionInfo &version); + + /** + * @brief Controls if mod should be highlighted based on plugin selection + * @param isSelected whether or not the plugin has a selected mod + **/ + virtual void setPluginSelected(const bool &isSelected); + + /** + * @brief set the newest version of this mod on the nexus + * + * this can be used to overwrite the version of a mod without actually + * updating the mod + * + * @param version the new version to use + * @todo this function should be made obsolete. All queries for mod information should go through + * this class so no public function for this change is required + **/ + virtual void setNewestVersion(const MOBase::VersionInfo &version) = 0; + + /** + * @brief sets the repository that was used to download the mod + */ + virtual void setRepository(const QString &) {} + + /** + * @brief changes/updates the nexus description text + * @param description the current description text + */ + virtual void setNexusDescription(const QString &description) = 0; + + /** + * @brief sets the file this mod was installed from + * @param fileName name of the file + */ + virtual void setInstallationFile(const QString &fileName) = 0; + + /** + * @brief sets the category id from a nexus category id. Conversion to MO id happens internally + * @param categoryID the nexus category id + * @note if a mapping is not possible, the category is set to the default value + */ + virtual void addNexusCategory(int categoryID) = 0; + + virtual void addCategory(const QString &categoryName) override; + virtual bool removeCategory(const QString &categoryName) override; + virtual QStringList categories() override; + + /** + * update the endorsement state for the mod. This only changes the + * buffered state, it does not sync with Nexus + * @param endorsed the new endorsement state + */ + virtual void setIsEndorsed(bool endorsed) = 0; + + /** + * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed + */ + virtual void setNeverEndorse() = 0; + + /** + * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices + * @return true if the mod was successfully removed + **/ + virtual bool remove() = 0; + + /** + * @brief endorse or un-endorse the mod. This will sync with nexus! + * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. + * @note if doEndorse doesn't differ from the current value, nothing happens. + */ + virtual void endorse(bool doEndorse) = 0; + + /** + * @brief clear all caches held for this mod + */ + virtual void clearCaches() {} + + /** + * @brief getter for the mod name + * + * @return the mod name + **/ + virtual QString name() const = 0; + + /** + * @brief getter for an internal name. This is usually the same as the regular name, but with special mod types it might be + * this is used to distinguish between mods that have the same visible name + * @return internal mod name + */ + virtual QString internalName() const { return name(); } + + /** + * @brief getter for the mod path + * + * @return the (absolute) path to the mod + **/ + virtual QString absolutePath() const = 0; + + /** + * @brief getter for the installation file + * + * @return file used to install this mod from + */ + virtual QString getInstallationFile() const = 0; + + /** + * @return version object for machine based comparisons + **/ + virtual MOBase::VersionInfo getVersion() const { return m_Version; } + + /** + * @brief getter for the newest version number of this mod + * + * @return newest version of the mod + **/ + virtual MOBase::VersionInfo getNewestVersion() const = 0; + + /** + * @return the repository from which the file was downloaded. Only relevant regular mods + */ + virtual QString repository() const { return ""; } + + /** + * @brief ignore the newest version for updates + */ + virtual void ignoreUpdate(bool ignore) = 0; + + /** + * @brief getter for the nexus mod id + * + * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist + **/ + virtual int getNexusID() const = 0; + + /** + * @brief getter for the source game repository + * + * @return the source game repository. should default to the active game. + **/ + virtual QString getGameName() const = 0; + + /** + * @return the fixed priority of mods of this type or INT_MIN if the priority of mods + * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods + * or INT_MAX to force priority above all user-modifiables + */ + virtual int getFixedPriority() const = 0; + + /** + * @return true if the mod is always enabled + */ + virtual bool alwaysEnabled() const { return false; } + + /** + * @return true if the mod can be updated + */ + virtual bool canBeUpdated() const { return false; } + + /** + * @return true if the mod can be enabled/disabled + */ + virtual bool canBeEnabled() const { return false; } + + /** + * @return a list of flags for this mod + */ + virtual std::vector getFlags() const = 0; + + /** + * @return a list of content types contained in a mod + */ + virtual std::vector getContents() const { return std::vector(); } + + /** + * @brief test if the specified flag is set for this mod + * @param flag the flag to test + * @return true if the flag is set, false otherwise + */ + bool hasFlag(EFlag flag) const; + + /** + * @brief test if the mods contains the specified content + * @param content the content to test + * @return true if the content is there, false otherwise + */ + bool hasContent(ModInfo::EContent content) const; + + /** + * @return an indicator if and how this mod should be highlighted by the UI + */ + virtual int getHighlight() const { return HIGHLIGHT_NONE; } + + /** + * @return list of names of ini tweaks + **/ + virtual std::vector getIniTweaks() const = 0; + + /** + * @return a description about the mod, to be displayed in the ui + */ + virtual QString getDescription() const = 0; + + /** + * @return comments for this mod + */ + virtual QString comments() const = 0; + + /** + * @return notes for this mod + */ + virtual QString notes() const = 0; + + /** + * @return creation time of this mod + */ + virtual QDateTime creationTime() const = 0; + + /** + * @return nexus description of the mod (html) + */ + virtual QString getNexusDescription() const = 0; + + /** + * @brief get the last time nexus was checked for file updates on this mod + */ + virtual QDateTime getLastNexusUpdate() const = 0; + + /** + * @brief set the last time nexus was checked for file updates on this mod + */ + virtual void setLastNexusUpdate(QDateTime time) = 0; + + /** + * @return last time nexus was queried for infos on this mod + */ + virtual QDateTime getLastNexusQuery() const = 0; + + /** + * @brief set the last time nexus was queried for info on this mod + */ + virtual void setLastNexusQuery(QDateTime time) = 0; + + /** + * @return a list of files that, if they exist in the data directory are treated as files in THIS mod + */ + virtual QStringList stealFiles() const { return QStringList(); } + + /** + * @return a list of archives belonging to this mod (as absolute file paths) + */ + virtual QStringList archives(bool checkOnDisk = false) = 0; + + /* + *@return the color choosen by the user for the mod/separator + */ + virtual QColor getColor() { return QColor(); } + + /* + *@return true if the color has been set successfully. + */ + virtual void setColor(QColor color) { } + + /** + * @brief adds the information that a file has been installed into this mod + * @param modId id of the mod installed + * @param fileId id of the file installed + */ + virtual void addInstalledFile(int modId, int fileId) = 0; + + /** + * @brief test if the mod belongs to the specified category + * + * @param categoryID the category to test for. + * @return true if the mod belongs to the specified category + * @note this does not verify the id actually identifies a category + **/ + bool categorySet(int categoryID) const; + + /** + * @brief retrive the whole list of categories (as ids) this mod belongs to + * + * @return list of categories + **/ + const std::set &getCategories() const { return m_Categories; } + + /** + * @return id of the primary category of this mod + */ + int getPrimaryCategory() const { return m_PrimaryCategory; } + + /** + * @brief sets the new primary category of the mod + * @param categoryID the category to set + */ + virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; } + + /** + * @return true if this mod is considered "valid", that is: it contains data used by the game + **/ + virtual bool isValid() const { return m_Valid; } + + /** + * @return true if the file has been endorsed on nexus + */ + virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; } + + /** + * @brief updates the valid-flag for this mod + */ + void testValid(); + + /** + * @brief updates the mod to flag it as converted in order to ignore the alternate game warning + */ + virtual void markConverted(bool converted) {} + + /** + * @brief updates the mod to flag it as valid in order to ignore the invalid game data flag + */ + virtual void markValidated(bool validated) {} + + /** + * @brief reads meta information from disk + */ + virtual void readMeta() {} + + /** + * @brief stores meta information back to disk + */ + virtual void saveMeta() {} + + /** + * @return retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed + */ + virtual std::set getModOverwrite() { return std::set(); } + + /** + * @return list of mods (as mod index) that overwrite this one. Updates may be delayed + */ + virtual std::set getModOverwritten() { return std::set(); } + + /** + * @return retrieve list of mods (as mod index) with archives that are overwritten by this one. Updates may be delayed + */ + virtual std::set getModArchiveOverwrite() { return std::set(); } + + /** + * @return list of mods (as mod index) with archives that overwrite this one. Updates may be delayed + */ + virtual std::set getModArchiveOverwritten() { return std::set(); } + + /** + * @return retrieve list of mods (as mod index) with archives that are overwritten by thos mod's loose files. Updates may be delayed + */ + virtual std::set getModArchiveLooseOverwrite() { return std::set(); } + + /** + * @return list of mods (as mod index) with loose files that overwrite this one's archive files. Updates may be delayed + */ + virtual std::set getModArchiveLooseOverwritten() { return std::set(); } + + /** + * @brief update conflict information + */ + virtual void doConflictCheck() const {} + + /** + * @brief set the URL for a mod + */ + virtual void setURL(QString const &) {} + + /** + * @returns the URL for a mod + */ + virtual QString getURL() const { return ""; } + +signals: + + /** + * @brief emitted whenever the information of a mod changes + * + * @param success true if the mod details were updated successfully, false if not + **/ + void modDetailsUpdated(bool success); + +protected: + + ModInfo(PluginContainer *pluginContainer); + + static void updateIndices(); + static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS); + +private: + + static void createFromOverwrite(PluginContainer *pluginContainer); + +protected: + + static std::vector s_Collection; + static std::map s_ModsByName; + + int m_PrimaryCategory; + std::set m_Categories; + + MOBase::VersionInfo m_Version; + + bool m_PluginSelected = false; + +private: + + static QMutex s_Mutex; + static std::map, std::vector > s_ModsByModID; + static int s_NextID; + + bool m_Valid; + +}; + + +#endif // MODINFO_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index d086de08..db3e5de1 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -929,7 +929,7 @@ void ModInfoDialog::activateNexusTab() visitNexusLabel->setToolTip(nexusLink); if (m_ModInfo->getNexusDescription().isEmpty() || - QDateTime::currentDateTime() > m_ModInfo->getLastNexusQuery().addDays(1)) { + QDateTime::currentDateTimeUtc() > m_ModInfo->getLastNexusQuery().addDays(1)) { refreshNexusData(modID); } else { this->modDetailsUpdated(true); diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 20bfab2a..0702f268 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -47,7 +47,10 @@ public: virtual std::vector getFlags() const; virtual int getHighlight() const; virtual QString getDescription() const; + virtual QDateTime getLastNexusUpdate() const { return QDateTime(); } + virtual void setLastNexusUpdate(QDateTime time) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual void setLastNexusQuery(QDateTime time) {} virtual QString getNexusDescription() const { return QString(); } virtual int getFixedPriority() const { return INT_MIN; } virtual QStringList archives(bool checkOnDisk = false) { return m_Archives; } diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index b68fb15b..d2882301 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -50,7 +50,10 @@ public: virtual std::vector getFlags() const; virtual int getHighlight() const; virtual QString getDescription() const; + virtual QDateTime getLastNexusUpdate() const { return QDateTime(); } + virtual void setLastNexusUpdate(QDateTime time) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual void setLastNexusQuery(QDateTime time) {} virtual QString getNexusDescription() const { return QString(); } virtual QStringList archives(bool checkOnDisk = false); virtual void addInstalledFile(int, int) {} diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 834c09ae..57c4baad 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -98,6 +98,7 @@ void ModInfoRegular::readMeta() m_Validated = metaFile.value("validated", false).toBool(); m_URL = metaFile.value("url", "").toString(); m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); + m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); m_Color = metaFile.value("color",QColor()).value(); if (metaFile.contains("endorsed")) { if (metaFile.value("endorsed").canConvert()) { @@ -161,6 +162,7 @@ void ModInfoRegular::saveMeta() metaFile.setValue("nexusDescription", m_NexusDescription); metaFile.setValue("url", m_URL); metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); + metaFile.setValue("lastNexusUpdate", m_LastNexusUpdate.toString(Qt::ISODate)); metaFile.setValue("converted", m_Converted); metaFile.setValue("validated", m_Validated); metaFile.setValue("color", m_Color); @@ -225,8 +227,8 @@ void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, QVariant re else setEndorsedState(ENDORSED_FALSE); } - m_LastNexusQuery = QDateTime::currentDateTime(); - //m_MetaInfoChanged = true; + m_LastNexusQuery = QDateTime::currentDateTimeUtc(); + m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } @@ -263,10 +265,14 @@ void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, QNet bool ModInfoRegular::updateNXMInfo() { - if (m_NexusID > 0) { + QDateTime time = QDateTime::currentDateTimeUtc(); + QDateTime target = m_LastNexusQuery.addSecs(3600); + if (m_NexusID > 0 && time >= target) { m_NexusBridge.requestDescription(m_GameName, m_NexusID, QVariant()); return true; } + QString warning("Please wait until %1 to request updated mod info from Nexus!"); + qWarning() << warning.arg(target.toLocalTime().time().toString(Qt::DefaultLocaleShortDate)); return false; } @@ -483,6 +489,15 @@ void ModInfoRegular::ignoreUpdate(bool ignore) m_MetaInfoChanged = true; } +bool ModInfoRegular::canBeUpdated() const +{ + QDateTime now = QDateTime::currentDateTimeUtc(); + QDateTime target = m_LastNexusUpdate.addSecs(3600); + if (now >= target) + return m_NexusID > 0; + return false; +} + std::vector ModInfoRegular::getFlags() const { @@ -628,11 +643,32 @@ ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const return m_EndorsedState; } +QDateTime ModInfoRegular::getLastNexusUpdate() const +{ + return m_LastNexusUpdate; +} + +void ModInfoRegular::setLastNexusUpdate(QDateTime time) +{ + m_LastNexusUpdate = time; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + QDateTime ModInfoRegular::getLastNexusQuery() const { return m_LastNexusQuery; } +void ModInfoRegular::setLastNexusQuery(QDateTime time) +{ + m_LastNexusQuery = time; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + void ModInfoRegular::setURL(QString const &url) { m_URL = url; diff --git a/src/modinforegular.h b/src/modinforegular.h index fdb0e672..8e3952e8 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -256,7 +256,7 @@ public: /** * @return true if the mod can be updated */ - virtual bool canBeUpdated() const { return m_NexusID > 0; } + virtual bool canBeUpdated() const; /** * @return true if the mod can be enabled/disabled @@ -315,11 +315,26 @@ public: */ virtual EEndorsedState endorsedState() const; + /** + * @brief get the last time nexus was checked for file updates on this mod + */ + virtual QDateTime getLastNexusUpdate() const; + + /** + * @brief set the last time nexus was checked for file updates on this mod + */ + virtual void setLastNexusUpdate(QDateTime time); + /** * @return last time nexus was queried for infos on this mod */ virtual QDateTime getLastNexusQuery() const; + /** + * @brief set the last time nexus was queried for info on this mod + */ + virtual void setLastNexusQuery(QDateTime time); + virtual QStringList archives(bool checkOnDisk = false); virtual void setColor(QColor color); @@ -376,6 +391,7 @@ private: QDateTime m_CreationTime; QDateTime m_LastNexusQuery; + QDateTime m_LastNexusUpdate; QColor m_Color; diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index c5e0f0e5..1f3be861 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -43,8 +43,10 @@ public: virtual QString getInstallationFile() const { return ""; } virtual QString getURL() const { return ""; } virtual QString repository() const { return ""; } - + virtual QDateTime getLastNexusUpdate() const { return QDateTime(); } + virtual void setLastNexusUpdate(QDateTime time) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual void setLastNexusQuery(QDateTime time) {} virtual QDateTime creationTime() const { return QDateTime(); } virtual void getNexusFiles diff --git a/src/modlist.cpp b/src/modlist.cpp index daa6ce73..1ec5b556 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1,1398 +1,1410 @@ -/* -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 . -*/ - -#include "modlist.h" - -#include "messagedialog.h" -#include "installationtester.h" -#include "qtgroupingproxy.h" -#include "viewmarkingscrollbar.h" -#include "modlistsortproxy.h" -#include "pluginlist.h" -#include "settings.h" -#include "modinforegular.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -using namespace MOBase; - - -ModList::ModList(PluginContainer *pluginContainer, QObject *parent) - : QAbstractItemModel(parent) - , m_Profile(nullptr) - , m_NexusInterface(nullptr) - , m_Modified(false) - , m_FontMetrics(QFont()) - , m_DropOnItems(false) - , m_PluginContainer(pluginContainer) -{ - m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)")); - m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface")); - m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes")); - m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive")); - m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)")); - m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin")); - m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher")); - m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music")); - m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures")); - m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", tr("MCM Configuration")); - m_ContentIcons[ModInfo::CONTENT_INI] = std::make_tuple(":/MO/gui/content/inifile", tr("INI files")); - m_ContentIcons[ModInfo::CONTENT_MODGROUP] = std::make_tuple(":/MO/gui/content/modgroup", tr("ModGroup files")); - - m_LastCheck.start(); -} - -ModList::~ModList() -{ - m_ModStateChanged.disconnect_all_slots(); - m_ModMoved.disconnect_all_slots(); -} - -void ModList::setProfile(Profile *profile) -{ - m_Profile = profile; -} - -int ModList::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) { - return ModInfo::getNumMods(); - } else { - return 0; - } -} - -bool ModList::hasChildren(const QModelIndex &parent) const -{ - if (!parent.isValid()) { - return ModInfo::getNumMods() > 0; - } else { - return false; - } -} - - -int ModList::columnCount(const QModelIndex &) const -{ - return COL_LASTCOLUMN + 1; -} - - -QVariant ModList::getOverwriteData(int column, int role) const -{ - switch (role) { - case Qt::DisplayRole: { - if (column == 0) { - return "Overwrite"; - } else { - return QVariant(); - } - } break; - case Qt::CheckStateRole: { - if (column == 0) { - return Qt::Checked; - } else { - return QVariant(); - } - } break; - case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case Qt::UserRole: return -1; - case Qt::ForegroundRole: return QBrush(Qt::red); - case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)"); - default: return QVariant(); - } -} - - -QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const -{ - switch (flag) { - case ModInfo::FLAG_BACKUP: return tr("Backup"); - case ModInfo::FLAG_SEPARATOR: return tr("Separator"); - case ModInfo::FLAG_INVALID: return tr("No valid game data"); - case ModInfo::FLAG_NOTENDORSED: return tr("Not endorsed yet"); - case ModInfo::FLAG_NOTES: { - QStringList output; - if (!modInfo->comments().isEmpty()) - output << QString("%1").arg(modInfo->comments()); - if (!modInfo->notes().isEmpty()) - output << QString("%1").arg(modInfo->notes()); - return output.join(""); - } - case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); - case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); - case ModInfo::FLAG_ALTERNATE_GAME: return tr("
This mod is for a different game, " - "make sure it's compatible or it could cause crashes."); - default: return ""; - } -} - - -QVariantList ModList::contentsToIcons(const std::vector &contents) const -{ - QVariantList result; - std::set contentsSet(contents.begin(), contents.end()); - for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { - if (contentsSet.find(iter->first) != contentsSet.end()) { - result.append(std::get<0>(iter->second)); - } else { - result.append(QString()); - } - } - return result; -} - -QString ModList::contentsToToolTip(const std::vector &contents) const -{ - QString result(""); - - std::set contentsSet(contents.begin(), contents.end()); - for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { - if (contentsSet.find(iter->first) != contentsSet.end()) { - result.append(QString("" - "") - .arg(std::get<0>(iter->second)).arg(std::get<1>(iter->second))); - } - } - result.append("
%2
"); - return result; -} - - -QVariant ModList::data(const QModelIndex &modelIndex, int role) const -{ - if (m_Profile == nullptr) return QVariant(); - if (!modelIndex.isValid()) return QVariant(); - unsigned int modIndex = modelIndex.row(); - int column = modelIndex.column(); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if ((role == Qt::DisplayRole) || - (role == Qt::EditRole)) { - if ((column == COL_FLAGS) - || (column == COL_CONTENT)) { - return QVariant(); - } else if (column == COL_NAME) { - auto flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) - { - return modInfo->name().replace("_separator", ""); - } - else - return modInfo->name(); - } else if (column == COL_VERSION) { - VersionInfo verInfo = modInfo->getVersion(); - QString version = verInfo.displayString(); - if (role != Qt::EditRole) { - if (version.isEmpty() && modInfo->canBeUpdated()) { - version = "?"; - } - } - return version; - } else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { - return QVariant(); // hide priority for mods where it's fixed - } else { - return m_Profile->getModPriority(modIndex); - } - } else if (column == COL_MODID) { - int modID = modInfo->getNexusID(); - if (modID >= 0) { - return modID; - } - else { - return QVariant(); - } - } else if (column == COL_GAME) { - if (m_PluginContainer != nullptr) { - for (auto game : m_PluginContainer->plugins()) { - if (game->gameShortName().compare(modInfo->getGameName(), Qt::CaseInsensitive) == 0) - return game->gameName(); - } - } - return modInfo->getGameName(); - } else if (column == COL_CATEGORY) { - if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { - return tr("Non-MO"); - } else { - int category = modInfo->getPrimaryCategory(); - if (category != -1) { - CategoryFactory &categoryFactory = CategoryFactory::instance(); - if (categoryFactory.categoryExists(category)) { - try { - int categoryIdx = categoryFactory.getCategoryIndex(category); - return categoryFactory.getCategoryName(categoryIdx); - } catch (const std::exception &e) { - qCritical("failed to retrieve category name: %s", e.what()); - return QString(); - } - } else { - qWarning("category %d doesn't exist (may have been removed)", category); - modInfo->setCategory(category, false); - return QString(); - } - } else { - return QVariant(); - } - } - } else if (column == COL_INSTALLTIME) { - // display installation time for mods that can be updated - if (modInfo->creationTime().isValid()) { - return modInfo->creationTime(); - } else { - return QVariant(); - } - } else if (column == COL_NOTES) { - return modInfo->comments(); - } else { - return tr("invalid"); - } - } else if ((role == Qt::CheckStateRole) && (column == 0)) { - if (modInfo->canBeEnabled()) { - return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked; - } else { - return QVariant(); - } - } else if (role == Qt::TextAlignmentRole) { - auto flags = modInfo->getFlags(); - if (column == COL_NAME) { - if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { - return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } - } else if (column == COL_VERSION) { - return QVariant(Qt::AlignRight | Qt::AlignVCenter); - } else if (column == COL_NOTES) { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - } - } else if (role == Qt::UserRole) { - if (column == COL_CATEGORY) { - QVariantList categoryNames; - std::set categories = modInfo->getCategories(); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (auto iter = categories.begin(); iter != categories.end(); ++iter) { - categoryNames.append(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*iter))); - } - if (categoryNames.count() != 0) { - return categoryNames; - } else { - return QVariant(); - } - } else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { - return priority; - } else { - return m_Profile->getModPriority(modIndex); - } - } else { - return modInfo->getNexusID(); - } - } else if (role == Qt::UserRole + 1) { - return modIndex; - } else if (role == Qt::UserRole + 2) { - switch (column) { - case COL_MODID: return QtGroupingProxy::AGGR_FIRST; - case COL_VERSION: return QtGroupingProxy::AGGR_MAX; - case COL_CATEGORY: return QtGroupingProxy::AGGR_FIRST; - case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN; - default: return QtGroupingProxy::AGGR_NONE; - } - } else if (role == Qt::UserRole + 3) { - return contentsToIcons(modInfo->getContents()); - } else if (role == Qt::UserRole + 4) { - return modInfo->getGameName(); - } else if (role == Qt::FontRole) { - QFont result; - auto flags = modInfo->getFlags(); - if (column == COL_NAME) { - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) - { - //result.setCapitalization(QFont::AllUppercase); - result.setItalic(true); - //result.setUnderline(true); - result.setBold(true); - } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { - result.setItalic(true); - } - } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) { - result.setItalic(true); - } else if (column == COL_VERSION) { - if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { - result.setWeight(QFont::Bold); - } - } - return result; - } else if (role == Qt::DecorationRole) { - if (column == COL_VERSION) { - if (modInfo->updateAvailable()) { - return QIcon(":/MO/gui/update_available"); - } else if (modInfo->downgradeAvailable()) { - return QIcon(":/MO/gui/warning"); - } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) { - return QIcon(":/MO/gui/version_date"); - } - } - return QVariant(); - } else if (role == Qt::ForegroundRole) { - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid()) { - return Settings::getIdealTextColor(modInfo->getColor()); - } else if (column == COL_NAME) { - int highlight = modInfo->getHighlight(); - if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) - return QBrush(Qt::darkRed); - else if (highlight & ModInfo::HIGHLIGHT_INVALID) - return QBrush(Qt::darkGray); - } else if (column == COL_VERSION) { - if (!modInfo->getNewestVersion().isValid()) { - return QVariant(); - } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { - return QBrush(Qt::red); - } else { - return QBrush(Qt::darkGreen); - } - } - return QVariant(); - } else if ((role == Qt::BackgroundRole) - || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { - bool overwrite = m_Overwrite.find(modIndex) != m_Overwrite.end(); - bool archiveOverwrite = m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end(); - bool archiveLooseOverwrite = m_ArchiveLooseOverwrite.find(modIndex) != m_ArchiveLooseOverwrite.end(); - bool overwritten = m_Overwritten.find(modIndex) != m_Overwritten.end(); - bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end(); - bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end(); - if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { - return Settings::instance().modlistContainsPluginColor(); - } else if (overwritten || archiveLooseOverwritten) { - return Settings::instance().modlistOverwritingLooseColor(); - } else if (overwrite || archiveLooseOverwrite) { - return Settings::instance().modlistOverwrittenLooseColor(); - } else if (archiveOverwritten) { - return Settings::instance().modlistOverwritingArchiveColor(); - } else if (archiveOverwrite) { - return Settings::instance().modlistOverwrittenArchiveColor(); - } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) - && modInfo->getColor().isValid() - && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) - || Settings::instance().colorSeparatorScrollbar())) { - return modInfo->getColor(); - } else { - return QVariant(); - } - } else if (role == Qt::ToolTipRole) { - if (column == COL_FLAGS) { - QString result; - - for (ModInfo::EFlag flag : modInfo->getFlags()) { - if (result.length() != 0) result += "
"; - result += getFlagText(flag, modInfo); - } - - return result; - } else if (column == COL_CONTENT) { - return contentsToToolTip(modInfo->getContents()); - } else if (column == COL_NAME) { - try { - return modInfo->getDescription(); - } catch (const std::exception &e) { - qCritical("invalid mod description: %s", e.what()); - return QString(); - } - } else if (column == COL_VERSION) { - QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->getVersion().displayString(3)).arg(modInfo->getNewestVersion().displayString(3)); - if (modInfo->downgradeAvailable()) { - text += "
" + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn " - "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. " - "Either way you may want to \"upgrade\"."); - } - return text; - } else if (column == COL_CATEGORY) { - const std::set &categories = modInfo->getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories:
")); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (std::set::const_iterator catIter = categories.begin(); - catIter != categories.end(); ++catIter) { - if (catIter != categories.begin()) { - categoryString << " , "; - } - try { - categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; - } catch (const std::exception &e) { - qCritical("failed to generate tooltip: %s", e.what()); - return QString(); - } - } - - return ToQString(categoryString.str()); - } else if (column == COL_NOTES) { - return getFlagText(ModInfo::FLAG_NOTES, modInfo); - } else { - return QVariant(); - } - } else { - return QVariant(); - } -} - - -bool ModList::renameMod(int index, const QString &newName) -{ - QString nameFixed = newName; - if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) { - MessageDialog::showMessage(tr("Invalid name"), nullptr); - return false; - } - - if (ModList::allMods().contains(nameFixed, Qt::CaseInsensitive) && nameFixed.toLower()!=ModInfo::getByIndex(index)->name().toLower() ) { - MessageDialog::showMessage(tr("Name is already in use by another mod"), nullptr); - return false; - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - QString oldName = modInfo->name(); - if (nameFixed != oldName) { - // before we rename, ensure there is no scheduled asynchronous to rewrite - m_Profile->cancelModlistWrite(); - - - if (modInfo->setName(nameFixed)) - // Notice there is a good chance that setName() updated the modinfo indexes - // the modRenamed() call will refresh the indexes in the current profile - // and update the modlists in all profiles - emit modRenamed(oldName, nameFixed); - } - - // invalidate the currently displayed state of this list - notifyChange(-1); - return true; -} - -bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (m_Profile == nullptr) return false; - - if (static_cast(index.row()) >= ModInfo::getNumMods()) { - return false; - } - - int modID = index.row(); - - ModInfo::Ptr info = ModInfo::getByIndex(modID); - IModList::ModStates oldState = state(modID); - - bool result = false; - - emit aboutToChangeData(); - - if (role == Qt::CheckStateRole) { - bool enabled = value.toInt() == Qt::Checked; - if (m_Profile->modEnabled(modID) != enabled) { - m_Profile->setModEnabled(modID, enabled); - m_Modified = true; - m_LastCheck.restart(); - emit modlistChanged(index, role); - } - result = true; - emit dataChanged(index, index); - } else if (role == Qt::EditRole) { - switch (index.column()) { - case COL_NAME: { - auto flags = info->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) - { - result = renameMod(modID, value.toString() + "_separator"); - } - else - result = renameMod(modID, value.toString()); - } break; - case COL_PRIORITY: { - bool ok = false; - int newPriority = value.toInt(&ok); - if (ok && newPriority < 0) { - newPriority = 0; - } - if (ok) { - m_Profile->setModPriority(modID, newPriority); - - emit modorder_changed(); - result = true; - } else { - result = false; - } - } break; - case COL_MODID: { - bool ok = false; - int newID = value.toInt(&ok); - if (ok) { - info->setNexusID(newID); - emit modlistChanged(index, role); - result = true; - } else { - result = false; - } - } break; - case COL_VERSION: { - VersionInfo::VersionScheme scheme = info->getVersion().scheme(); - VersionInfo version(value.toString(), scheme, true); - if (version.isValid()) { - info->setVersion(version); - result = true; - } else { - result = false; - } - } break; - case COL_NOTES: { - info->setComments(value.toString()); - result = true; - } break; - default: { - qWarning("edit on column \"%s\" not supported", - getColumnName(index.column()).toUtf8().constData()); - result = false; - } break; - } - if (result) { - emit dataChanged(index, index); - } - } - - emit postDataChanged(); - - IModList::ModStates newState = state(modID); - if (oldState != newState) { - try { - m_ModStateChanged(info->name(), newState); - } catch (const std::exception &e) { - qCritical("failed to invoke state changed notification: %s", e.what()); - } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); - } - } - - return result; -} - - -QVariant ModList::headerData(int section, Qt::Orientation orientation, - int role) const -{ - if (orientation == Qt::Horizontal) { - if (role == Qt::DisplayRole) { - return getColumnName(section); - } else if (role == Qt::ToolTipRole) { - return getColumnToolTip(section); - } else if (role == Qt::TextAlignmentRole) { - return QVariant(Qt::AlignCenter); - } else if (role == Qt::SizeHintRole) { - QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); - temp.rwidth() += 20; - temp.rheight() += 12; - return temp; - } - } - return QAbstractItemModel::headerData(section, orientation, role); -} - - -Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const -{ - Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); - if (modelIndex.internalId() < 0) { - return Qt::ItemIsEnabled; - } - if (modelIndex.isValid()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row()); - std::vector flags = modInfo->getFlags(); - if (modInfo->getFixedPriority() == INT_MIN) { - result |= Qt::ItemIsDragEnabled; - result |= Qt::ItemIsUserCheckable; - if ((modelIndex.column() == COL_PRIORITY) || - (modelIndex.column() == COL_VERSION) || - (modelIndex.column() == COL_MODID)) { - result |= Qt::ItemIsEditable; - } - if (((modelIndex.column() == COL_NAME) || - (modelIndex.column() == COL_NOTES)) - && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end())) { - result |= Qt::ItemIsEditable; - } - } - if (m_DropOnItems - && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { - result |= Qt::ItemIsDropEnabled; - } - } else { - if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled; - } - return result; -} - - -QStringList ModList::mimeTypes() const -{ - QStringList result = QAbstractItemModel::mimeTypes(); - result.append("text/uri-list"); - return result; -} - -QMimeData *ModList::mimeData(const QModelIndexList &indexes) const -{ - QMimeData *result = QAbstractItemModel::mimeData(indexes); - result->setData("text/plain", "mod"); - return result; -} - -void ModList::changeModPriority(std::vector sourceIndices, int newPriority) -{ - if (m_Profile == nullptr) return; - - emit layoutAboutToBeChanged(); - Profile *profile = m_Profile; - - // sort the moving mods by ascending priorities - std::sort(sourceIndices.begin(), sourceIndices.end(), - [profile](const int &LHS, const int &RHS) { - return profile->getModPriority(LHS) > profile->getModPriority(RHS); - }); - - // move mods that are decreasing in priority - for (std::vector::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - int oldPriority = profile->getModPriority(*iter); - if (oldPriority > newPriority) { - profile->setModPriority(*iter, newPriority); - m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); - } - } - - // sort the moving mods by descending priorities - std::sort(sourceIndices.begin(), sourceIndices.end(), - [profile](const int &LHS, const int &RHS) { - return profile->getModPriority(LHS) < profile->getModPriority(RHS); - }); - - // if at least one mod is increasing in priority, the target index is - // that of the row BELOW the dropped location, otherwise it's the one above - for (std::vector::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - int oldPriority = profile->getModPriority(*iter); - if (oldPriority < newPriority) { - --newPriority; - break; - } - } - - // move mods that are increasing in priority - for (std::vector::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - int oldPriority = profile->getModPriority(*iter); - if (oldPriority < newPriority) { - profile->setModPriority(*iter, newPriority); - m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); - } - } - - emit layoutChanged(); - - emit modorder_changed(); -} - - -void ModList::changeModPriority(int sourceIndex, int newPriority) -{ - if (m_Profile == nullptr) return; - emit layoutAboutToBeChanged(); - - m_Profile->setModPriority(sourceIndex, newPriority); - - emit layoutChanged(); - - emit modorder_changed(); -} - -void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) -{ - m_Overwrite = overwrite; - m_Overwritten = overwritten; - notifyChange(0, rowCount() - 1); -} - -void ModList::setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) -{ - m_ArchiveOverwrite = overwrite; - m_ArchiveOverwritten = overwritten; - notifyChange(0, rowCount() - 1); -} - -void ModList::setArchiveLooseOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) -{ - m_ArchiveLooseOverwrite = overwrite; - m_ArchiveLooseOverwritten = overwritten; - notifyChange(0, rowCount() - 1); -} - -void ModList::setPluginContainer(PluginContainer *pluginContianer) -{ - m_PluginContainer = pluginContianer; -} - -bool ModList::modInfoAboutToChange(ModInfo::Ptr info) -{ - if (m_ChangeInfo.name.isEmpty()) { - m_ChangeInfo.name = info->name(); - m_ChangeInfo.state = state(info->name()); - return true; - } else { - return false; - } -} - -void ModList::modInfoChanged(ModInfo::Ptr info) -{ - if (info->name() == m_ChangeInfo.name) { - IModList::ModStates newState = state(info->name()); - if (m_ChangeInfo.state != newState) { - m_ModStateChanged(info->name(), newState); - } - - int row = ModInfo::getIndex(info->name()); - info->testValid(); - emit aboutToChangeData(); - emit dataChanged(index(row, 0), index(row, columnCount())); - emit postDataChanged(); - } else { - qCritical("modInfoChanged not called after modInfoAboutToChange"); - } - m_ChangeInfo.name = QString(); -} - -void ModList::disconnectSlots() { - m_ModMoved.disconnect_all_slots(); - m_ModStateChanged.disconnect_all_slots(); -} - -int ModList::timeElapsedSinceLastChecked() const -{ - return m_LastCheck.elapsed(); -} - -void ModList::highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry) -{ - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::getByIndex(i)->setPluginSelected(false); - } - for (QModelIndex idx : selection->selectedRows(PluginList::COL_NAME)) { - QString modName = idx.data().toString(); - - const MOShared::FileEntry::Ptr fileEntry = directoryEntry.findFile(modName.toStdWString()); - if (fileEntry.get() != nullptr) { - bool archive = false; - std::vector>> origins; - { - std::vector>> alternatives = fileEntry->getAlternatives(); - origins.insert(origins.end(), std::pair>(fileEntry->getOrigin(archive), fileEntry->getArchive())); - } - for (auto originInfo : origins) { - MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originInfo.first); - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) { - ModInfo::getByIndex(i)->setPluginSelected(true); - break; - } - } - } - } - } - notifyChange(0, rowCount() - 1); -} - -IModList::ModStates ModList::state(unsigned int modIndex) const -{ - IModList::ModStates result; - if (modIndex != UINT_MAX) { - result |= IModList::STATE_EXISTS; - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (modInfo->isEmpty()) { - result |= IModList::STATE_EMPTY; - } - if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) { - result |= IModList::STATE_ENDORSED; - } - if (modInfo->isValid()) { - result |= IModList::STATE_VALID; - } - if (modInfo->isRegular()) { - QSharedPointer modInfoRegular = modInfo.staticCast(); - if (modInfoRegular->isAlternate() && !modInfoRegular->isConverted()) - result |= IModList::STATE_ALTERNATE; - if (!modInfo->isValid() && modInfoRegular->isValidated()) - result |= IModList::STATE_VALID; - } - if (modInfo->canBeEnabled()) { - if (m_Profile->modEnabled(modIndex)) { - result |= IModList::STATE_ACTIVE; - } - } else { - result |= IModList::STATE_ESSENTIAL; - } - } - return result; -} - -QString ModList::displayName(const QString &internalName) const -{ - unsigned int modIndex = ModInfo::getIndex(internalName); - if (modIndex == UINT_MAX) { - // might be better to throw an exception? - return internalName; - } else { - return ModInfo::getByIndex(modIndex)->name(); - } -} - -QStringList ModList::allMods() const -{ - QStringList result; - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - result.append(ModInfo::getByIndex(i)->internalName()); - } - return result; -} - -IModList::ModStates ModList::state(const QString &name) const -{ - unsigned int modIndex = ModInfo::getIndex(name); - - return state(modIndex); -} - -bool ModList::setActive(const QString &name, bool active) -{ - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { - return false; - } else { - m_Profile->setModEnabled(modIndex, active); - - IModList::ModStates newState = state(modIndex); - m_ModStateChanged(name, newState); - return true; - } -} - -int ModList::priority(const QString &name) const -{ - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { - return -1; - } else { - return m_Profile->getModPriority(modIndex); - } -} - -bool ModList::setPriority(const QString &name, int newPriority) -{ - if ((newPriority < 0) || (newPriority >= static_cast(m_Profile->numRegularMods()))) { - return false; - } - - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { - return false; - } else { - m_Profile->setModPriority(modIndex, newPriority); - notifyChange(modIndex); - return true; - } -} - -bool ModList::onModStateChanged(const std::function &func) -{ - auto conn = m_ModStateChanged.connect(func); - return conn.connected(); -} - -bool ModList::onModMoved(const std::function &func) -{ - auto conn = m_ModMoved.connect(func); - return conn.connected(); -} - -bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent) -{ - QStringList source; - QStringList target; - - if (row == -1) { - row = parent.row(); - } - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - QDir modDirectory(modInfo->absolutePath()); - QDir gameDirectory(Settings::instance().getOverwriteDirectory()); - - unsigned int overwriteIndex = ModInfo::findMod([] (ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); - - for (const QUrl &url : mimeData->urls()) { - //qDebug("URL drop requested: %s", qUtf8Printable(url.toLocalFile())); - if (!url.isLocalFile()) { - qDebug("URL drop ignored: Not a local file."); - continue; - } - QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile()); - if (relativePath.startsWith("..")) { - qDebug("URL drop ignored: relative path starts with .."); - continue; - } - source.append(url.toLocalFile()); - target.append(modDirectory.absoluteFilePath(relativePath)); - emit fileMoved(relativePath, overwriteName, modInfo->name()); - } - - if (source.count() != 0) { - if (!shellMove(source, target)) { - qDebug("Move failed %lu",::GetLastError()); - return false; - } - } - - if (!modInfo->isValid()) { - modInfo->testValid(); - } - - return true; -} - -bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) -{ - - try { - QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - std::vector sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { - sourceRows.push_back(sourceRow); - } - } - - if (row == -1) { - row = parent.row(); - } - - if ((row < 0) || (static_cast(row) >= ModInfo::getNumMods())) { - return false; - } - - int newPriority = 0; - { - if ((row < 0) || (row > static_cast(m_Profile->numRegularMods()))) { - newPriority = m_Profile->numRegularMods() + 1; - } else { - newPriority = m_Profile->getModPriority(row); - } - if (newPriority == -1) { - newPriority = m_Profile->numRegularMods() + 1; - } - } - changeModPriority(sourceRows, newPriority); - } catch (const std::exception &e) { - reportError(tr("drag&drop failed: %1").arg(e.what())); - } - - return false; -} - - -bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) { - return true; - } - - if (m_Profile == nullptr) return false; - - if (mimeData->hasUrls()) { - return dropURLs(mimeData, row, parent); - } else if (mimeData->hasText()) { - return dropMod(mimeData, row, parent); - } else { - return false; - } -} - -void ModList::removeRowForce(int row, const QModelIndex &parent) -{ - if (static_cast(row) >= ModInfo::getNumMods()) { - return; - } - if (m_Profile == nullptr) return; - - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - - bool wasEnabled = m_Profile->modEnabled(row); - - m_Profile->setModEnabled(row, false); - - m_Profile->cancelModlistWrite(); - beginRemoveRows(parent, row, row); - ModInfo::removeMod(row); - endRemoveRows(); - m_Profile->refreshModStatus(); // removes the mod from the status list - m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed - - if (wasEnabled) { - emit removeOrigin(modInfo->name()); - } - - emit modUninstalled(modInfo->getInstallationFile()); -} - -bool ModList::removeRows(int row, int count, const QModelIndex &parent) -{ - if (static_cast(row) >= ModInfo::getNumMods()) { - return false; - } - if (m_Profile == nullptr) { - return false; - } - - bool success = false; - - if (count == 1) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - std::vector flags = modInfo->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) && (QDir(modInfo->absolutePath()).count() > 2)) { - emit clearOverwrite(); - success = true; - } - } - - for (int i = 0; i < count; ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(row + i); - if (!modInfo->isRegular()) { - continue; - } - - success = true; - - QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), - tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()), - QMessageBox::Yes | QMessageBox::No); - - if (confirmBox.exec() == QMessageBox::Yes) { - m_Profile->setModEnabled(row + i, false); - removeRowForce(row + i, parent); - } - } - - return success; -} - - -void ModList::notifyChange(int rowStart, int rowEnd) -{ - if (rowStart < 0) { - m_Overwrite.clear(); - m_Overwritten.clear(); - m_ArchiveOverwrite.clear(); - m_ArchiveOverwritten.clear(); - m_ArchiveLooseOverwrite.clear(); - m_ArchiveLooseOverwritten.clear(); - beginResetModel(); - endResetModel(); - } else { - if (rowEnd == -1) { - rowEnd = rowStart; - } - emit dataChanged(this->index(rowStart, 0), this->index(rowEnd, this->columnCount() - 1)); - } -} - - -QModelIndex ModList::index(int row, int column, const QModelIndex&) const -{ - if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { - return QModelIndex(); - } - QModelIndex res = createIndex(row, column, row); - return res; -} - - -QModelIndex ModList::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -QMap ModList::itemData(const QModelIndex &index) const -{ - QMap result = QAbstractItemModel::itemData(index); - result[Qt::UserRole] = data(index, Qt::UserRole); - return result; -} - - -void ModList::dropModeUpdate(bool dropOnItems) -{ - if (m_DropOnItems != dropOnItems) { - m_DropOnItems = dropOnItems; - } -} - - -QString ModList::getColumnName(int column) -{ - switch (column) { - case COL_FLAGS: return tr("Flags"); - case COL_CONTENT: return tr("Content"); - case COL_NAME: return tr("Mod Name"); - case COL_VERSION: return tr("Version"); - case COL_PRIORITY: return tr("Priority"); - case COL_CATEGORY: return tr("Category"); - case COL_GAME: return tr("Source Game"); - case COL_MODID: return tr("Nexus ID"); - case COL_INSTALLTIME: return tr("Installation"); - case COL_NOTES: return tr("Notes"); - default: return tr("unknown"); - } -} - - -QString ModList::getColumnToolTip(int column) -{ - switch (column) { - case COL_NAME: return tr("Name of your mods"); - case COL_VERSION: return tr("Version of the mod (if available)"); - case COL_PRIORITY: return tr("Installation priority of your mod. The higher, the more \"important\" it is and thus " - "overwrites files from mods with lower priority."); - case COL_CATEGORY: return tr("Category of the mod."); - case COL_GAME: return tr("The source game which was the origin of this mod."); - case COL_MODID: return tr("Id of the mod as used on Nexus."); - case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); - case COL_CONTENT: return tr("Depicts the content of the mod:
" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - - "
Game plugins (esp/esm/esl)
Interface
Meshes
BSA
Textures
Sounds
Music
Strings
Scripts (Papyrus)
Script Extender plugins
SkyProc Patcher
Mod Configuration Menu
INI files
ModGroup files
"); - case COL_INSTALLTIME: return tr("Time this mod was installed"); - case COL_NOTES: return tr("User notes about the mod"); - default: return tr("unknown"); - } -} - - -bool ModList::moveSelection(QAbstractItemView *itemView, int direction) -{ - QItemSelectionModel *selectionModel = itemView->selectionModel(); - - const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); - const QSortFilterProxyModel *filterModel = nullptr; - - while ((filterModel == nullptr) && (proxyModel != nullptr)) { - filterModel = qobject_cast(proxyModel); - if (filterModel == nullptr) { - proxyModel = qobject_cast(proxyModel->sourceModel()); - } - } - if (filterModel == nullptr) { - return true; - } - - int offset = -1; - if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) || - ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) { - offset = 1; - } - - QModelIndexList rows = selectionModel->selectedRows(); - if (direction > 0) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); - } - } - for (QModelIndex idx : rows) { - if (filterModel != nullptr) { - idx = filterModel->mapToSource(idx); - } - int newPriority = m_Profile->getModPriority(idx.row()) + offset; - if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { - m_Profile->setModPriority(idx.row(), newPriority); - notifyChange(idx.row()); - } - } - emit modorder_changed(); - return true; -} - -bool ModList::deleteSelection(QAbstractItemView *itemView) -{ - QItemSelectionModel *selectionModel = itemView->selectionModel(); - - QModelIndexList rows = selectionModel->selectedRows(); - if (rows.count() > 1) { - emit removeSelectedMods(); - } else if (rows.count() == 1) { - removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); - } - return true; -} - -bool ModList::toggleSelection(QAbstractItemView *itemView) -{ - emit aboutToChangeData(); - - QItemSelectionModel *selectionModel = itemView->selectionModel(); - - QList modsToEnable; - QList modsToDisable; - QModelIndexList dirtyMods; - for (QModelIndex idx : selectionModel->selectedRows()) { - int modId = idx.data(Qt::UserRole + 1).toInt(); - if (m_Profile->modEnabled(modId)) { - modsToDisable.append(modId); - dirtyMods.append(idx); - } else { - modsToEnable.append(modId); - dirtyMods.append(idx); - } - } - - m_Profile->setModsEnabled(modsToEnable, modsToDisable); - - emit modlistChanged(dirtyMods, 0); - - m_Modified = true; - m_LastCheck.restart(); - - emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); - - emit postDataChanged(); - - return true; -} - -bool ModList::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::ContextMenu) { - QContextMenuEvent *contextEvent = static_cast(event); - QWidget *object = qobject_cast(obj); - if ((object != nullptr) && (contextEvent->reason() == QContextMenuEvent::Mouse)) { - emit requestColumnSelect(object->mapToGlobal(contextEvent->pos())); - - return true; - } - } else if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) { - QAbstractItemView *itemView = qobject_cast(obj); - QKeyEvent *keyEvent = static_cast(event); - - if ((itemView != nullptr) - && (keyEvent->modifiers() == Qt::ControlModifier) - && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { - return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1); - } else if (keyEvent->key() == Qt::Key_Delete) { - return deleteSelection(itemView); - } else if (keyEvent->key() == Qt::Key_Space) { - return toggleSelection(itemView); - } - return QAbstractItemModel::eventFilter(obj, event); - } - return QAbstractItemModel::eventFilter(obj, event); -} - -//note: caller needs to make sure sort proxy is updated -void ModList::enableSelected(const QItemSelectionModel *selectionModel) -{ - if (selectionModel->hasSelection()) { - QList modsToEnable; - for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { - int modID = m_Profile->modIndexByPriority(row.data().toInt()); - modsToEnable.append(modID); - } - m_Profile->setModsEnabled(modsToEnable, QList()); - } -} - -//note: caller needs to make sure sort proxy is updated -void ModList::disableSelected(const QItemSelectionModel *selectionModel) -{ - if (selectionModel->hasSelection()) { - QList modsToDisable; - for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { - int modID = m_Profile->modIndexByPriority(row.data().toInt()); - modsToDisable.append(modID); - } - m_Profile->setModsEnabled(QList(), modsToDisable); - } -} +/* +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 . +*/ + +#include "modlist.h" + +#include "messagedialog.h" +#include "installationtester.h" +#include "qtgroupingproxy.h" +#include "viewmarkingscrollbar.h" +#include "modlistsortproxy.h" +#include "pluginlist.h" +#include "settings.h" +#include "modinforegular.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +using namespace MOBase; + + +ModList::ModList(PluginContainer *pluginContainer, QObject *parent) + : QAbstractItemModel(parent) + , m_Profile(nullptr) + , m_NexusInterface(nullptr) + , m_Modified(false) + , m_FontMetrics(QFont()) + , m_DropOnItems(false) + , m_PluginContainer(pluginContainer) +{ + m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)")); + m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface")); + m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes")); + m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive")); + m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)")); + m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin")); + m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher")); + m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music")); + m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures")); + m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", tr("MCM Configuration")); + m_ContentIcons[ModInfo::CONTENT_INI] = std::make_tuple(":/MO/gui/content/inifile", tr("INI files")); + m_ContentIcons[ModInfo::CONTENT_MODGROUP] = std::make_tuple(":/MO/gui/content/modgroup", tr("ModGroup files")); + + m_LastCheck.start(); +} + +ModList::~ModList() +{ + m_ModStateChanged.disconnect_all_slots(); + m_ModMoved.disconnect_all_slots(); +} + +void ModList::setProfile(Profile *profile) +{ + m_Profile = profile; +} + +int ModList::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return ModInfo::getNumMods(); + } else { + return 0; + } +} + +bool ModList::hasChildren(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return ModInfo::getNumMods() > 0; + } else { + return false; + } +} + + +int ModList::columnCount(const QModelIndex &) const +{ + return COL_LASTCOLUMN + 1; +} + + +QVariant ModList::getOverwriteData(int column, int role) const +{ + switch (role) { + case Qt::DisplayRole: { + if (column == 0) { + return "Overwrite"; + } else { + return QVariant(); + } + } break; + case Qt::CheckStateRole: { + if (column == 0) { + return Qt::Checked; + } else { + return QVariant(); + } + } break; + case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + case Qt::UserRole: return -1; + case Qt::ForegroundRole: return QBrush(Qt::red); + case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)"); + default: return QVariant(); + } +} + + +QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: return tr("Backup"); + case ModInfo::FLAG_SEPARATOR: return tr("Separator"); + case ModInfo::FLAG_INVALID: return tr("No valid game data"); + case ModInfo::FLAG_NOTENDORSED: return tr("Not endorsed yet"); + case ModInfo::FLAG_NOTES: { + QStringList output; + if (!modInfo->comments().isEmpty()) + output << QString("%1").arg(modInfo->comments()); + if (!modInfo->notes().isEmpty()) + output << QString("%1").arg(modInfo->notes()); + return output.join(""); + } + case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); + case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); + case ModInfo::FLAG_ALTERNATE_GAME: return tr("
This mod is for a different game, " + "make sure it's compatible or it could cause crashes."); + default: return ""; + } +} + + +QVariantList ModList::contentsToIcons(const std::vector &contents) const +{ + QVariantList result; + std::set contentsSet(contents.begin(), contents.end()); + for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { + if (contentsSet.find(iter->first) != contentsSet.end()) { + result.append(std::get<0>(iter->second)); + } else { + result.append(QString()); + } + } + return result; +} + +QString ModList::contentsToToolTip(const std::vector &contents) const +{ + QString result(""); + + std::set contentsSet(contents.begin(), contents.end()); + for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { + if (contentsSet.find(iter->first) != contentsSet.end()) { + result.append(QString("" + "") + .arg(std::get<0>(iter->second)).arg(std::get<1>(iter->second))); + } + } + result.append("
%2
"); + return result; +} + + +QVariant ModList::data(const QModelIndex &modelIndex, int role) const +{ + if (m_Profile == nullptr) return QVariant(); + if (!modelIndex.isValid()) return QVariant(); + unsigned int modIndex = modelIndex.row(); + int column = modelIndex.column(); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if ((role == Qt::DisplayRole) || + (role == Qt::EditRole)) { + if ((column == COL_FLAGS) + || (column == COL_CONTENT)) { + return QVariant(); + } else if (column == COL_NAME) { + auto flags = modInfo->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) + { + return modInfo->name().replace("_separator", ""); + } + else + return modInfo->name(); + } else if (column == COL_VERSION) { + VersionInfo verInfo = modInfo->getVersion(); + QString version = verInfo.displayString(); + if (role != Qt::EditRole) { + if (version.isEmpty() && modInfo->canBeUpdated()) { + version = "?"; + } + } + return version; + } else if (column == COL_PRIORITY) { + int priority = modInfo->getFixedPriority(); + if (priority != INT_MIN) { + return QVariant(); // hide priority for mods where it's fixed + } else { + return m_Profile->getModPriority(modIndex); + } + } else if (column == COL_MODID) { + int modID = modInfo->getNexusID(); + if (modID >= 0) { + return modID; + } + else { + return QVariant(); + } + } else if (column == COL_GAME) { + if (m_PluginContainer != nullptr) { + for (auto game : m_PluginContainer->plugins()) { + if (game->gameShortName().compare(modInfo->getGameName(), Qt::CaseInsensitive) == 0) + return game->gameName(); + } + } + return modInfo->getGameName(); + } else if (column == COL_CATEGORY) { + if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + return tr("Non-MO"); + } else { + int category = modInfo->getPrimaryCategory(); + if (category != -1) { + CategoryFactory &categoryFactory = CategoryFactory::instance(); + if (categoryFactory.categoryExists(category)) { + try { + int categoryIdx = categoryFactory.getCategoryIndex(category); + return categoryFactory.getCategoryName(categoryIdx); + } catch (const std::exception &e) { + qCritical("failed to retrieve category name: %s", e.what()); + return QString(); + } + } else { + qWarning("category %d doesn't exist (may have been removed)", category); + modInfo->setCategory(category, false); + return QString(); + } + } else { + return QVariant(); + } + } + } else if (column == COL_INSTALLTIME) { + // display installation time for mods that can be updated + if (modInfo->creationTime().isValid()) { + return modInfo->creationTime(); + } else { + return QVariant(); + } + } else if (column == COL_NOTES) { + return modInfo->comments(); + } else { + return tr("invalid"); + } + } else if ((role == Qt::CheckStateRole) && (column == 0)) { + if (modInfo->canBeEnabled()) { + return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked; + } else { + return QVariant(); + } + } else if (role == Qt::TextAlignmentRole) { + auto flags = modInfo->getFlags(); + if (column == COL_NAME) { + if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { + return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } + } else if (column == COL_VERSION) { + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + } else if (column == COL_NOTES) { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + } + } else if (role == Qt::UserRole) { + if (column == COL_CATEGORY) { + QVariantList categoryNames; + std::set categories = modInfo->getCategories(); + CategoryFactory &categoryFactory = CategoryFactory::instance(); + for (auto iter = categories.begin(); iter != categories.end(); ++iter) { + categoryNames.append(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*iter))); + } + if (categoryNames.count() != 0) { + return categoryNames; + } else { + return QVariant(); + } + } else if (column == COL_PRIORITY) { + int priority = modInfo->getFixedPriority(); + if (priority != INT_MIN) { + return priority; + } else { + return m_Profile->getModPriority(modIndex); + } + } else { + return modInfo->getNexusID(); + } + } else if (role == Qt::UserRole + 1) { + return modIndex; + } else if (role == Qt::UserRole + 2) { + switch (column) { + case COL_MODID: return QtGroupingProxy::AGGR_FIRST; + case COL_VERSION: return QtGroupingProxy::AGGR_MAX; + case COL_CATEGORY: return QtGroupingProxy::AGGR_FIRST; + case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN; + default: return QtGroupingProxy::AGGR_NONE; + } + } else if (role == Qt::UserRole + 3) { + return contentsToIcons(modInfo->getContents()); + } else if (role == Qt::UserRole + 4) { + return modInfo->getGameName(); + } else if (role == Qt::FontRole) { + QFont result; + auto flags = modInfo->getFlags(); + if (column == COL_NAME) { + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) + { + //result.setCapitalization(QFont::AllUppercase); + result.setItalic(true); + //result.setUnderline(true); + result.setBold(true); + } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { + result.setItalic(true); + } + } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) { + result.setItalic(true); + } else if (column == COL_VERSION) { + if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { + result.setWeight(QFont::Bold); + } + if (modInfo->canBeUpdated()) { + result.setItalic(true); + } + } + return result; + } else if (role == Qt::DecorationRole) { + if (column == COL_VERSION) { + if (modInfo->updateAvailable()) { + return QIcon(":/MO/gui/update_available"); + } else if (modInfo->downgradeAvailable()) { + return QIcon(":/MO/gui/warning"); + } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) { + return QIcon(":/MO/gui/version_date"); + } + } + return QVariant(); + } else if (role == Qt::ForegroundRole) { + if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid()) { + return Settings::getIdealTextColor(modInfo->getColor()); + } else if (column == COL_NAME) { + int highlight = modInfo->getHighlight(); + if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) + return QBrush(Qt::darkRed); + else if (highlight & ModInfo::HIGHLIGHT_INVALID) + return QBrush(Qt::darkGray); + } else if (column == COL_VERSION) { + if (!modInfo->getNewestVersion().isValid()) { + return QVariant(); + } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { + return QBrush(Qt::red); + } else { + return QBrush(Qt::darkGreen); + } + } + return QVariant(); + } else if ((role == Qt::BackgroundRole) + || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + bool overwrite = m_Overwrite.find(modIndex) != m_Overwrite.end(); + bool archiveOverwrite = m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end(); + bool archiveLooseOverwrite = m_ArchiveLooseOverwrite.find(modIndex) != m_ArchiveLooseOverwrite.end(); + bool overwritten = m_Overwritten.find(modIndex) != m_Overwritten.end(); + bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end(); + bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end(); + if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { + return Settings::instance().modlistContainsPluginColor(); + } else if (overwritten || archiveLooseOverwritten) { + return Settings::instance().modlistOverwritingLooseColor(); + } else if (overwrite || archiveLooseOverwrite) { + return Settings::instance().modlistOverwrittenLooseColor(); + } else if (archiveOverwritten) { + return Settings::instance().modlistOverwritingArchiveColor(); + } else if (archiveOverwrite) { + return Settings::instance().modlistOverwrittenArchiveColor(); + } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) + && modInfo->getColor().isValid() + && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) + || Settings::instance().colorSeparatorScrollbar())) { + return modInfo->getColor(); + } else { + return QVariant(); + } + } else if (role == Qt::ToolTipRole) { + if (column == COL_FLAGS) { + QString result; + + for (ModInfo::EFlag flag : modInfo->getFlags()) { + if (result.length() != 0) result += "
"; + result += getFlagText(flag, modInfo); + } + + return result; + } else if (column == COL_CONTENT) { + return contentsToToolTip(modInfo->getContents()); + } else if (column == COL_NAME) { + try { + return modInfo->getDescription(); + } catch (const std::exception &e) { + qCritical("invalid mod description: %s", e.what()); + return QString(); + } + } else if (column == COL_VERSION) { + QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->getVersion().displayString(3)).arg(modInfo->getNewestVersion().displayString(3)); + if (modInfo->downgradeAvailable()) { + text += "
" + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn " + "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. " + "Either way you may want to \"upgrade\"."); + } + if (modInfo->getNexusID() > 0) { + if (!modInfo->canBeUpdated()) { + text += "
" + tr("This mod was last checked on %1. It will be available to check after %2.") + .arg(modInfo->getLastNexusUpdate().toLocalTime().toString(Qt::DefaultLocaleShortDate)) + .arg(modInfo->getLastNexusUpdate().toLocalTime().addSecs(3600).time().toString(Qt::DefaultLocaleShortDate)); + } else { + text += "
" + tr("This mod is eligible for an update check."); + } + } + return text; + } else if (column == COL_CATEGORY) { + const std::set &categories = modInfo->getCategories(); + std::wostringstream categoryString; + categoryString << ToWString(tr("Categories:
")); + CategoryFactory &categoryFactory = CategoryFactory::instance(); + for (std::set::const_iterator catIter = categories.begin(); + catIter != categories.end(); ++catIter) { + if (catIter != categories.begin()) { + categoryString << " , "; + } + try { + categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; + } catch (const std::exception &e) { + qCritical("failed to generate tooltip: %s", e.what()); + return QString(); + } + } + + return ToQString(categoryString.str()); + } else if (column == COL_NOTES) { + return getFlagText(ModInfo::FLAG_NOTES, modInfo); + } else { + return QVariant(); + } + } else { + return QVariant(); + } +} + + +bool ModList::renameMod(int index, const QString &newName) +{ + QString nameFixed = newName; + if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) { + MessageDialog::showMessage(tr("Invalid name"), nullptr); + return false; + } + + if (ModList::allMods().contains(nameFixed, Qt::CaseInsensitive) && nameFixed.toLower()!=ModInfo::getByIndex(index)->name().toLower() ) { + MessageDialog::showMessage(tr("Name is already in use by another mod"), nullptr); + return false; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + QString oldName = modInfo->name(); + if (nameFixed != oldName) { + // before we rename, ensure there is no scheduled asynchronous to rewrite + m_Profile->cancelModlistWrite(); + + + if (modInfo->setName(nameFixed)) + // Notice there is a good chance that setName() updated the modinfo indexes + // the modRenamed() call will refresh the indexes in the current profile + // and update the modlists in all profiles + emit modRenamed(oldName, nameFixed); + } + + // invalidate the currently displayed state of this list + notifyChange(-1); + return true; +} + +bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (m_Profile == nullptr) return false; + + if (static_cast(index.row()) >= ModInfo::getNumMods()) { + return false; + } + + int modID = index.row(); + + ModInfo::Ptr info = ModInfo::getByIndex(modID); + IModList::ModStates oldState = state(modID); + + bool result = false; + + emit aboutToChangeData(); + + if (role == Qt::CheckStateRole) { + bool enabled = value.toInt() == Qt::Checked; + if (m_Profile->modEnabled(modID) != enabled) { + m_Profile->setModEnabled(modID, enabled); + m_Modified = true; + m_LastCheck.restart(); + emit modlistChanged(index, role); + } + result = true; + emit dataChanged(index, index); + } else if (role == Qt::EditRole) { + switch (index.column()) { + case COL_NAME: { + auto flags = info->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()) + { + result = renameMod(modID, value.toString() + "_separator"); + } + else + result = renameMod(modID, value.toString()); + } break; + case COL_PRIORITY: { + bool ok = false; + int newPriority = value.toInt(&ok); + if (ok && newPriority < 0) { + newPriority = 0; + } + if (ok) { + m_Profile->setModPriority(modID, newPriority); + + emit modorder_changed(); + result = true; + } else { + result = false; + } + } break; + case COL_MODID: { + bool ok = false; + int newID = value.toInt(&ok); + if (ok) { + info->setNexusID(newID); + emit modlistChanged(index, role); + result = true; + } else { + result = false; + } + } break; + case COL_VERSION: { + VersionInfo::VersionScheme scheme = info->getVersion().scheme(); + VersionInfo version(value.toString(), scheme, true); + if (version.isValid()) { + info->setVersion(version); + result = true; + } else { + result = false; + } + } break; + case COL_NOTES: { + info->setComments(value.toString()); + result = true; + } break; + default: { + qWarning("edit on column \"%s\" not supported", + getColumnName(index.column()).toUtf8().constData()); + result = false; + } break; + } + if (result) { + emit dataChanged(index, index); + } + } + + emit postDataChanged(); + + IModList::ModStates newState = state(modID); + if (oldState != newState) { + try { + m_ModStateChanged(info->name(), newState); + } catch (const std::exception &e) { + qCritical("failed to invoke state changed notification: %s", e.what()); + } catch (...) { + qCritical("failed to invoke state changed notification: unknown exception"); + } + } + + return result; +} + + +QVariant ModList::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole) { + return getColumnName(section); + } else if (role == Qt::ToolTipRole) { + return getColumnToolTip(section); + } else if (role == Qt::TextAlignmentRole) { + return QVariant(Qt::AlignCenter); + } else if (role == Qt::SizeHintRole) { + QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); + temp.rwidth() += 20; + temp.rheight() += 12; + return temp; + } + } + return QAbstractItemModel::headerData(section, orientation, role); +} + + +Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const +{ + Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); + if (modelIndex.internalId() < 0) { + return Qt::ItemIsEnabled; + } + if (modelIndex.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row()); + std::vector flags = modInfo->getFlags(); + if (modInfo->getFixedPriority() == INT_MIN) { + result |= Qt::ItemIsDragEnabled; + result |= Qt::ItemIsUserCheckable; + if ((modelIndex.column() == COL_PRIORITY) || + (modelIndex.column() == COL_VERSION) || + (modelIndex.column() == COL_MODID)) { + result |= Qt::ItemIsEditable; + } + if (((modelIndex.column() == COL_NAME) || + (modelIndex.column() == COL_NOTES)) + && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end())) { + result |= Qt::ItemIsEditable; + } + } + if (m_DropOnItems + && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { + result |= Qt::ItemIsDropEnabled; + } + } else { + if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled; + } + return result; +} + + +QStringList ModList::mimeTypes() const +{ + QStringList result = QAbstractItemModel::mimeTypes(); + result.append("text/uri-list"); + return result; +} + +QMimeData *ModList::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *result = QAbstractItemModel::mimeData(indexes); + result->setData("text/plain", "mod"); + return result; +} + +void ModList::changeModPriority(std::vector sourceIndices, int newPriority) +{ + if (m_Profile == nullptr) return; + + emit layoutAboutToBeChanged(); + Profile *profile = m_Profile; + + // sort the moving mods by ascending priorities + std::sort(sourceIndices.begin(), sourceIndices.end(), + [profile](const int &LHS, const int &RHS) { + return profile->getModPriority(LHS) > profile->getModPriority(RHS); + }); + + // move mods that are decreasing in priority + for (std::vector::const_iterator iter = sourceIndices.begin(); + iter != sourceIndices.end(); ++iter) { + int oldPriority = profile->getModPriority(*iter); + if (oldPriority > newPriority) { + profile->setModPriority(*iter, newPriority); + m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + } + } + + // sort the moving mods by descending priorities + std::sort(sourceIndices.begin(), sourceIndices.end(), + [profile](const int &LHS, const int &RHS) { + return profile->getModPriority(LHS) < profile->getModPriority(RHS); + }); + + // if at least one mod is increasing in priority, the target index is + // that of the row BELOW the dropped location, otherwise it's the one above + for (std::vector::const_iterator iter = sourceIndices.begin(); + iter != sourceIndices.end(); ++iter) { + int oldPriority = profile->getModPriority(*iter); + if (oldPriority < newPriority) { + --newPriority; + break; + } + } + + // move mods that are increasing in priority + for (std::vector::const_iterator iter = sourceIndices.begin(); + iter != sourceIndices.end(); ++iter) { + int oldPriority = profile->getModPriority(*iter); + if (oldPriority < newPriority) { + profile->setModPriority(*iter, newPriority); + m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + } + } + + emit layoutChanged(); + + emit modorder_changed(); +} + + +void ModList::changeModPriority(int sourceIndex, int newPriority) +{ + if (m_Profile == nullptr) return; + emit layoutAboutToBeChanged(); + + m_Profile->setModPriority(sourceIndex, newPriority); + + emit layoutChanged(); + + emit modorder_changed(); +} + +void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) +{ + m_Overwrite = overwrite; + m_Overwritten = overwritten; + notifyChange(0, rowCount() - 1); +} + +void ModList::setArchiveOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) +{ + m_ArchiveOverwrite = overwrite; + m_ArchiveOverwritten = overwritten; + notifyChange(0, rowCount() - 1); +} + +void ModList::setArchiveLooseOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) +{ + m_ArchiveLooseOverwrite = overwrite; + m_ArchiveLooseOverwritten = overwritten; + notifyChange(0, rowCount() - 1); +} + +void ModList::setPluginContainer(PluginContainer *pluginContianer) +{ + m_PluginContainer = pluginContianer; +} + +bool ModList::modInfoAboutToChange(ModInfo::Ptr info) +{ + if (m_ChangeInfo.name.isEmpty()) { + m_ChangeInfo.name = info->name(); + m_ChangeInfo.state = state(info->name()); + return true; + } else { + return false; + } +} + +void ModList::modInfoChanged(ModInfo::Ptr info) +{ + if (info->name() == m_ChangeInfo.name) { + IModList::ModStates newState = state(info->name()); + if (m_ChangeInfo.state != newState) { + m_ModStateChanged(info->name(), newState); + } + + int row = ModInfo::getIndex(info->name()); + info->testValid(); + emit aboutToChangeData(); + emit dataChanged(index(row, 0), index(row, columnCount())); + emit postDataChanged(); + } else { + qCritical("modInfoChanged not called after modInfoAboutToChange"); + } + m_ChangeInfo.name = QString(); +} + +void ModList::disconnectSlots() { + m_ModMoved.disconnect_all_slots(); + m_ModStateChanged.disconnect_all_slots(); +} + +int ModList::timeElapsedSinceLastChecked() const +{ + return m_LastCheck.elapsed(); +} + +void ModList::highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry) +{ + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + ModInfo::getByIndex(i)->setPluginSelected(false); + } + for (QModelIndex idx : selection->selectedRows(PluginList::COL_NAME)) { + QString modName = idx.data().toString(); + + const MOShared::FileEntry::Ptr fileEntry = directoryEntry.findFile(modName.toStdWString()); + if (fileEntry.get() != nullptr) { + bool archive = false; + std::vector>> origins; + { + std::vector>> alternatives = fileEntry->getAlternatives(); + origins.insert(origins.end(), std::pair>(fileEntry->getOrigin(archive), fileEntry->getArchive())); + } + for (auto originInfo : origins) { + MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originInfo.first); + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) { + ModInfo::getByIndex(i)->setPluginSelected(true); + break; + } + } + } + } + } + notifyChange(0, rowCount() - 1); +} + +IModList::ModStates ModList::state(unsigned int modIndex) const +{ + IModList::ModStates result; + if (modIndex != UINT_MAX) { + result |= IModList::STATE_EXISTS; + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if (modInfo->isEmpty()) { + result |= IModList::STATE_EMPTY; + } + if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) { + result |= IModList::STATE_ENDORSED; + } + if (modInfo->isValid()) { + result |= IModList::STATE_VALID; + } + if (modInfo->isRegular()) { + QSharedPointer modInfoRegular = modInfo.staticCast(); + if (modInfoRegular->isAlternate() && !modInfoRegular->isConverted()) + result |= IModList::STATE_ALTERNATE; + if (!modInfo->isValid() && modInfoRegular->isValidated()) + result |= IModList::STATE_VALID; + } + if (modInfo->canBeEnabled()) { + if (m_Profile->modEnabled(modIndex)) { + result |= IModList::STATE_ACTIVE; + } + } else { + result |= IModList::STATE_ESSENTIAL; + } + } + return result; +} + +QString ModList::displayName(const QString &internalName) const +{ + unsigned int modIndex = ModInfo::getIndex(internalName); + if (modIndex == UINT_MAX) { + // might be better to throw an exception? + return internalName; + } else { + return ModInfo::getByIndex(modIndex)->name(); + } +} + +QStringList ModList::allMods() const +{ + QStringList result; + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + result.append(ModInfo::getByIndex(i)->internalName()); + } + return result; +} + +IModList::ModStates ModList::state(const QString &name) const +{ + unsigned int modIndex = ModInfo::getIndex(name); + + return state(modIndex); +} + +bool ModList::setActive(const QString &name, bool active) +{ + unsigned int modIndex = ModInfo::getIndex(name); + if (modIndex == UINT_MAX) { + return false; + } else { + m_Profile->setModEnabled(modIndex, active); + + IModList::ModStates newState = state(modIndex); + m_ModStateChanged(name, newState); + return true; + } +} + +int ModList::priority(const QString &name) const +{ + unsigned int modIndex = ModInfo::getIndex(name); + if (modIndex == UINT_MAX) { + return -1; + } else { + return m_Profile->getModPriority(modIndex); + } +} + +bool ModList::setPriority(const QString &name, int newPriority) +{ + if ((newPriority < 0) || (newPriority >= static_cast(m_Profile->numRegularMods()))) { + return false; + } + + unsigned int modIndex = ModInfo::getIndex(name); + if (modIndex == UINT_MAX) { + return false; + } else { + m_Profile->setModPriority(modIndex, newPriority); + notifyChange(modIndex); + return true; + } +} + +bool ModList::onModStateChanged(const std::function &func) +{ + auto conn = m_ModStateChanged.connect(func); + return conn.connected(); +} + +bool ModList::onModMoved(const std::function &func) +{ + auto conn = m_ModMoved.connect(func); + return conn.connected(); +} + +bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent) +{ + QStringList source; + QStringList target; + + if (row == -1) { + row = parent.row(); + } + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + QDir modDirectory(modInfo->absolutePath()); + QDir gameDirectory(Settings::instance().getOverwriteDirectory()); + + unsigned int overwriteIndex = ModInfo::findMod([] (ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); + + QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); + + for (const QUrl &url : mimeData->urls()) { + //qDebug("URL drop requested: %s", qUtf8Printable(url.toLocalFile())); + if (!url.isLocalFile()) { + qDebug("URL drop ignored: Not a local file."); + continue; + } + QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile()); + if (relativePath.startsWith("..")) { + qDebug("URL drop ignored: relative path starts with .."); + continue; + } + source.append(url.toLocalFile()); + target.append(modDirectory.absoluteFilePath(relativePath)); + emit fileMoved(relativePath, overwriteName, modInfo->name()); + } + + if (source.count() != 0) { + if (!shellMove(source, target)) { + qDebug("Move failed %lu",::GetLastError()); + return false; + } + } + + if (!modInfo->isValid()) { + modInfo->testValid(); + } + + return true; +} + +bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) +{ + + try { + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + std::vector sourceRows; + + while (!stream.atEnd()) { + int sourceRow, col; + QMap roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { + sourceRows.push_back(sourceRow); + } + } + + if (row == -1) { + row = parent.row(); + } + + if ((row < 0) || (static_cast(row) >= ModInfo::getNumMods())) { + return false; + } + + int newPriority = 0; + { + if ((row < 0) || (row > static_cast(m_Profile->numRegularMods()))) { + newPriority = m_Profile->numRegularMods() + 1; + } else { + newPriority = m_Profile->getModPriority(row); + } + if (newPriority == -1) { + newPriority = m_Profile->numRegularMods() + 1; + } + } + changeModPriority(sourceRows, newPriority); + } catch (const std::exception &e) { + reportError(tr("drag&drop failed: %1").arg(e.what())); + } + + return false; +} + + +bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) { + return true; + } + + if (m_Profile == nullptr) return false; + + if (mimeData->hasUrls()) { + return dropURLs(mimeData, row, parent); + } else if (mimeData->hasText()) { + return dropMod(mimeData, row, parent); + } else { + return false; + } +} + +void ModList::removeRowForce(int row, const QModelIndex &parent) +{ + if (static_cast(row) >= ModInfo::getNumMods()) { + return; + } + if (m_Profile == nullptr) return; + + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + + bool wasEnabled = m_Profile->modEnabled(row); + + m_Profile->setModEnabled(row, false); + + m_Profile->cancelModlistWrite(); + beginRemoveRows(parent, row, row); + ModInfo::removeMod(row); + endRemoveRows(); + m_Profile->refreshModStatus(); // removes the mod from the status list + m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed + + if (wasEnabled) { + emit removeOrigin(modInfo->name()); + } + + emit modUninstalled(modInfo->getInstallationFile()); +} + +bool ModList::removeRows(int row, int count, const QModelIndex &parent) +{ + if (static_cast(row) >= ModInfo::getNumMods()) { + return false; + } + if (m_Profile == nullptr) { + return false; + } + + bool success = false; + + if (count == 1) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + std::vector flags = modInfo->getFlags(); + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) && (QDir(modInfo->absolutePath()).count() > 2)) { + emit clearOverwrite(); + success = true; + } + } + + for (int i = 0; i < count; ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(row + i); + if (!modInfo->isRegular()) { + continue; + } + + success = true; + + QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), + tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()), + QMessageBox::Yes | QMessageBox::No); + + if (confirmBox.exec() == QMessageBox::Yes) { + m_Profile->setModEnabled(row + i, false); + removeRowForce(row + i, parent); + } + } + + return success; +} + + +void ModList::notifyChange(int rowStart, int rowEnd) +{ + if (rowStart < 0) { + m_Overwrite.clear(); + m_Overwritten.clear(); + m_ArchiveOverwrite.clear(); + m_ArchiveOverwritten.clear(); + m_ArchiveLooseOverwrite.clear(); + m_ArchiveLooseOverwritten.clear(); + beginResetModel(); + endResetModel(); + } else { + if (rowEnd == -1) { + rowEnd = rowStart; + } + emit dataChanged(this->index(rowStart, 0), this->index(rowEnd, this->columnCount() - 1)); + } +} + + +QModelIndex ModList::index(int row, int column, const QModelIndex&) const +{ + if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { + return QModelIndex(); + } + QModelIndex res = createIndex(row, column, row); + return res; +} + + +QModelIndex ModList::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +QMap ModList::itemData(const QModelIndex &index) const +{ + QMap result = QAbstractItemModel::itemData(index); + result[Qt::UserRole] = data(index, Qt::UserRole); + return result; +} + + +void ModList::dropModeUpdate(bool dropOnItems) +{ + if (m_DropOnItems != dropOnItems) { + m_DropOnItems = dropOnItems; + } +} + + +QString ModList::getColumnName(int column) +{ + switch (column) { + case COL_FLAGS: return tr("Flags"); + case COL_CONTENT: return tr("Content"); + case COL_NAME: return tr("Mod Name"); + case COL_VERSION: return tr("Version"); + case COL_PRIORITY: return tr("Priority"); + case COL_CATEGORY: return tr("Category"); + case COL_GAME: return tr("Source Game"); + case COL_MODID: return tr("Nexus ID"); + case COL_INSTALLTIME: return tr("Installation"); + case COL_NOTES: return tr("Notes"); + default: return tr("unknown"); + } +} + + +QString ModList::getColumnToolTip(int column) +{ + switch (column) { + case COL_NAME: return tr("Name of your mods"); + case COL_VERSION: return tr("Version of the mod (if available)"); + case COL_PRIORITY: return tr("Installation priority of your mod. The higher, the more \"important\" it is and thus " + "overwrites files from mods with lower priority."); + case COL_CATEGORY: return tr("Category of the mod."); + case COL_GAME: return tr("The source game which was the origin of this mod."); + case COL_MODID: return tr("Id of the mod as used on Nexus."); + case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_CONTENT: return tr("Depicts the content of the mod:
" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + "
Game plugins (esp/esm/esl)
Interface
Meshes
BSA
Textures
Sounds
Music
Strings
Scripts (Papyrus)
Script Extender plugins
SkyProc Patcher
Mod Configuration Menu
INI files
ModGroup files
"); + case COL_INSTALLTIME: return tr("Time this mod was installed"); + case COL_NOTES: return tr("User notes about the mod"); + default: return tr("unknown"); + } +} + + +bool ModList::moveSelection(QAbstractItemView *itemView, int direction) +{ + QItemSelectionModel *selectionModel = itemView->selectionModel(); + + const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); + const QSortFilterProxyModel *filterModel = nullptr; + + while ((filterModel == nullptr) && (proxyModel != nullptr)) { + filterModel = qobject_cast(proxyModel); + if (filterModel == nullptr) { + proxyModel = qobject_cast(proxyModel->sourceModel()); + } + } + if (filterModel == nullptr) { + return true; + } + + int offset = -1; + if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) || + ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) { + offset = 1; + } + + QModelIndexList rows = selectionModel->selectedRows(); + if (direction > 0) { + for (int i = 0; i < rows.size() / 2; ++i) { + rows.swap(i, rows.size() - i - 1); + } + } + for (QModelIndex idx : rows) { + if (filterModel != nullptr) { + idx = filterModel->mapToSource(idx); + } + int newPriority = m_Profile->getModPriority(idx.row()) + offset; + if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { + m_Profile->setModPriority(idx.row(), newPriority); + notifyChange(idx.row()); + } + } + emit modorder_changed(); + return true; +} + +bool ModList::deleteSelection(QAbstractItemView *itemView) +{ + QItemSelectionModel *selectionModel = itemView->selectionModel(); + + QModelIndexList rows = selectionModel->selectedRows(); + if (rows.count() > 1) { + emit removeSelectedMods(); + } else if (rows.count() == 1) { + removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); + } + return true; +} + +bool ModList::toggleSelection(QAbstractItemView *itemView) +{ + emit aboutToChangeData(); + + QItemSelectionModel *selectionModel = itemView->selectionModel(); + + QList modsToEnable; + QList modsToDisable; + QModelIndexList dirtyMods; + for (QModelIndex idx : selectionModel->selectedRows()) { + int modId = idx.data(Qt::UserRole + 1).toInt(); + if (m_Profile->modEnabled(modId)) { + modsToDisable.append(modId); + dirtyMods.append(idx); + } else { + modsToEnable.append(modId); + dirtyMods.append(idx); + } + } + + m_Profile->setModsEnabled(modsToEnable, modsToDisable); + + emit modlistChanged(dirtyMods, 0); + + m_Modified = true; + m_LastCheck.restart(); + + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); + + emit postDataChanged(); + + return true; +} + +bool ModList::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::ContextMenu) { + QContextMenuEvent *contextEvent = static_cast(event); + QWidget *object = qobject_cast(obj); + if ((object != nullptr) && (contextEvent->reason() == QContextMenuEvent::Mouse)) { + emit requestColumnSelect(object->mapToGlobal(contextEvent->pos())); + + return true; + } + } else if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) { + QAbstractItemView *itemView = qobject_cast(obj); + QKeyEvent *keyEvent = static_cast(event); + + if ((itemView != nullptr) + && (keyEvent->modifiers() == Qt::ControlModifier) + && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { + return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1); + } else if (keyEvent->key() == Qt::Key_Delete) { + return deleteSelection(itemView); + } else if (keyEvent->key() == Qt::Key_Space) { + return toggleSelection(itemView); + } + return QAbstractItemModel::eventFilter(obj, event); + } + return QAbstractItemModel::eventFilter(obj, event); +} + +//note: caller needs to make sure sort proxy is updated +void ModList::enableSelected(const QItemSelectionModel *selectionModel) +{ + if (selectionModel->hasSelection()) { + QList modsToEnable; + for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { + int modID = m_Profile->modIndexByPriority(row.data().toInt()); + modsToEnable.append(modID); + } + m_Profile->setModsEnabled(modsToEnable, QList()); + } +} + +//note: caller needs to make sure sort proxy is updated +void ModList::disableSelected(const QItemSelectionModel *selectionModel) +{ + if (selectionModel->hasSelection()) { + QList modsToDisable; + for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { + int modID = m_Profile->modIndexByPriority(row.data().toInt()); + modsToDisable.append(modID); + } + m_Profile->setModsEnabled(QList(), modsToDisable); + } +} -- cgit v1.3.1 From 6b9e94473addf468224ea2b0521ab724d5842cb5 Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 31 Jan 2019 00:36:41 -0600 Subject: Various fixes and updates * Add second api type for mod info to segment updates from basic desc * Add saved nexus file type and factor it into the update display * Fix some issues with how we were checking for 'latest update' files --- src/downloadmanager.cpp | 3 ++- src/mainwindow.cpp | 36 ++++++++++++++++++++---------------- src/mainwindow.h | 2 +- src/modinfo.h | 12 ++++++++++++ src/modinfobackup.h | 5 +++++ src/modinfodialog.cpp | 1 + src/modinfoforeign.h | 6 ++++-- src/modinfooverwrite.h | 6 ++++-- src/modinforegular.cpp | 20 ++++++++++++++++++-- src/modinforegular.h | 14 ++++++++++++++ src/modinfoseparator.h | 6 ++++-- src/modlist.cpp | 5 +++++ src/nexusinterface.cpp | 32 +++++++++++++++++++++++++------- src/nexusinterface.h | 43 ++++++++++++++++++++++++++++++++++++++++--- 14 files changed, 155 insertions(+), 36 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index f5e4d688..32d05230 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1004,7 +1004,8 @@ QString DownloadManager::getFileTypeString(int fileType) case 2: return tr("Update"); case 3: return tr("Optional"); case 4: return tr("Old"); - case 5: return tr("Misc"); + case 5: return tr("Miscellaneous"); + case 6: return tr("Deleted"); default: return tr("Unknown"); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6933efda..064f817d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -441,7 +441,7 @@ MainWindow::MainWindow(QSettings &initSettings m_ModUpdateTimer.setSingleShot(false); connect(&m_ModUpdateTimer, SIGNAL(timeout()), this, SLOT(modUpdateCheck())); - m_ModUpdateTimer.start(300 * 1000); + //m_ModUpdateTimer.start(300 * 1000); setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); FileDialogMemory::restore(initSettings); @@ -496,7 +496,7 @@ MainWindow::MainWindow(QSettings &initSettings updatePluginCount(); updateModCount(); - modUpdateCheck(); + //modUpdateCheck(); } @@ -5472,9 +5472,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD QVariantMap resultInfo = resultData.toMap(); QList files = resultInfo["files"].toList(); QList fileUpdates = resultInfo["file_updates"].toList(); - bool foundUpdate = false; m_ModsToUpdate--; - bool sameNexus = false; QString gameNameReal; for (IPluginGame *game : m_PluginContainer.plugins()) { if (game->gameNexusName() == gameName) { @@ -5485,7 +5483,19 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD std::vector modsList = ModInfo::getByModID(gameNameReal, modID); for (auto mod : modsList) { + bool foundUpdate = false; + bool oldFile = false; QString installedFile = mod->getInstallationFile(); + QVariantMap foundFile; + for (auto file : files) { + QVariantMap fileData = file.toMap(); + if (fileData["file_name"].toString().compare(installedFile, Qt::CaseInsensitive) == 0) { + foundFile = fileData; + mod->setNexusFileStatus(foundFile["category_id"].toInt()); + if (mod->getNexusFileStatus() == 4 || mod->getNexusFileStatus() == 6) + oldFile = true; + } + } for (auto update : fileUpdates) { QVariantMap updateData = update.toMap(); // Locate the current install file as an update @@ -5514,17 +5524,11 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD } break; - } else if (installedFile == updateData["new_file_name"]) { + } else if (installedFile == updateData["new_file_name"].toString()) { // This is a safety mechanism if this is the latest update file so we don't use the mod version - if (!foundUpdate) { - for (auto file : files) { - QVariantMap fileData = file.toMap(); - if (fileData["file_id"].toInt() == updateData["new_file_id"]) { - mod->setVersion(fileData["version"].toString()); - mod->setNewestVersion(fileData["version"].toString()); - foundUpdate = true; - } - } + if (!foundUpdate && !oldFile) { + foundUpdate = true; + mod->setNewestVersion(foundFile["version"].toString()); } } } @@ -5535,7 +5539,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD mod->updateNXMInfo(); } else { // Scrape mod data here so we can use the mod version if no file update was located - NexusInterface::instance(&m_PluginContainer)->requestDescription(gameName, modID, this, QVariant(), QString()); + NexusInterface::instance(&m_PluginContainer)->requestModInfo(gameName, modID, this, QVariant(), QString()); } } @@ -5546,7 +5550,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD } } -void MainWindow::nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) +void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) { QVariantMap result = resultData.toMap(); QString gameNameReal; diff --git a/src/mainwindow.h b/src/mainwindow.h index 65ca99dd..3f3a5457 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -512,7 +512,7 @@ private slots: void modUpdateCheck(); void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); - void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int); void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString); diff --git a/src/modinfo.h b/src/modinfo.h index 9c753752..ae9dd1f3 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -503,6 +503,18 @@ public: */ virtual QString getDescription() const = 0; + /** + * @return the nexus file status (aka category ID) + */ + virtual int getNexusFileStatus() const = 0; + + + /** + * @brief sets the file status (category ID) from Nexus + * @param status the status id of the installed file + */ + virtual void setNexusFileStatus(int status) = 0; + /** * @return comments for this mod */ diff --git a/src/modinfobackup.h b/src/modinfobackup.h index f74cd111..e88335e2 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -25,7 +25,12 @@ public: virtual std::vector getIniTweaks() const { return std::vector(); } virtual std::vector getFlags() const; virtual QString getDescription() const; + virtual int getNexusFileStatus() const { return 0; } + virtual void setNexusFileStatus(int) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual void setLastNexusQuery(QDateTime) {} + virtual QDateTime getLastNexusUpdate() const { return QDateTime(); } + virtual void setLastNexusUpdate(QDateTime) {} virtual void getNexusFiles(QList::const_iterator&, QList::const_iterator&) {} virtual QString getNexusDescription() const { return QString(); } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index db3e5de1..30d09579 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -851,6 +851,7 @@ QString ModInfoDialog::getFileCategory(int categoryID) case 2: return tr("Update"); case 3: return tr("Optional"); case 4: return tr("Old"); + case 5: return tr("Miscellaneous"); case 6: return tr("Deleted"); default: return tr("Unknown"); } diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 0702f268..6fcf56a1 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -47,10 +47,12 @@ public: virtual std::vector getFlags() const; virtual int getHighlight() const; virtual QString getDescription() const; + virtual int getNexusFileStatus() const { return 0; } + virtual void setNexusFileStatus(int) {} virtual QDateTime getLastNexusUpdate() const { return QDateTime(); } - virtual void setLastNexusUpdate(QDateTime time) {} + virtual void setLastNexusUpdate(QDateTime) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual void setLastNexusQuery(QDateTime time) {} + virtual void setLastNexusQuery(QDateTime) {} virtual QString getNexusDescription() const { return QString(); } virtual int getFixedPriority() const { return INT_MIN; } virtual QStringList archives(bool checkOnDisk = false) { return m_Archives; } diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index d2882301..9f77f648 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -50,10 +50,12 @@ public: virtual std::vector getFlags() const; virtual int getHighlight() const; virtual QString getDescription() const; + virtual int getNexusFileStatus() const { return 0; } + virtual void setNexusFileStatus(int) {} virtual QDateTime getLastNexusUpdate() const { return QDateTime(); } - virtual void setLastNexusUpdate(QDateTime time) {} + virtual void setLastNexusUpdate(QDateTime) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual void setLastNexusQuery(QDateTime time) {} + virtual void setLastNexusQuery(QDateTime) {} virtual QString getNexusDescription() const { return QString(); } virtual QStringList archives(bool checkOnDisk = false); virtual void addInstalledFile(int, int) {} diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index f2844084..89541a70 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -93,6 +93,7 @@ void ModInfoRegular::readMeta() m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); m_InstallationFile = metaFile.value("installationFile", "").toString(); m_NexusDescription = metaFile.value("nexusDescription", "").toString(); + m_NexusFileStatus = metaFile.value("nexusFileStatus", "1").toInt(); m_Repository = metaFile.value("repository", "Nexus").toString(); m_Converted = metaFile.value("converted", false).toBool(); m_Validated = metaFile.value("validated", false).toBool(); @@ -161,6 +162,7 @@ void ModInfoRegular::saveMeta() metaFile.setValue("notes", m_Notes); metaFile.setValue("nexusDescription", m_NexusDescription); metaFile.setValue("url", m_URL); + metaFile.setValue("nexusFileStatus", m_NexusFileStatus); metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); metaFile.setValue("lastNexusUpdate", m_LastNexusUpdate.toString(Qt::ISODate)); metaFile.setValue("converted", m_Converted); @@ -199,6 +201,9 @@ bool ModInfoRegular::updateAvailable() const if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { return false; } + if (m_NexusFileStatus == 4 || m_NexusFileStatus == 6) { + return true; + } return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); } @@ -271,8 +276,6 @@ bool ModInfoRegular::updateNXMInfo() m_NexusBridge.requestDescription(m_GameName, m_NexusID, QVariant()); return true; } - QString warning("Please wait until %1 to request updated mod info from Nexus!"); - qWarning() << warning.arg(target.toLocalTime().time().toString(Qt::DefaultLocaleShortDate)); return false; } @@ -613,6 +616,19 @@ QString ModInfoRegular::getDescription() const } } +int ModInfoRegular::getNexusFileStatus() const +{ + return m_NexusFileStatus; +} + +void ModInfoRegular::setNexusFileStatus(int status) +{ + m_NexusFileStatus = status; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + QString ModInfoRegular::comments() const { return m_Comments; diff --git a/src/modinforegular.h b/src/modinforegular.h index 8e3952e8..13ce7e42 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -285,6 +285,19 @@ public: */ virtual QString getDescription() const; + + /** + * @return the nexus file status (aka category ID) + */ + virtual int getNexusFileStatus() const; + + + /** + * @brief sets the file status (category ID) from Nexus + * @param status the status id of the installed file + */ + virtual void setNexusFileStatus(int status); + /** * @return comments for this mod */ @@ -402,6 +415,7 @@ private: bool m_IsAlternate; bool m_Converted; bool m_Validated; + bool m_NexusFileStatus; MOBase::VersionInfo m_NewestVersion; MOBase::VersionInfo m_IgnoredVersion; diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index 1f3be861..819ee686 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -43,10 +43,12 @@ public: virtual QString getInstallationFile() const { return ""; } virtual QString getURL() const { return ""; } virtual QString repository() const { return ""; } + virtual int getNexusFileStatus() const { return 0; } + virtual void setNexusFileStatus(int) {} virtual QDateTime getLastNexusUpdate() const { return QDateTime(); } - virtual void setLastNexusUpdate(QDateTime time) {} + virtual void setLastNexusUpdate(QDateTime) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual void setLastNexusQuery(QDateTime time) {} + virtual void setLastNexusQuery(QDateTime) {} virtual QDateTime creationTime() const { return QDateTime(); } virtual void getNexusFiles diff --git a/src/modlist.cpp b/src/modlist.cpp index 8c502f99..3f8ea4e1 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -458,6 +458,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. " "Either way you may want to \"upgrade\"."); } + if (modInfo->getNexusFileStatus() == 4) { + text += "
" + tr("This file has been marked as \"Old\". There is most likely an updated version of this file available."); + } else if (modInfo->getNexusFileStatus() == 6) { + text += "
" + tr("This file has been marked as \"Deleted\"! You may want to check for an update or remove the nexus ID from this mod!"); + } if (modInfo->getNexusID() > 0) { if (!modInfo->canBeUpdated()) { text += "
" + tr("This mod was last checked on %1. It will be available to check after %2.") diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 7481cf96..06fbe451 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -326,6 +326,22 @@ int NexusInterface::requestDescription(QString gameName, int modID, QObject *rec return requestInfo.m_ID; } +int NexusInterface::requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData, + const QString &subModule, MOBase::IPluginGame const *game) +{ + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), + receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant userData, QString gameName, const QString &subModule) @@ -504,12 +520,11 @@ void NexusInterface::nextRequest() if (!info.m_Reroute) { bool hasParams = false; switch (info.m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: { + case NXMRequestInfo::TYPE_DESCRIPTION: + case NXMRequestInfo::TYPE_MODINFO: { url = QString("%1/games/%2/mods/%3").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID); } break; - case NXMRequestInfo::TYPE_FILES: { - url = QString("%1/games/%2/mods/%3/files").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID); - } break; + case NXMRequestInfo::TYPE_FILES: case NXMRequestInfo::TYPE_GETUPDATES: { url = QString("%1/games/%2/mods/%3/files").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID); } break; @@ -594,18 +609,21 @@ void NexusInterface::requestFinished(std::list::iterator iter) case NXMRequestInfo::TYPE_DESCRIPTION: { emit nxmDescriptionAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); } break; + case NXMRequestInfo::TYPE_MODINFO: { + emit nxmModInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; case NXMRequestInfo::TYPE_FILES: { emit nxmFilesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); } break; + case NXMRequestInfo::TYPE_GETUPDATES: { + emit nxmUpdatesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; case NXMRequestInfo::TYPE_FILEINFO: { emit nxmFileInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); } break; case NXMRequestInfo::TYPE_DOWNLOADURL: { emit nxmDownloadURLsAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); } break; - case NXMRequestInfo::TYPE_GETUPDATES: { - emit nxmUpdatesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { emit nxmEndorsementToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); } break; diff --git a/src/nexusinterface.h b/src/nexusinterface.h index e60ebf3f..64f15961 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -151,6 +151,7 @@ public: /** * @brief request description for a mod * + * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in (assumed to be for the current game) * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) * @param userData user data to be returned with the result @@ -164,6 +165,7 @@ public: /** * @brief request description for a mod * + * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) * @param userData user data to be returned with the result @@ -173,12 +175,39 @@ public: int requestDescription(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + /** + * @brief request description for a mod + * + * @param gameName the game short name to support multiple game sources + * @param modID id of the mod caller is interested in (assumed to be for the current game) + * @param receiver the object to receive the result asynchronously via a signal (nxmModInfoAvailable) + * @param userData user data to be returned with the result + * @return int an id to identify the request + **/ + int requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestModInfo(gameName, modID, receiver, userData, subModule, getGame(gameName)); + } + + /** + * @brief request mod info + * + * @param gameName the game short name to support multiple game sources + * @param modID id of the mod caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmModInfoAvailable) + * @param userData user data to be returned with the result + * @param game Game with which the mod is associated + * @return int an id to identify the request + **/ + int requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game); + /** * @brief request nexus descriptions for multiple mods at once - * @param modIDs a list of ids of mods the caller is interested in + * @param modID id of the mod the caller is interested in * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) * @param userData user data to be returned with the result - * @param game the game with which the mods are associated + * @param gameName the game with which the mods are associated * @return int an id to identify the request */ int requestUpdates(const int &modID, QObject *receiver, QVariant userData, QString gameName, const QString &subModule); @@ -186,6 +215,7 @@ public: /** * @brief request a list of the files belonging to a mod * + * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in (assumed to be for the current game) * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) * @param userData user data to be returned with the result @@ -200,6 +230,7 @@ public: /** * @brief request a list of the files belonging to a mod * + * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) * @param userData user data to be returned with the result @@ -212,7 +243,7 @@ public: /** * @brief request info about a single file of a mod * - * @param game name of the game short name to request the download from + * @param gameName name of the game short name to request the download from * @param modID id of the mod caller is interested in (assumed to be for the current game) * @param fileID id of the file the caller is interested in * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) @@ -224,6 +255,7 @@ public: /** * @brief request the download url of a file * + * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in (assumed to be for the current game) * @param fileID id of the file the caller is interested in * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) @@ -238,6 +270,7 @@ public: /** * @brief request the download url of a file * + * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in * @param fileID id of the file the caller is interested in * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) @@ -248,6 +281,7 @@ public: int requestDownloadURL(QString gameName, int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); /** + * @param gameName the game short name to support multiple game sources * @brief toggle endorsement state of the mod * @param modID id of the mod (assumed to be for the current game) * @param endorse true if the mod should be endorsed, false for un-endorse @@ -261,6 +295,7 @@ public: } /** + * @param gameName the game short name to support multiple game sources * @brief toggle endorsement state of the mod * @param modID id of the mod * @param endorse true if the mod should be endorsed, false for un-endorse @@ -342,6 +377,7 @@ signals: void needLogin(); void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); @@ -374,6 +410,7 @@ private: QNetworkReply *m_Reply; enum Type { TYPE_DESCRIPTION, + TYPE_MODINFO, TYPE_FILES, TYPE_FILEINFO, TYPE_DOWNLOADURL, -- cgit v1.3.1 From 2b5862ef0144d49e1927b5c914cbcea7183fab8a Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 1 Feb 2019 17:45:05 -0600 Subject: Multiple updates: * Remove periodic auto-update * Add context menu force-update for mod list * Fix some bugs with update parsing - Ignore update files that are deleted - Fix remaining issue with failing to get all mods due to capitalization * Remove queue retry when out of requests * Clear all requests when out of requests * More potential improvements to login/validation process --- src/mainwindow.cpp | 54 +++++++++++++++++++++++++++++------------------- src/mainwindow.h | 4 ++-- src/modinfo.cpp | 42 ++++++++++++++++++------------------- src/modinfo.h | 2 +- src/nexusinterface.cpp | 30 ++++++++++----------------- src/nexusinterface.h | 2 -- src/nxmaccessmanager.cpp | 4 ++++ 7 files changed, 71 insertions(+), 67 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3a341298..fecf8704 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -439,10 +439,6 @@ MainWindow::MainWindow(QSettings &initSettings connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); - m_ModUpdateTimer.setSingleShot(false); - connect(&m_ModUpdateTimer, SIGNAL(timeout()), this, SLOT(modUpdateCheck())); - m_ModUpdateTimer.start(300 * 1000); - setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); FileDialogMemory::restore(initSettings); @@ -495,8 +491,6 @@ MainWindow::MainWindow(QSettings &initSettings updatePluginCount(); updateModCount(); - - modUpdateCheck(); } @@ -4056,6 +4050,22 @@ void MainWindow::ignoreUpdate() { } } +void MainWindow::checkModUpdates_clicked() +{ + std::multimap IDs; + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + IDs.insert(std::make_pair(info->getGameName(), info->getNexusID())); + } + } else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + IDs.insert(std::make_pair(info->getGameName(), info->getNexusID())); + } + modUpdateCheck(IDs); +} + void MainWindow::unignoreUpdate() { QItemSelectionModel *selection = ui->modList->selectionModel(); @@ -4411,7 +4421,7 @@ void MainWindow::initModListContextMenu(QMenu *menu) menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); - menu->addAction(tr("Force update check"), this, SLOT(checkModsForUpdates())); + menu->addAction(tr("Check for updates"), this, SLOT(checkModsForUpdates())); menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh())); menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); } @@ -4518,10 +4528,11 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); } + if (info->getNexusID() > 0) + menu->addAction(tr("Force-check updates"), this, SLOT(checkModUpdates_clicked())); if (info->updateIgnored()) { menu.addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); - } - else { + } else { if (info->updateAvailable() || info->downgradeAvailable()) { menu.addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); } @@ -5447,15 +5458,15 @@ void MainWindow::modDetailsUpdated(bool) } } -void MainWindow::modUpdateCheck() +void MainWindow::modUpdateCheck(std::multimap IDs) { if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - m_ModsToUpdate += ModInfo::autoUpdateCheck(&m_PluginContainer, this); + m_ModsToUpdate += ModInfo::manualUpdateCheck(&m_PluginContainer, this, IDs); m_RefreshProgress->setRange(0, m_ModsToUpdate); } else { QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([this]() { this->modUpdateCheck(); }); + 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."); @@ -5506,19 +5517,20 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD if (currentUpdate == updateScanData["old_file_id"].toInt()) { currentUpdate = updateScanData["new_file_id"].toInt(); finalUpdate = false; + // Apply the version data from the latest file + for (auto file : files) { + QVariantMap fileData = file.toMap(); + if (fileData["file_id"].toInt() == currentUpdate) { + if (fileData["category_id"].toInt() != 6) { + mod->setNewestVersion(fileData["version"].toString()); + foundUpdate = true; + } + } + } break; } } } - // Apply the version data from the latest file - for (auto file : files) { - QVariantMap fileData = file.toMap(); - if (fileData["file_id"].toInt() == currentUpdate) { - mod->setNewestVersion(fileData["version"].toString()); - foundUpdate = true; - } - } - break; } else if (installedFile == updateData["new_file_name"].toString()) { // This is a safety mechanism if this is the latest update file so we don't use the mod version diff --git a/src/mainwindow.h b/src/mainwindow.h index 3f3a5457..59be9800 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -348,7 +348,6 @@ private: QTimer m_CheckBSATimer; QTimer m_SaveMetaTimer; QTimer m_UpdateProblemsTimer; - QTimer m_ModUpdateTimer; QFuture m_MetaSave; @@ -509,7 +508,7 @@ private slots: void modInstalled(const QString &modName); - void modUpdateCheck(); + void modUpdateCheck(std::multimap IDs); void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); @@ -590,6 +589,7 @@ private slots: void overwriteClosed(int); void changeVersioningScheme(); + void checkModUpdates_clicked(); void ignoreUpdate(); void unignoreUpdate(); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index e9cfd77b..a5b240c8 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -148,8 +148,7 @@ std::vector ModInfo::getByModID(QString game, int modID) for (auto iter : s_ModsByModID) { if (iter.first.second == modID) { if (iter.first.first.compare(game, Qt::CaseInsensitive) == 0) { - match = iter.second; - break; + match.insert(match.end(), iter.second.begin(), iter.second.end()); } } } @@ -303,6 +302,9 @@ int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiv } result = organizedGames.size(); + + if (organizedGames.empty()) + qWarning("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(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); @@ -312,32 +314,28 @@ int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiv } -int ModInfo::autoUpdateCheck(PluginContainer *pluginContainer, QObject *receiver) +int ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs) { - qInfo("Initializing periodic update check."); - int result = 0; - - std::vector> sortedMods; + std::vector> mods; std::multimap organizedGames; - for (auto mod : s_Collection) { - if (mod->canBeUpdated()) { - sortedMods.push_back(mod); - } + + for (auto ID : IDs) { + auto matchedMods = getByModID(ID.first, ID.second); + mods.insert(mods.end(), matchedMods.begin(), matchedMods.end()); } + mods.erase( + std::remove_if(mods.begin(), mods.end(), [](ModInfo::Ptr mod) -> bool { return !mod->canBeUpdated(); }), + mods.end() + ); - std::sort(sortedMods.begin(), sortedMods.end(), [](QSharedPointer a, QSharedPointer b) -> bool { + std::sort(mods.begin(), mods.end(), [](QSharedPointer a, QSharedPointer b) -> bool { return a->getLastNexusUpdate() < b->getLastNexusUpdate(); }); - if (sortedMods.size() > 10) - sortedMods.resize(10); - - result = sortedMods.size(); + if (mods.size()) { + qInfo("Checking updates for %d mods...", mods.size()); - if (sortedMods.size()) { - qInfo("Checking updates for %d mods...", sortedMods.size()); - - for (auto mod : sortedMods) { + for (auto mod : mods) { organizedGames.insert(std::make_pair(mod->getGameName(), mod->getNexusID())); } @@ -345,10 +343,10 @@ int ModInfo::autoUpdateCheck(PluginContainer *pluginContainer, QObject *receiver NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); } } else { - qInfo("No mods require updates at this time."); + qInfo("None of the selected mods can be updated."); } - return result; + return organizedGames.size(); } diff --git a/src/modinfo.h b/src/modinfo.h index ae9dd1f3..9343a98c 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -188,7 +188,7 @@ public: /** * @brief run a limited batch of mod update checks for "newest version" information */ - static int autoUpdateCheck(PluginContainer *pluginContainer, QObject *receiver); + static int manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs); /** * @brief query nexus information for every mod and update the "newest version" information diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 1d6d2015..130a87b1 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -155,10 +155,6 @@ NexusInterface::NexusInterface(PluginContainer *pluginContainer) m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString(3)); m_DiskCache = new QNetworkDiskCache(this); connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); - - m_RetryTimer.setSingleShot(true); - m_RetryTimer.setInterval(2000); - m_RetryTimer.callOnTimeout(this, &NexusInterface::nextRequest); } NXMAccessManager *NexusInterface::getAccessManager() @@ -468,8 +464,6 @@ IPluginGame* NexusInterface::getGame(QString gameName) const void NexusInterface::cleanup() { -// delete m_AccessManager; -// delete m_DiskCache; m_AccessManager = nullptr; m_DiskCache = nullptr; } @@ -487,19 +481,6 @@ void NexusInterface::nextRequest() return; } - if (m_RemainingDailyRequests + m_RemainingHourlyRequests <= 0) { - if (!m_RetryTimer.isActive()) { - QTime time = QTime::currentTime(); - QTime targetTime; - targetTime.setHMS((time.hour() + 1) % 23, 0, 5); - m_RetryTimer.start(time.msecsTo(targetTime)); - QString warning("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); - } - return; - } - if (requiresLogin(m_RequestQueue.head()) && !getAccessManager()->validated()) { if (!getAccessManager()->validateAttempted()) { emit needLogin(); @@ -509,6 +490,17 @@ void NexusInterface::nextRequest() } } + if (m_RemainingDailyRequests + m_RemainingHourlyRequests <= 0) { + m_RequestQueue.clear(); + QTime time = QTime::currentTime(); + QTime targetTime; + targetTime.setHMS((time.hour() + 1) % 23, 5, 0); + QString warning("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); + return; + } + NXMRequestInfo info = m_RequestQueue.dequeue(); info.m_Timeout = new QTimer(this); info.m_Timeout->setInterval(60000); diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 64f15961..db4ebfd6 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -460,8 +460,6 @@ private: PluginContainer *m_PluginContainer; - QTimer m_RetryTimer; - int m_RemainingDailyRequests; int m_RemainingHourlyRequests; int m_MaxDailyRequests; diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index d1720c76..7ab474a7 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -268,9 +268,13 @@ void NXMAccessManager::validateFinished() m_ValidateState = VALIDATE_VALID; emit validateSuccessful(true); } else { + m_ApiKey.clear(); + m_ValidateState = VALIDATE_NOT_VALID; emit validateFailed(tr("Validation failed, please reauthenticate in the Settings -> Nexus tab: %1").arg(credentialsData.value("message").toString())); } } else { + m_ApiKey.clear(); + m_ValidateState = VALIDATE_NOT_CHECKED; emit validateFailed(tr("unknown error")); } } -- cgit v1.3.1 From d86d2f704ab0eb91a9051fa818b5195f323467fa Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 5 Feb 2019 16:53:54 -0600 Subject: Implement staggered timeouts bases on age. * < 1 mo = 2 hours * < 3 mo = 4 hours * < 6 mo = 6 hours * < 1 yr = 12 hours * > 1 yr = 24 hours --- src/mainwindow.cpp | 5 +++-- src/modinfo.h | 19 +++++++++++++++++-- src/modinfobackup.h | 3 +++ src/modinfoforeign.h | 3 +++ src/modinfooverwrite.h | 3 +++ src/modinforegular.cpp | 37 ++++++++++++++++++++++++++++++++++++- src/modinforegular.h | 16 ++++++++++++++++ src/modinfoseparator.h | 3 +++ src/modlist.cpp | 2 +- 9 files changed, 85 insertions(+), 6 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cdf363cc..ce476143 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5550,7 +5550,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD requiresInfo = true; } - if (mod->getLastNexusQuery().addDays(1) <= QDateTime::currentDateTime()) + if (mod->getLastNexusQuery().addDays(1) <= QDateTime::currentDateTimeUtc()) requiresInfo = true; } @@ -5577,7 +5577,7 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD std::vector modsList = ModInfo::getByModID(gameNameReal, modID); for (auto mod : modsList) { QDateTime now = QDateTime::currentDateTimeUtc(); - QDateTime updateTarget = mod->getLastNexusUpdate().addSecs(3600); + QDateTime updateTarget = mod->getExpires(); if (now >= updateTarget) { mod->setNewestVersion(result["version"].toString()); mod->setLastNexusUpdate(QDateTime::currentDateTimeUtc()); @@ -5594,6 +5594,7 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD mod->setIsEndorsed(false); } mod->setLastNexusQuery(QDateTime::currentDateTimeUtc()); + mod->setNexusLastModified(QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt())); mod->saveMeta(); } } diff --git a/src/modinfo.h b/src/modinfo.h index 9343a98c..2048432e 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -24,9 +24,9 @@ along with Mod Organizer. If not, see . #include "versioninfo.h" class PluginContainer; - -class QDateTime; class QDir; + +#include #include #include #include @@ -459,6 +459,11 @@ public: */ virtual bool canBeUpdated() const { return false; } + /** + * @return the mod update check expiration date + */ + virtual QDateTime getExpires() const { return QDateTime(); } + /** * @return true if the mod can be enabled/disabled */ @@ -555,6 +560,16 @@ public: */ virtual void setLastNexusQuery(QDateTime time) = 0; + /** + * @return last time the mod was updated on Nexus + */ + virtual QDateTime getNexusLastModified() const = 0; + + /** + * @brief set the last time the mod was updated on Nexus + */ + virtual void setNexusLastModified(QDateTime time) = 0; + /** * @return a list of files that, if they exist in the data directory are treated as files in THIS mod */ diff --git a/src/modinfobackup.h b/src/modinfobackup.h index e88335e2..3c948931 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -21,6 +21,7 @@ public: virtual int getFixedPriority() const { return -1; } virtual void ignoreUpdate(bool) {} virtual bool canBeUpdated() const { return false; } + virtual QDateTime getExpires() const { return QDateTime(); } virtual bool canBeEnabled() const { return false; } virtual std::vector getIniTweaks() const { return std::vector(); } virtual std::vector getFlags() const; @@ -31,6 +32,8 @@ public: virtual void setLastNexusQuery(QDateTime) {} virtual QDateTime getLastNexusUpdate() const { return QDateTime(); } virtual void setLastNexusUpdate(QDateTime) {} + virtual QDateTime getNexusLastModified() const { return QDateTime(); } + virtual void setNexusLastModified(QDateTime) {} virtual void getNexusFiles(QList::const_iterator&, QList::const_iterator&) {} virtual QString getNexusDescription() const { return QString(); } diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 6fcf56a1..7f5bb711 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -43,6 +43,7 @@ public: virtual QString getInstallationFile() const { return ""; } virtual QString getGameName() const { return ""; } virtual int getNexusID() const { return -1; } + virtual QDateTime getExpires() const { return QDateTime(); } virtual std::vector getIniTweaks() const { return std::vector(); } virtual std::vector getFlags() const; virtual int getHighlight() const; @@ -53,6 +54,8 @@ public: virtual void setLastNexusUpdate(QDateTime) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } virtual void setLastNexusQuery(QDateTime) {} + virtual QDateTime getNexusLastModified() const { return QDateTime(); } + virtual void setNexusLastModified(QDateTime) {} virtual QString getNexusDescription() const { return QString(); } virtual int getFixedPriority() const { return INT_MIN; } virtual QStringList archives(bool checkOnDisk = false) { return m_Archives; } diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index 9f77f648..71c20bee 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -46,6 +46,7 @@ public: virtual int getFixedPriority() const { return INT_MAX; } virtual QString getGameName() const { return ""; } virtual int getNexusID() const { return -1; } + virtual QDateTime getExpires() const { return QDateTime(); } virtual std::vector getIniTweaks() const { return std::vector(); } virtual std::vector getFlags() const; virtual int getHighlight() const; @@ -56,6 +57,8 @@ public: virtual void setLastNexusUpdate(QDateTime) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } virtual void setLastNexusQuery(QDateTime) {} + virtual QDateTime getNexusLastModified() const { return QDateTime(); } + virtual void setNexusLastModified(QDateTime) {} virtual QString getNexusDescription() const { return QString(); } virtual QStringList archives(bool checkOnDisk = false); virtual void addInstalledFile(int, int) {} diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 8651f15e..876ccf3d 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -100,6 +100,7 @@ void ModInfoRegular::readMeta() m_URL = metaFile.value("url", "").toString(); m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); + m_NexusLastModified = QDateTime::fromString(metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), Qt::ISODate); m_Color = metaFile.value("color",QColor()).value(); if (metaFile.contains("endorsed")) { if (metaFile.value("endorsed").canConvert()) { @@ -165,6 +166,7 @@ void ModInfoRegular::saveMeta() metaFile.setValue("nexusFileStatus", m_NexusFileStatus); metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); metaFile.setValue("lastNexusUpdate", m_LastNexusUpdate.toString(Qt::ISODate)); + metaFile.setValue("nexusLastModified", m_NexusLastModified.toString(Qt::ISODate)); metaFile.setValue("converted", m_Converted); metaFile.setValue("validated", m_Validated); metaFile.setValue("color", m_Color); @@ -233,6 +235,7 @@ void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, QVariant re setEndorsedState(ENDORSED_FALSE); } m_LastNexusQuery = QDateTime::currentDateTimeUtc(); + m_NexusLastModified = QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt()); m_MetaInfoChanged = true; saveMeta(); disconnect(sender(), SIGNAL(nxmDescriptionAvailable(QString, int, QVariant, QVariant))); @@ -496,12 +499,31 @@ void ModInfoRegular::ignoreUpdate(bool ignore) bool ModInfoRegular::canBeUpdated() const { QDateTime now = QDateTime::currentDateTimeUtc(); - QDateTime target = m_LastNexusUpdate.addSecs(3600); + QDateTime target = getExpires(); if (now >= target) return m_NexusID > 0; return false; } +QDateTime ModInfoRegular::getExpires() const +{ + qint64 diff = m_NexusLastModified.msecsTo(QDateTime::currentDateTimeUtc()); + qint64 year = 31536000000; + qint64 sixMonths = 15768000000; + qint64 threeMonths = 7884000000; + qint64 oneMonth = 1314000000; + + if (diff < oneMonth) + return m_LastNexusUpdate.addSecs(7200); + else if (diff < threeMonths) + return m_LastNexusUpdate.addSecs(14400); + else if (diff < sixMonths) + return m_LastNexusUpdate.addSecs(21600); + else if (diff < year) + return m_LastNexusUpdate.addSecs(43200); + else + return m_LastNexusUpdate.addSecs(86400); +} std::vector ModInfoRegular::getFlags() const { @@ -686,6 +708,19 @@ void ModInfoRegular::setLastNexusQuery(QDateTime time) emit modDetailsUpdated(true); } +QDateTime ModInfoRegular::getNexusLastModified() const +{ + return m_NexusLastModified; +} + +void ModInfoRegular::setNexusLastModified(QDateTime time) +{ + m_NexusLastModified = time; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + void ModInfoRegular::setURL(QString const &url) { m_URL = url; diff --git a/src/modinforegular.h b/src/modinforegular.h index 6ad51e5f..7dc2fd6e 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -258,6 +258,11 @@ public: */ virtual bool canBeUpdated() const; + /** + * @return the update expiration date based on the last updated date from Nexus + */ + virtual QDateTime getExpires() const; + /** * @return true if the mod can be enabled/disabled */ @@ -348,6 +353,16 @@ public: */ virtual void setLastNexusQuery(QDateTime time); + /** + * @return last time the mod was updated on Nexus + */ + virtual QDateTime getNexusLastModified() const; + + /** + * @brief set the last time the mod was updated on Nexus + */ + virtual void setNexusLastModified(QDateTime time); + virtual QStringList archives(bool checkOnDisk = false); virtual void setColor(QColor color); @@ -405,6 +420,7 @@ private: QDateTime m_CreationTime; QDateTime m_LastNexusQuery; QDateTime m_LastNexusUpdate; + QDateTime m_NexusLastModified; QColor m_Color; diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index 819ee686..865a5179 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -31,6 +31,7 @@ public: virtual void ignoreUpdate(bool /*ignore*/) {} virtual bool canBeUpdated() const { return false; } + virtual QDateTime getExpires() const { return QDateTime(); } virtual bool canBeEnabled() const { return false; } virtual std::vector getIniTweaks() const { return std::vector(); } @@ -49,6 +50,8 @@ public: virtual void setLastNexusUpdate(QDateTime) {} virtual QDateTime getLastNexusQuery() const { return QDateTime(); } virtual void setLastNexusQuery(QDateTime) {} + virtual QDateTime getNexusLastModified() const { return QDateTime(); } + virtual void setNexusLastModified(QDateTime) {} virtual QDateTime creationTime() const { return QDateTime(); } virtual void getNexusFiles diff --git a/src/modlist.cpp b/src/modlist.cpp index 3f8ea4e1..c252d73b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -467,7 +467,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const if (!modInfo->canBeUpdated()) { text += "
" + tr("This mod was last checked on %1. It will be available to check after %2.") .arg(modInfo->getLastNexusUpdate().toLocalTime().toString(Qt::DefaultLocaleShortDate)) - .arg(modInfo->getLastNexusUpdate().toLocalTime().addSecs(3600).time().toString(Qt::DefaultLocaleShortDate)); + .arg(modInfo->getExpires().toLocalTime().time().toString(Qt::DefaultLocaleShortDate)); } else { text += "
" + tr("This mod is eligible for an update check."); text += "
" + tr("It was last checked on %1").arg(modInfo->getLastNexusUpdate().toLocalTime().toString(Qt::DefaultLocaleShortDate)); -- cgit v1.3.1 From ae74c7ff2bf1caf285f3cc547372f1c313e4f639 Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 5 Feb 2019 17:02:00 -0600 Subject: Re-simplify ModInfo interface --- src/modinfo.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/modinfo.h b/src/modinfo.h index 2048432e..348583d2 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -25,8 +25,8 @@ along with Mod Organizer. If not, see . class PluginContainer; class QDir; +class QDateTime; -#include #include #include #include @@ -462,7 +462,7 @@ public: /** * @return the mod update check expiration date */ - virtual QDateTime getExpires() const { return QDateTime(); } + virtual QDateTime getExpires() const = 0; /** * @return true if the mod can be enabled/disabled -- cgit v1.3.1 From 5a7d0243c69d5194ee1a4d0be05d2bcaec18aeac Mon Sep 17 00:00:00 2001 From: Silarn Date: Sat, 9 Feb 2019 04:52:44 -0600 Subject: Implement bulk update check layer --- src/mainwindow.cpp | 33 +++++++++++++++++++++-- src/mainwindow.h | 1 + src/modinfo.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++------ src/modinfo.h | 2 ++ src/modinforegular.cpp | 4 +-- src/nexusinterface.cpp | 64 +++++++++++++++++++++++++++++++++++++++++++ src/nexusinterface.h | 24 ++++++++++++++++- 7 files changed, 188 insertions(+), 13 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a17b51d5..5e97a959 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5468,10 +5468,39 @@ void MainWindow::modUpdateCheck(std::multimap IDs) if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { + } else qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); + } +} + +void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int) +{ + QString gameNameReal; + for (IPluginGame *game : m_PluginContainer.plugins()) { + if (game->gameNexusName() == gameName) { + gameNameReal = game->gameShortName(); + break; + } + } + QVariantList resultList = resultData.toList(); + std::set> finalMods = ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true); + + if (finalMods.empty()) { + qInfo("None of your mods appear to have had recent file updates."); + } + + std::set> organizedGames; + for (auto mod : finalMods) { + if (mod->canBeUpdated()) { + organizedGames.insert(std::make_pair(mod->getGameName().toLower(), mod->getNexusID())); } } + + 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."); + + for (auto game : organizedGames) + NexusInterface::instance(&m_PluginContainer)->requestUpdates(game.second, this, QVariant(), game.first, QString()); } void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) @@ -5594,7 +5623,7 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD mod->setIsEndorsed(false); } mod->setLastNexusQuery(QDateTime::currentDateTimeUtc()); - mod->setNexusLastModified(QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt())); + mod->setNexusLastModified(QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC)); mod->saveMeta(); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 59be9800..d2704b72 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -510,6 +510,7 @@ private slots: void modUpdateCheck(std::multimap IDs); + void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index a1059e24..3e694baf 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -294,25 +294,82 @@ int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiv // NexusInterface::instance(pluginContainer)->requestUpdates(game->nexusModOrganizerID(), receiver, QVariant(), game->gameShortName(), QString()); //} - std::set> organizedGames; + QDateTime earliest = QDateTime::currentDateTimeUtc(); + QDateTime latest; + std::set games; for (auto mod : s_Collection) { if (mod->canBeUpdated()) { - organizedGames.insert(std::make_pair(mod->getGameName().toLower(), mod->getNexusID())); + if (mod->getLastNexusUpdate() < earliest) + earliest = mod->getLastNexusUpdate(); + if (mod->getLastNexusUpdate() > latest) + latest = mod->getLastNexusUpdate(); + games.insert(mod->getGameName().toLower()); } } - result = organizedGames.size(); - - if (organizedGames.empty()) - qWarning("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + if (latest < QDateTime::currentDateTimeUtc().addDays(-30)) { + std::set> organizedGames; + for (auto mod : s_Collection) { + if (mod->canBeUpdated()) { + organizedGames.insert(std::make_pair(mod->getGameName().toLower(), mod->getNexusID())); + } + } + + result = organizedGames.size(); + + if (organizedGames.empty()) + qWarning("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(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); + for (auto game : organizedGames) { + NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); + } + } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-30)) { + for (auto gameName : games) + NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(true), QString()); + } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-7)) { + for (auto gameName : games) + NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(false), QString()); + } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-1)) { + for (auto gameName : games) + NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::WEEK, receiver, QVariant(false), QString()); + } else { + for (auto gameName : games) + NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::DAY, receiver, QVariant(false), QString()); } return result; } +std::set> ModInfo::filteredMods(QString gameName, QVariantList updateData, bool addOldMods, bool markUpdated) +{ + std::set> finalMods; + for (QVariant result : updateData) { + QVariantMap update = result.toMap(); + for (auto mod : s_Collection) + if (mod->getNexusID() == update["mod_id"].toInt() && mod->getGameName().compare(gameName, Qt::CaseInsensitive) == 0) + if (mod->getLastNexusUpdate() > QDateTime::fromSecsSinceEpoch(update["latest_file_update"].toInt(), Qt::UTC)) + finalMods.insert(mod); + } + + if (addOldMods) + for (auto mod : s_Collection) + if (mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addDays(-30) && mod->getGameName().compare(gameName, Qt::CaseInsensitive) == 0) + finalMods.insert(mod); + + if (markUpdated) { + std::set> updates; + for (auto mod : s_Collection) + if (mod->getGameName().compare(gameName, Qt::CaseInsensitive) == 0 && mod->canBeUpdated()) + updates.insert(mod); + std::set> diff; + std::set_difference(updates.begin(), updates.end(), finalMods.begin(), finalMods.end(), std::inserter(diff, diff.end())); + for (auto skipped : diff) { + skipped->setLastNexusUpdate(QDateTime::currentDateTimeUtc()); + skipped->updateNXMInfo(); + } + } + return finalMods; +} int ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs) { diff --git a/src/modinfo.h b/src/modinfo.h index 348583d2..8c234225 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -195,6 +195,8 @@ public: **/ static int checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); + static std::set> filteredMods(QString gameName, QVariantList updateData, bool addOldMods = false, bool markUpdated = false); + /** * @brief create a new mod from the specified directory and add it to the collection * @param dir directory to create from diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 10a1317c..9532bf6d 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -235,10 +235,10 @@ void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, QVariant re setEndorsedState(ENDORSED_FALSE); } m_LastNexusQuery = QDateTime::currentDateTimeUtc(); - m_NexusLastModified = QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt()); + m_NexusLastModified = QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC); m_MetaInfoChanged = true; saveMeta(); - disconnect(sender(), SIGNAL(nxmDescriptionAvailable(QString, int, QVariant, QVariant))); + disconnect(sender(), SIGNAL(descriptionAvailable(QString, int, QVariant, QVariant))); emit modDetailsUpdated(true); } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index f1fa252a..abddbd3e 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -343,6 +343,26 @@ int NexusInterface::requestModInfo(QString gameName, int modID, QObject *receive return -1; } +int NexusInterface::requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData, + const QString &subModule, const MOBase::IPluginGame const *game) +{ + if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) { + NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), + receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; + } + qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.") + .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests)); + return -1; +} int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant userData, QString gameName, const QString &subModule) @@ -531,6 +551,21 @@ void NexusInterface::nextRequest() case NXMRequestInfo::TYPE_MODINFO: { url = QString("%1/games/%2/mods/%3").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID); } break; + case NXMRequestInfo::TYPE_CHECKUPDATES: { + QString period; + switch (info.m_UpdatePeriod) { + case UpdatePeriod::DAY: + period = "1d"; + break; + case UpdatePeriod::WEEK: + period = "1w"; + break; + case UpdatePeriod::MONTH: + period = "1m"; + break; + } + url = QString("%1/games/%2/mods/updated?period=%3").arg(info.m_URL).arg(info.m_GameName).arg(period); + } break; case NXMRequestInfo::TYPE_FILES: case NXMRequestInfo::TYPE_GETUPDATES: { url = QString("%1/games/%2/mods/%3/files").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID); @@ -624,6 +659,9 @@ void NexusInterface::requestFinished(std::list::iterator iter) case NXMRequestInfo::TYPE_MODINFO: { emit nxmModInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); } break; + case NXMRequestInfo::TYPE_CHECKUPDATES: { + emit nxmUpdateInfoAvailable(iter->m_GameName, iter->m_UserData, result, iter->m_ID); + } break; case NXMRequestInfo::TYPE_FILES: { emit nxmFilesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); } break; @@ -726,6 +764,7 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID , m_FileID(0) , m_Reply(nullptr) , m_Type(type) + , m_UpdatePeriod(UpdatePeriod::NONE) , m_UserData(userData) , m_Timeout(nullptr) , m_Reroute(false) @@ -750,6 +789,7 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID , m_FileID(0) , m_Reply(nullptr) , m_Type(type) + , m_UpdatePeriod(UpdatePeriod::NONE) , m_UserData(userData) , m_Timeout(nullptr) , m_Reroute(false) @@ -773,6 +813,30 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID , m_FileID(fileID) , m_Reply(nullptr) , m_Type(type) + , m_UpdatePeriod(UpdatePeriod::NONE) + , m_UserData(userData) + , m_Timeout(nullptr) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(get_management_url(game)) + , m_SubModule(subModule) + , m_NexusGameID(game->nexusGameID()) + , m_GameName(game->gameNexusName()) + , m_Endorse(false) +{} + +NexusInterface::NXMRequestInfo::NXMRequestInfo(UpdatePeriod period + , NexusInterface::NXMRequestInfo::Type type + , QVariant userData + , const QString &subModule + , MOBase::IPluginGame const *game +) + : m_ModID(0) + , m_ModVersion("0") + , m_FileID(0) + , m_Reply(nullptr) + , m_Type(type) + , m_UpdatePeriod(period) , m_UserData(userData) , m_Timeout(nullptr) , m_Reroute(false) diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 1c283335..06b19947 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -127,6 +127,15 @@ class NexusInterface : public QObject { Q_OBJECT +public: + + enum UpdatePeriod { + NONE, + DAY, + WEEK, + MONTH + }; + public: ~NexusInterface(); @@ -202,6 +211,15 @@ public: int requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + int requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData, + const QString &subModule) + { + return requestUpdateInfo(gameName, period, receiver, userData, subModule, getGame(gameName)); + } + + int requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData, + const QString &subModule, MOBase::IPluginGame const *game); + /** * @brief request nexus descriptions for multiple mods at once * @param modID id of the mod the caller is interested in @@ -378,6 +396,7 @@ signals: void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); @@ -415,8 +434,10 @@ private: TYPE_FILEINFO, TYPE_DOWNLOADURL, TYPE_TOGGLEENDORSEMENT, - TYPE_GETUPDATES + TYPE_GETUPDATES, + TYPE_CHECKUPDATES } m_Type; + UpdatePeriod m_UpdatePeriod; QVariant m_UserData; QTimer *m_Timeout; QString m_URL; @@ -430,6 +451,7 @@ private: NXMRequestInfo(int modID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); NXMRequestInfo(int modID, QString modVersion, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + NXMRequestInfo(UpdatePeriod period, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); private: static QAtomicInt s_NextID; -- cgit v1.3.1 From 2b9d2f32fbbc576c9367a24c0347e5870ff20117 Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 11 Feb 2019 16:51:47 -0600 Subject: Endorsement and efficiency updates * Reduce update timeout to 5 minutes * Introduce bulk endorsement check replacing global mod info check * Fine tune performance of bulk update info query * Thread the update processing to prevent lockups --- src/mainwindow.cpp | 162 ++++++++++++++++++++++++++++++++++--------------- src/mainwindow.h | 13 ++-- src/modinfo.cpp | 37 ++++------- src/modinfo.h | 4 +- src/modinforegular.cpp | 3 + src/nexusinterface.cpp | 70 +++++++++++++++++---- src/nexusinterface.h | 9 ++- 7 files changed, 203 insertions(+), 95 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5e97a959..7592bd1e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -261,7 +261,8 @@ MainWindow::MainWindow(QSettings &initSettings actionToToolButton(ui->actionEndorseMO); createEndorseWidget(); - ui->actionEndorseMO->setVisible(false); + + toggleMO2EndorseState(); for (QAction *action : ui->toolBar->actions()) { if (action->isSeparator()) { @@ -752,7 +753,7 @@ void MainWindow::createEndorseWidget() buttonMenu->addAction(endorseAction); QAction *wontEndorseAction = new QAction(tr("Won't Endorse"), buttonMenu); - connect(wontEndorseAction, SIGNAL(triggered()), this, SLOT(wontEndorse())); + connect(wontEndorseAction, SIGNAL(triggered()), this, SLOT(on_actionWontEndorseMO_triggered())); buttonMenu->addAction(wontEndorseAction); } @@ -3983,8 +3984,8 @@ void MainWindow::checkModsForUpdates() { statusBar()->show(); if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - m_ModsToUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this); - m_RefreshProgress->setRange(0, m_ModsToUpdate); + ModInfo::checkAllForUpdate(&m_PluginContainer, this); + NexusInterface::instance(&m_PluginContainer)->requestEndorsementInfo(this, QVariant(), QString()); } else { QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { @@ -5318,26 +5319,8 @@ void MainWindow::motdReceived(const QString &motd) m_OrganizerCore.settings().setMotDHash(hash); } } - - ui->actionEndorseMO->setVisible(false); -} - - -void MainWindow::notEndorsedYet() -{ - if (!Settings::instance().directInterface().value("wont_endorse_MO", false).toBool()) { - ui->actionEndorseMO->setVisible(true); - } -} - - -void MainWindow::wontEndorse() -{ - Settings::instance().directInterface().setValue("wont_endorse_MO", true); - ui->actionEndorseMO->setVisible(false); } - void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) { QTreeWidget *dataTree = findChild("dataTree"); @@ -5398,6 +5381,21 @@ void MainWindow::on_actionEndorseMO_triggered() } } +void MainWindow::on_actionWontEndorseMO_triggered() +{ + // Normally this would be the managed game but MO2 is only uploaded to the Skyrim SE site right now + IPluginGame * game = m_OrganizerCore.getGame("skyrimse"); + if (!game) return; + + if (QMessageBox::question(this, tr("Abstain from Endorsing Mod Organizer"), + tr("Are you sure you want to abstain from endorsing Mod Organizer 2?\n" + "You will have to visit the mod page on the %1 Nexus site to change your mind.").arg( + NexusInterface::instance(&m_PluginContainer)->getGameURL(game->gameShortName())), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + NexusInterface::instance(&m_PluginContainer)->requestToggleEndorsement( + game->gameShortName(), game->nexusModOrganizerID(), m_OrganizerCore.getVersion().canonicalString(), false, this, QVariant(), QString()); + } +} void MainWindow::initDownloadView() { @@ -5448,21 +5446,10 @@ void MainWindow::updateDownloadView() m_OrganizerCore.downloadManager()->refreshList(); } -void MainWindow::modDetailsUpdated(bool) -{ - if (m_ModsToUpdate <= 0) { - statusBar()->hide(); - m_RefreshProgress->setVisible(false); - } else { - m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); - } -} - void MainWindow::modUpdateCheck(std::multimap IDs) { if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - m_ModsToUpdate += ModInfo::manualUpdateCheck(&m_PluginContainer, this, IDs); - m_RefreshProgress->setRange(0, m_ModsToUpdate); + ModInfo::manualUpdateCheck(&m_PluginContainer, this, IDs); } else { QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { @@ -5473,6 +5460,80 @@ void MainWindow::modUpdateCheck(std::multimap IDs) } } +void MainWindow::toggleMO2EndorseState() +{ + QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionEndorseMO)); + if (Settings::instance().endorsementIntegration()) { + ui->actionEndorseMO->setVisible(true); + if (Settings::instance().directInterface().contains("endorse_state")) { + ui->actionEndorseMO->setEnabled(false); + if (Settings::instance().directInterface().value("endorse_state").toString() == "Endorsed") { + ui->actionEndorseMO->setToolTip(tr("Thank you for endorsing MO2! :)")); + toolBtn->setToolTip(tr("Thank you for endorsing MO2! :)")); + } else if (Settings::instance().directInterface().value("endorse_state").toString() == "Abstained") { + ui->actionEndorseMO->setToolTip(tr("Please reconsider endorsing MO2 on Nexus!")); + toolBtn->setToolTip(tr("Please reconsider endorsing MO2 on Nexus!")); + } + } else { + ui->actionEndorseMO->setEnabled(true); + } + } else + ui->actionEndorseMO->setVisible(false); +} + +void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int) +{ + QVariantList data = resultData.toList(); + std::multimap> sorted; + QStringList games = m_OrganizerCore.managedGame()->validShortNames(); + games += m_OrganizerCore.managedGame()->gameShortName(); + bool searchedMO2NexusGame = false; + for (auto endorsementData : data) { + QVariantMap endorsement = endorsementData.toMap(); + std::pair data = std::make_pair(endorsement["mod_id"].toInt(), endorsement["status"].toString()); + sorted.insert(std::pair>(endorsement["domain_name"].toString(), data)); + } + for (auto game : games) { + IPluginGame *gamePlugin = m_OrganizerCore.getGame(game); + if (gamePlugin->gameShortName().compare("SkyrimSE", Qt::CaseInsensitive) == 0) + searchedMO2NexusGame = true; + auto iter = sorted.equal_range(gamePlugin->gameNexusName()); + for (auto result = iter.first; result != iter.second; ++result) { + std::vector modsList = ModInfo::getByModID(result->first, result->second.first); + + for (auto mod : modsList) { + if (result->second.second == "Endorsed") + mod->setIsEndorsed(true); + else if (result->second.second == "Abstained") + mod->setNeverEndorse(); + else + mod->setIsEndorsed(false); + } + + if (Settings::instance().endorsementIntegration()) { + if (result->first == "skyrimspecialedition" && result->second.first == gamePlugin->nexusModOrganizerID()) { + Settings::instance().directInterface().setValue("endorse_state", result->second.second); + toggleMO2EndorseState(); + } + } + } + } + + if (!searchedMO2NexusGame && Settings::instance().endorsementIntegration()) { + auto gamePlugin = m_OrganizerCore.getGame("SkyrimSE"); + if (gamePlugin) { + auto iter = sorted.equal_range(gamePlugin->gameNexusName()); + for (auto result = iter.first; result != iter.second; ++result) { + if (result->second.first == gamePlugin->nexusModOrganizerID()) { + Settings::instance().directInterface().setValue("endorse_state", result->second.second); + toggleMO2EndorseState(); + break; + } + } + } + } +} + void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int) { QString gameNameReal; @@ -5483,7 +5544,20 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa } } QVariantList resultList = resultData.toList(); - std::set> finalMods = ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true); + + QFutureWatcher>> *watcher = new QFutureWatcher>>(); + QObject::connect(watcher, &QFutureWatcher>>::finished, this, &MainWindow::finishUpdateInfo); + QFuture>> future = QtConcurrent::run([=]() -> std::set> { + return ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true); + }); + watcher->setFuture(future); +} + +void MainWindow::finishUpdateInfo() +{ + QFutureWatcher>> *watcher = static_cast>> *>(sender()); + + auto finalMods = watcher->result(); if (finalMods.empty()) { qInfo("None of your mods appear to have had recent file updates."); @@ -5501,6 +5575,9 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa for (auto game : organizedGames) NexusInterface::instance(&m_PluginContainer)->requestUpdates(game.second, this, QVariant(), game.first, QString()); + + disconnect(sender()); + delete sender(); } void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) @@ -5508,7 +5585,6 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD QVariantMap resultInfo = resultData.toMap(); QList files = resultInfo["files"].toList(); QList fileUpdates = resultInfo["file_updates"].toList(); - m_ModsToUpdate--; QString gameNameReal; for (IPluginGame *game : m_PluginContainer.plugins()) { if (game->gameNexusName() == gameName) { @@ -5578,19 +5654,10 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD // Scrape mod data here so we can use the mod version if no file update was located requiresInfo = true; } - - if (mod->getLastNexusQuery().addDays(1) <= QDateTime::currentDateTimeUtc()) - requiresInfo = true; } if (requiresInfo) NexusInterface::instance(&m_PluginContainer)->requestModInfo(gameName, modID, this, QVariant(), QString()); - - if (--m_ModsToUpdate <= 0) { - statusBar()->hide(); - } else { - m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); - } } void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) @@ -5663,11 +5730,6 @@ 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 (modID == -1) { - // must be the update-check that failed - m_ModsToUpdate = 0; - statusBar()->hide(); - } if (error == QNetworkReply::ContentAccessDenied || error == QNetworkReply::ContentNotFoundError) { QString gameNameReal; for (IPluginGame *game : m_PluginContainer.plugins()) { diff --git a/src/mainwindow.h b/src/mainwindow.h index d2704b72..a0c043a1 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -64,6 +64,7 @@ namespace MOShared { class DirectoryEntry; } #include #include #include +#include class QAction; class QAbstractItemModel; @@ -303,6 +304,8 @@ private: void sendSelectedModsToPriority(int newPriority); void sendSelectedPluginsToPriority(int newPriority); + void toggleMO2EndorseState(); + private: static const char *PATTERN_BACKUP_GLOB; @@ -341,8 +344,6 @@ private: CategoryFactory &m_CategoryFactory; - int m_ModsToUpdate; - bool m_LoginAttempted; QTimer m_CheckBSATimer; @@ -494,8 +495,6 @@ private slots: void updateAvailable(); void motdReceived(const QString &motd); - void notEndorsedYet(); - void wontEndorse(); void originModified(int originID); @@ -504,12 +503,13 @@ private slots: void addPrimaryCategoryCandidates(); - void modDetailsUpdated(bool success); - void modInstalled(const QString &modName); void modUpdateCheck(std::multimap IDs); + void finishUpdateInfo(); + + void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int); void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); @@ -622,6 +622,7 @@ private slots: // ui slots void on_actionSettings_triggered(); void on_actionUpdate_triggered(); void on_actionEndorseMO_triggered(); + void on_actionWontEndorseMO_triggered(); void on_bsaList_customContextMenuRequested(const QPoint &pos); void on_clearFiltersButton_clicked(); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 3e694baf..938a6418 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -284,16 +284,8 @@ ModInfo::ModInfo(PluginContainer *pluginContainer) } -int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver) +void ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver) { - int result = 0; - - // MO2 endorsement status is no longer available via this method - an alternative must be found - //IPluginGame const *game = pluginContainer->managedGame("Skyrim Special Edition"); - //if (game && game->nexusModOrganizerID()) { - // NexusInterface::instance(pluginContainer)->requestUpdates(game->nexusModOrganizerID(), receiver, QVariant(), game->gameShortName(), QString()); - //} - QDateTime earliest = QDateTime::currentDateTimeUtc(); QDateTime latest; std::set games; @@ -315,8 +307,6 @@ int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiv } } - result = organizedGames.size(); - if (organizedGames.empty()) qWarning("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); @@ -336,8 +326,6 @@ int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiv for (auto gameName : games) NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::DAY, receiver, QVariant(false), QString()); } - - return result; } std::set> ModInfo::filteredMods(QString gameName, QVariantList updateData, bool addOldMods, bool markUpdated) @@ -345,10 +333,12 @@ std::set> ModInfo::filteredMods(QString gameName, QVaria std::set> finalMods; for (QVariant result : updateData) { QVariantMap update = result.toMap(); - for (auto mod : s_Collection) - if (mod->getNexusID() == update["mod_id"].toInt() && mod->getGameName().compare(gameName, Qt::CaseInsensitive) == 0) - if (mod->getLastNexusUpdate() > QDateTime::fromSecsSinceEpoch(update["latest_file_update"].toInt(), Qt::UTC)) - finalMods.insert(mod); + std::copy_if(s_Collection.begin(), s_Collection.end(), std::inserter(finalMods, finalMods.end()), [=](QSharedPointer info) -> bool { + if (info->getNexusID() == update["mod_id"].toInt() && info->getGameName().compare(gameName, Qt::CaseInsensitive) == 0) + if (info->getLastNexusUpdate() > QDateTime::fromSecsSinceEpoch(update["latest_file_update"].toInt(), Qt::UTC)) + return true; + return false; + }); } if (addOldMods) @@ -358,20 +348,21 @@ std::set> ModInfo::filteredMods(QString gameName, QVaria if (markUpdated) { std::set> updates; - for (auto mod : s_Collection) - if (mod->getGameName().compare(gameName, Qt::CaseInsensitive) == 0 && mod->canBeUpdated()) - updates.insert(mod); + std::copy_if(s_Collection.begin(), s_Collection.end(), std::inserter(updates, updates.end()), [=](QSharedPointer info) -> bool { + if (info->getGameName().compare(gameName, Qt::CaseInsensitive) == 0 && info->canBeUpdated()) + return true; + return false; + }); std::set> diff; std::set_difference(updates.begin(), updates.end(), finalMods.begin(), finalMods.end(), std::inserter(diff, diff.end())); for (auto skipped : diff) { skipped->setLastNexusUpdate(QDateTime::currentDateTimeUtc()); - skipped->updateNXMInfo(); } } return finalMods; } -int ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs) +void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs) { std::vector> mods; std::set> organizedGames; @@ -402,8 +393,6 @@ int ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiv } else { qInfo("None of the selected mods can be updated."); } - - return organizedGames.size(); } diff --git a/src/modinfo.h b/src/modinfo.h index 8c234225..c3115685 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -188,12 +188,12 @@ public: /** * @brief run a limited batch of mod update checks for "newest version" information */ - static int manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs); + static void manualUpdateCheck(PluginContainer *pluginContainer, QObject *receiver, std::multimap IDs); /** * @brief query nexus information for every mod and update the "newest version" information **/ - static int checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); + static void checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); static std::set> filteredMods(QString gameName, QVariantList updateData, bool addOldMods = false, bool markUpdated = false); diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 9532bf6d..ac1c46d6 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -507,6 +507,8 @@ bool ModInfoRegular::canBeUpdated() const QDateTime ModInfoRegular::getExpires() const { + return m_LastNexusUpdate.addSecs(300); + /* switch (Settings::instance().nexusUpdateStrategy()) { case Settings::NexusUpdateStrategy::Flexible: { qint64 diff = m_NexusLastModified.msecsTo(QDateTime::currentDateTimeUtc()); @@ -530,6 +532,7 @@ QDateTime ModInfoRegular::getExpires() const return m_LastNexusUpdate.addSecs(86400); } break; } + */ } std::vector ModInfoRegular::getFlags() const diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index abddbd3e..e657dfc1 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -125,6 +125,15 @@ void NexusBridge::nxmDownloadURLsAvailable(QString gameName, int modID, int file } } +void NexusBridge::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit endorsementsAvailable(userData, resultData); + } +} + void NexusBridge::nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); @@ -455,6 +464,21 @@ int NexusInterface::requestDownloadURL(QString gameName, int modID, int fileID, return requestInfo.m_ID; } +int NexusInterface::requestEndorsementInfo(QObject *receiver, QVariant userData, const QString &subModule) +{ + NXMRequestInfo requestInfo(NXMRequestInfo::TYPE_ENDORSEMENTS, userData, subModule); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmEndorsementsAvailable(QVariant, QVariant, int)), + receiver, SLOT(nxmEndorsementsAvailable(QVariant, QVariant, int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game) @@ -478,12 +502,6 @@ int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QStrin return -1; } -bool NexusInterface::requiresLogin(const NXMRequestInfo &info) -{ - return (info.m_Type == NXMRequestInfo::TYPE_TOGGLEENDORSEMENT) - || (info.m_Type == NXMRequestInfo::TYPE_DOWNLOADURL); -} - IPluginGame* NexusInterface::getGame(QString gameName) const { auto gamePlugins = m_PluginContainer->plugins(); @@ -516,7 +534,7 @@ void NexusInterface::nextRequest() return; } - if (requiresLogin(m_RequestQueue.head()) && !getAccessManager()->validated()) { + if (!getAccessManager()->validated()) { if (!getAccessManager()->validateAttempted()) { emit needLogin(); return; @@ -581,6 +599,9 @@ void NexusInterface::nextRequest() else url = QString("%1/games/%2/mods/%3/files/%4/download_link").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID); } break; + case NXMRequestInfo::TYPE_ENDORSEMENTS: { + url = QString("%1/user/endorsements").arg(info.m_URL); + } break; case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { QString endorse = info.m_Endorse ? "endorse" : "abstain"; url = QString("%1/games/%2/mods/%3/%4").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(endorse); @@ -674,6 +695,9 @@ void NexusInterface::requestFinished(std::list::iterator iter) case NXMRequestInfo::TYPE_DOWNLOADURL: { emit nxmDownloadURLsAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); } break; + case NXMRequestInfo::TYPE_ENDORSEMENTS: { + emit nxmEndorsementsAvailable(iter->m_UserData, result, iter->m_ID); + } break; case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { emit nxmEndorsementToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); } break; @@ -747,7 +771,7 @@ void NexusInterface::requestTimeout() } namespace { - QString get_management_url(MOBase::IPluginGame const *game) + QString get_management_url() { return "https://api.nexusmods.com/v1"; } @@ -769,7 +793,7 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID , m_Timeout(nullptr) , m_Reroute(false) , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url(game)) + , m_URL(get_management_url()) , m_SubModule(subModule) , m_NexusGameID(game->nexusGameID()) , m_GameName(game->gameNexusName()) @@ -794,7 +818,7 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID , m_Timeout(nullptr) , m_Reroute(false) , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url(game)) + , m_URL(get_management_url()) , m_SubModule(subModule) , m_NexusGameID(game->nexusGameID()) , m_GameName(game->gameNexusName()) @@ -818,13 +842,35 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID , m_Timeout(nullptr) , m_Reroute(false) , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url(game)) + , m_URL(get_management_url()) , m_SubModule(subModule) , m_NexusGameID(game->nexusGameID()) , m_GameName(game->gameNexusName()) , m_Endorse(false) {} +NexusInterface::NXMRequestInfo::NXMRequestInfo(Type type + , QVariant userData + , const QString &subModule +) + : m_ModID(0) + , m_ModVersion("0") + , m_FileID(0) + , m_Reply(nullptr) + , m_Type(type) + , m_UpdatePeriod(UpdatePeriod::NONE) + , m_UserData(userData) + , m_Timeout(nullptr) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(get_management_url()) + , m_SubModule(subModule) + , m_NexusGameID(0) + , m_GameName("") + , m_Endorse(false) +{} + + NexusInterface::NXMRequestInfo::NXMRequestInfo(UpdatePeriod period , NexusInterface::NXMRequestInfo::Type type , QVariant userData @@ -841,7 +887,7 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(UpdatePeriod period , m_Timeout(nullptr) , m_Reroute(false) , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url(game)) + , m_URL(get_management_url()) , m_SubModule(subModule) , m_NexusGameID(game->nexusGameID()) , m_GameName(game->gameNexusName()) diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 06b19947..ac9e466b 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -104,6 +104,7 @@ public slots: void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorMessage); @@ -298,6 +299,10 @@ public: **/ int requestDownloadURL(QString gameName, int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + + int requestEndorsementInfo(QObject *receiver, QVariant userData, const QString &subModule); + + /** * @param gameName the game short name to support multiple game sources * @brief toggle endorsement state of the mod @@ -401,6 +406,7 @@ signals: void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString); void requestsChanged(int queueCount, std::tuple requestsRemaining); @@ -433,6 +439,7 @@ private: TYPE_FILES, TYPE_FILEINFO, TYPE_DOWNLOADURL, + TYPE_ENDORSEMENTS, TYPE_TOGGLEENDORSEMENT, TYPE_GETUPDATES, TYPE_CHECKUPDATES @@ -451,6 +458,7 @@ private: NXMRequestInfo(int modID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); NXMRequestInfo(int modID, QString modVersion, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + NXMRequestInfo(Type type, QVariant userData, const QString &subModule); NXMRequestInfo(UpdatePeriod period, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); private: @@ -464,7 +472,6 @@ private: NexusInterface(PluginContainer *pluginContainer); void nextRequest(); void requestFinished(std::list::iterator iter); - bool requiresLogin(const NXMRequestInfo &info); MOBase::IPluginGame *getGame(QString gameName) const; QString getOldModsURL(QString gameName) const; -- cgit v1.3.1 From 208f389300b308966b8354a631bc18f1ad0880d5 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 22 Feb 2019 22:18:05 -0600 Subject: Change setGameName parameters to match MOBase changes --- src/modinfo.h | 2 +- src/modinfobackup.h | 2 +- src/modinfoforeign.h | 2 +- src/modinfooverwrite.h | 2 +- src/modinforegular.cpp | 2 +- src/modinforegular.h | 2 +- src/modinfoseparator.h | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/modinfo.h b/src/modinfo.h index c3115685..52c29154 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -289,7 +289,7 @@ public: * * @param gameName the source game shortName */ - virtual void setGameName(QString gameName) = 0; + virtual void setGameName(const QString &gameName) = 0; /** * @brief set/change the nexus mod id of this mod diff --git a/src/modinfobackup.h b/src/modinfobackup.h index 3c948931..cab613c9 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -14,7 +14,7 @@ public: virtual bool updateIgnored() const { return false; } virtual bool downgradeAvailable() const { return false; } virtual bool updateNXMInfo() { return false; } - virtual void setGameName(QString) {} + virtual void setGameName(const QString&) {} virtual void setNexusID(int) {} virtual void endorse(bool) {} virtual void parseNexusInfo() {} diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 7f5bb711..1fdf5409 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -20,7 +20,7 @@ public: virtual bool setName(const QString&) { return false; } virtual void setComments(const QString&) {} virtual void setNotes(const QString&) {} - virtual void setGameName(QString) {} + virtual void setGameName(const QString&) {} virtual void setNexusID(int) {} virtual void setNewestVersion(const MOBase::VersionInfo&) {} virtual void ignoreUpdate(bool) {} diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index 71c20bee..e32653d5 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -22,7 +22,7 @@ public: virtual bool setName(const QString&) { return false; } virtual void setComments(const QString&) {} virtual void setNotes(const QString&) {} - virtual void setGameName(QString) {} + virtual void setGameName(const QString&) {} virtual void setNexusID(int) {} virtual void setNewestVersion(const MOBase::VersionInfo&) {} virtual void ignoreUpdate(bool) {} diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 8bef1c28..2545b3f8 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -372,7 +372,7 @@ void ModInfoRegular::setNotes(const QString ¬es) m_MetaInfoChanged = true; } -void ModInfoRegular::setGameName(QString gameName) +void ModInfoRegular::setGameName(const QString &gameName) { m_GameName = gameName; m_MetaInfoChanged = true; diff --git a/src/modinforegular.h b/src/modinforegular.h index c1785818..aaab778a 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -107,7 +107,7 @@ public: * * @param gameName the source game shortName */ - void setGameName(QString gameName); + void setGameName(const QString &gameName); /** * @brief set/change the nexus mod id of this mod diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index 865a5179..3560d786 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -20,7 +20,7 @@ public: virtual int getNexusID() const { return -1; } - virtual void setGameName(QString /*gameName*/) {} + virtual void setGameName(const QString& /*gameName*/) {} virtual void setNexusID(int /*modID*/) {} -- cgit v1.3.1 From d5332c5080ea5e1d0812cc900bb5ed6e20ff8f0e Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 6 Mar 2019 20:12:09 -0600 Subject: Add support for displaying tracked mods and setting tracked status --- src/mainwindow.cpp | 112 ++++ src/mainwindow.h | 4 + src/modflagicondelegate.cpp | 33 +- src/modinfo.h | 28 +- src/modinfoforeign.h | 2 + src/modinfooverwrite.h | 2 + src/modinforegular.cpp | 51 +- src/modinforegular.h | 22 + src/modlist.cpp | 1 + src/nexusinterface.cpp | 100 +++- src/nexusinterface.h | 46 +- src/organizer_en.ts | 1251 +++++++++++++++++++++---------------------- src/resources.qrc | 1 + src/resources/tracked.png | Bin 0 -> 1969 bytes 14 files changed, 1002 insertions(+), 651 deletions(-) create mode 100644 src/resources/tracked.png (limited to 'src/modinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 58c80b87..3f6da2a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2826,6 +2826,73 @@ void MainWindow::unendorse_clicked() } } + +void MainWindow::trackMod(ModInfo::Ptr mod, bool doTrack) +{ + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + ModInfo::getByIndex(m_ContextRow)->track(doTrack); + } else { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + m_OrganizerCore.doAfterLogin([&]() { this->trackMod(mod, doTrack); }); + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); + } + } +} + + +void MainWindow::track_clicked() +{ + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + for (auto idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(true); + } + } else { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + for (auto idx : selection->selectedRows()) { + auto modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + m_OrganizerCore.doAfterLogin([&]() { this->trackMod(modInfo, true); }); + } + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); + } + } + } else { + trackMod(ModInfo::getByIndex(m_ContextRow), true); + } +} + +void MainWindow::untrack_clicked() +{ + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + for (auto idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(false); + } + } else { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + for (auto idx : selection->selectedRows()) { + auto modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + m_OrganizerCore.doAfterLogin([&]() { this->trackMod(modInfo, false); }); + } + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); + } + } + } else { + trackMod(ModInfo::getByIndex(m_ContextRow), false); + } +} + void MainWindow::validationFailed(const QString &error) { qDebug("Nexus API validation failed: %s", qUtf8Printable(error)); @@ -4009,6 +4076,7 @@ void MainWindow::checkModsForUpdates() if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { ModInfo::checkAllForUpdate(&m_PluginContainer, this); NexusInterface::instance(&m_PluginContainer)->requestEndorsementInfo(this, QVariant(), QString()); + NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { @@ -4595,6 +4663,22 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) } } + if (info->getNexusID() > 0) { + switch (info->trackedState()) { + case ModInfo::TRACKED_FALSE: { + menu.addAction(tr("Start tracking"), this, SLOT(track_clicked())); + } break; + case ModInfo::TRACKED_TRUE: { + menu.addAction(tr("Stop tracking"), this, SLOT(untrack_clicked())); + } break; + default: { + QAction *action = new QAction(tr("Tracked state unknown"), &menu); + action->setEnabled(false); + menu.addAction(action); + } break; + } + } + menu.addSeparator(); std::vector flags = info->getFlags(); @@ -5741,6 +5825,34 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa } } +void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int) +{ + QMap gameNames; + for (auto game : m_PluginContainer.plugins()) { + gameNames[game->gameNexusName()] = game->gameShortName(); + } + + for (unsigned int i = 0; i < ModInfo::getNumMods(); i++) { + auto modInfo = ModInfo::getByIndex(i); + if (modInfo->getNexusID() <= 0) + continue; + + bool found = false; + auto resultsList = resultData.toList(); + for (auto item : resultsList) { + auto results = item.toMap(); + if ((gameNames[results["domain_name"].toString()].compare(modInfo->getGameName(), Qt::CaseInsensitive) == 0) && + (results["mod_id"].toInt() == modInfo->getNexusID())) { + found = true; + break; + } + } + + modInfo->setIsTracked(found); + modInfo->saveMeta(); + } +} + void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { QVariantList serverList = resultData.toList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index ff23b8fb..152591e8 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -428,6 +428,8 @@ private slots: void endorse_clicked(); void dontendorse_clicked(); void unendorse_clicked(); + void track_clicked(); + void untrack_clicked(); void ignoreMissingData_clicked(); void markConverted_clicked(); void visitOnNexus_clicked(); @@ -517,6 +519,7 @@ private slots: void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int); + void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int); void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString); @@ -539,6 +542,7 @@ private slots: void resumeDownload(int downloadIndex); void endorseMod(ModInfo::Ptr mod); void unendorseMod(ModInfo::Ptr mod); + void trackMod(ModInfo::Ptr mod, bool doTrack); void cancelModListEditor(); void lockESPIndex(); diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index 343c6ee3..fbd951eb 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -93,26 +93,27 @@ QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const { QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const { switch (flag) { - case ModInfo::FLAG_BACKUP: return ":/MO/gui/emblem_backup"; - case ModInfo::FLAG_INVALID: return ":/MO/gui/problem"; - case ModInfo::FLAG_NOTENDORSED: return ":/MO/gui/emblem_notendorsed"; - case ModInfo::FLAG_NOTES: return ":/MO/gui/emblem_notes"; - case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; - case ModInfo::FLAG_CONFLICT_OVERWRITE: return ":/MO/gui/emblem_conflict_overwrite"; - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return ":/MO/gui/emblem_conflict_overwritten"; - case ModInfo::FLAG_CONFLICT_REDUNDANT: return ":MO/gui/emblem_conflict_redundant"; - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return ":/MO/gui/archive_loose_conflict_overwrite"; - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return ":/MO/gui/archive_loose_conflict_overwritten"; - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return ":/MO/gui/archive_conflict_mixed"; - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return ":/MO/gui/archive_conflict_winner"; - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return ":/MO/gui/archive_conflict_loser"; - case ModInfo::FLAG_ALTERNATE_GAME: return ":MO/gui/alternate_game"; + case ModInfo::FLAG_BACKUP: return QStringLiteral(":/MO/gui/emblem_backup"); + case ModInfo::FLAG_INVALID: return QStringLiteral(":/MO/gui/problem"); + case ModInfo::FLAG_NOTENDORSED: return QStringLiteral(":/MO/gui/emblem_notendorsed"); + case ModInfo::FLAG_NOTES: return QStringLiteral(":/MO/gui/emblem_notes"); + case ModInfo::FLAG_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return QStringLiteral(":/MO/gui/emblem_conflict_redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwrite"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwritten"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/archive_conflict_mixed"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_conflict_winner"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_conflict_loser"); + case ModInfo::FLAG_ALTERNATE_GAME: return QStringLiteral(":/MO/gui/alternate_game"); case ModInfo::FLAG_FOREIGN: return QString(); case ModInfo::FLAG_SEPARATOR: return QString(); case ModInfo::FLAG_OVERWRITE: return QString(); case ModInfo::FLAG_PLUGIN_SELECTED: return QString(); - default: - qWarning("ModInfo flag %d has no defined icon", flag); + case ModInfo::FLAG_TRACKED: return QStringLiteral(":/MO/gui/tracked"); + default: + qWarning("ModInfo flag %d has no defined icon", flag); return QString(); } } diff --git a/src/modinfo.h b/src/modinfo.h index 52c29154..365dfe50 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -78,7 +78,8 @@ public: FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, FLAG_ARCHIVE_CONFLICT_MIXED, FLAG_PLUGIN_SELECTED, - FLAG_ALTERNATE_GAME + FLAG_ALTERNATE_GAME, + FLAG_TRACKED, }; enum EContent { @@ -114,6 +115,12 @@ public: ENDORSED_NEVER }; + enum ETrackedState { + TRACKED_FALSE, + TRACKED_TRUE, + TRACKED_UNKNOWN, + }; + enum EModType { MOD_DEFAULT, MOD_DLC, @@ -362,6 +369,13 @@ public: */ virtual void setNeverEndorse() = 0; + /** + * update the tracked state for the mod. This only changes the + * buffered state, it does not sync with Nexus + * @param tracked the new tracked state + */ + virtual void setIsTracked(bool tracked) = 0; + /** * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices * @return true if the mod was successfully removed @@ -375,6 +389,13 @@ public: */ virtual void endorse(bool doEndorse) = 0; + /** + * @brief track or untrack the mod. This will sync with nexus! + * @param doTrack if true, the mod is tracked, if false, it's untracked. + * @note if doTrack doesn't differ from the current value, nothing happens. + */ + virtual void track(bool doTrack) = 0; + /** * @brief clear all caches held for this mod */ @@ -636,6 +657,11 @@ public: */ virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; } + /** + * @return true if the file is being tracked on nexus + */ + virtual ETrackedState trackedState() const { return TRACKED_FALSE; } + /** * @brief updates the valid-flag for this mod */ diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 1fdf5409..72fbb04f 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -29,8 +29,10 @@ public: virtual void addNexusCategory(int) {} virtual void setIsEndorsed(bool) {} virtual void setNeverEndorse() {} + virtual void setIsTracked(bool) {} virtual bool remove() { return false; } virtual void endorse(bool) {} + virtual void track(bool) {} virtual void parseNexusInfo() {} virtual bool isEmpty() const { return false; } virtual QString name() const; diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index e32653d5..ff0d8cd4 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -31,8 +31,10 @@ public: virtual void addNexusCategory(int) {} virtual void setIsEndorsed(bool) {} virtual void setNeverEndorse() {} + virtual void setIsTracked(bool) {} virtual bool remove() { return false; } virtual void endorse(bool) {} + virtual void track(bool) {} virtual void parseNexusInfo() {} virtual bool alwaysEnabled() const { return true; } virtual bool isEmpty() const; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 2545b3f8..a271c4e8 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -35,6 +35,7 @@ ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGa , m_Validated(false) , m_MetaInfoChanged(false) , m_EndorsedState(ENDORSED_UNKNOWN) + , m_TrackedState(TRACKED_UNKNOWN) , m_NexusBridge(pluginContainer) { testValid(); @@ -55,6 +56,8 @@ ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGa , this, SLOT(nxmDescriptionAvailable(QString,int,QVariant,QVariant))); connect(&m_NexusBridge, SIGNAL(endorsementToggled(QString,int,QVariant,QVariant)) , this, SLOT(nxmEndorsementToggled(QString,int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(trackingToggled(QString,int,QVariant,bool)) + , this, SLOT(nxmTrackingToggled(QString,int,QVariant,bool))); connect(&m_NexusBridge, SIGNAL(requestFailed(QString,int,int,QVariant,QNetworkReply::NetworkError,QString)) , this, SLOT(nxmRequestFailed(QString,int,int,QVariant,QNetworkReply::NetworkError,QString))); } @@ -102,6 +105,7 @@ void ModInfoRegular::readMeta() m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); m_NexusLastModified = QDateTime::fromString(metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), Qt::ISODate); m_Color = metaFile.value("color",QColor()).value(); + m_TrackedState = metaFile.value("tracked", false).toBool() ? TRACKED_TRUE : TRACKED_FALSE; if (metaFile.contains("endorsed")) { if (metaFile.value("endorsed").canConvert()) { switch (metaFile.value("endorsed").toInt()) { @@ -114,7 +118,7 @@ void ModInfoRegular::readMeta() m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; } } - + QString categoriesString = metaFile.value("category", "").toString(); QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); @@ -173,6 +177,7 @@ void ModInfoRegular::saveMeta() if (m_EndorsedState != ENDORSED_UNKNOWN) { metaFile.setValue("endorsed", m_EndorsedState); } + metaFile.setValue("tracked", m_TrackedState); metaFile.remove("installedFiles"); metaFile.beginWriteArray("installedFiles"); @@ -259,6 +264,18 @@ void ModInfoRegular::nxmEndorsementToggled(QString, int, QVariant, QVariant resu } +void ModInfoRegular::nxmTrackingToggled(QString, int, QVariant, bool tracked) +{ + if (tracked) + m_TrackedState = TRACKED_TRUE; + else + m_TrackedState = TRACKED_FALSE; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + + void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, QNetworkReply::NetworkError error, const QString &errorMessage) { QString fullMessage = errorMessage; @@ -414,6 +431,14 @@ void ModInfoRegular::setEndorsedState(EEndorsedState endorsedState) } } +void ModInfoRegular::setTrackedState(ETrackedState trackedState) +{ + if (trackedState != m_TrackedState) { + m_TrackedState = trackedState; + m_MetaInfoChanged = true; + } +} + void ModInfoRegular::setInstallationFile(const QString &fileName) { m_InstallationFile = fileName; @@ -433,13 +458,19 @@ void ModInfoRegular::setIsEndorsed(bool endorsed) } } - void ModInfoRegular::setNeverEndorse() { m_EndorsedState = ENDORSED_NEVER; m_MetaInfoChanged = true; } +void ModInfoRegular::setIsTracked(bool tracked) +{ + if (tracked != (m_TrackedState == TRACKED_TRUE)) { + m_TrackedState = tracked ? TRACKED_TRUE : TRACKED_FALSE; + m_MetaInfoChanged = true; + } +} void ModInfoRegular::setColor(QColor color) { @@ -465,6 +496,13 @@ void ModInfoRegular::endorse(bool doEndorse) } } +void ModInfoRegular::track(bool doTrack) +{ + if (doTrack != (m_TrackedState == TRACKED_TRUE)) { + m_NexusBridge.requestToggleTracking(m_GameName, getNexusID(), doTrack, QVariant(1)); + } +} + void ModInfoRegular::markConverted(bool converted) { m_Converted = converted; @@ -518,6 +556,10 @@ std::vector ModInfoRegular::getFlags() const Settings::instance().endorsementIntegration()) { result.push_back(ModInfo::FLAG_NOTENDORSED); } + if ((m_NexusID > 0) && + (trackedState() == TRACKED_TRUE)) { + result.push_back(ModInfo::FLAG_TRACKED); + } if (!isValid() && !m_Validated) { result.push_back(ModInfo::FLAG_INVALID); } @@ -667,6 +709,11 @@ ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const return m_EndorsedState; } +ModInfoRegular::ETrackedState ModInfoRegular::trackedState() const +{ + return m_TrackedState; +} + QDateTime ModInfoRegular::getLastNexusUpdate() const { return m_LastNexusUpdate; diff --git a/src/modinforegular.h b/src/modinforegular.h index aaab778a..f70487a2 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -177,6 +177,13 @@ public: */ virtual void setNeverEndorse(); + /** + * update the tracked state for the mod. This only changes the + * buffered state. It does not sync with Nexus + * @param tracked the new tracked state + */ + virtual void setIsTracked(bool tracked); + /** * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices * @return true if the mod was successfully removed @@ -190,6 +197,13 @@ public: */ virtual void endorse(bool doEndorse); + /** + * @brief track or untrack the mod. This will sync with nexus! + * @param doTrack if true, the mod is tracked, if false, it's untracked. + * @note if doTrack doesn't differ from the current value, nothing happens. + */ + virtual void track(bool doTrack); + /** * @brief updates the mod to flag it as converted in order to ignore the alternate game warning */ @@ -333,6 +347,11 @@ public: */ virtual EEndorsedState endorsedState() const; + /** + * @return true if the file is being tracked on nexus + */ + virtual ETrackedState trackedState() const; + /** * @brief get the last time nexus was checked for file updates on this mod */ @@ -391,11 +410,13 @@ public: private: void setEndorsedState(EEndorsedState endorsedState); + void setTrackedState(ETrackedState trackedState); private slots: void nxmDescriptionAvailable(QString, int modID, QVariant userData, QVariant resultData); void nxmEndorsementToggled(QString, int, QVariant userData, QVariant resultData); + void nxmTrackingToggled(QString, int, QVariant userData, bool tracked); void nxmRequestFailed(QString, int modID, int fileID, QVariant userData, QNetworkReply::NetworkError error, const QString &errorMessage); protected: @@ -436,6 +457,7 @@ private: MOBase::VersionInfo m_IgnoredVersion; EEndorsedState m_EndorsedState; + ETrackedState m_TrackedState; NexusBridge m_NexusBridge; diff --git a/src/modlist.cpp b/src/modlist.cpp index 8e8ab2d2..3ca53949 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -169,6 +169,7 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); case ModInfo::FLAG_ALTERNATE_GAME: return tr("
This mod is for a different game, " "make sure it's compatible or it could cause crashes."); + case ModInfo::FLAG_TRACKED: return tr("Mod is being tracked on the website"); default: return ""; } } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 23dd7dbe..b1f50fd8 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include @@ -68,6 +69,11 @@ void NexusBridge::requestToggleEndorsement(QString gameName, int modID, QString m_RequestIDs.insert(m_Interface->requestToggleEndorsement(gameName, modID, modVersion, endorse, this, userData, m_SubModule)); } +void NexusBridge::requestToggleTracking(QString gameName, int modID, bool track, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestToggleTracking(gameName, modID, track, this, userData, m_SubModule)); +} + void NexusBridge::nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); @@ -142,6 +148,24 @@ void NexusBridge::nxmEndorsementToggled(QString gameName, int modID, QVariant us } } +void NexusBridge::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit trackedModsAvailable(userData, resultData); + } +} + +void NexusBridge::nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit trackingToggled(gameName, modID, userData, tracked); + } +} + void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorMessage) { std::set::iterator iter = m_RequestIDs.find(requestID); @@ -472,7 +496,6 @@ int NexusInterface::requestEndorsementInfo(QObject *receiver, QVariant userData, return requestInfo.m_ID; } - int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game) { @@ -495,6 +518,43 @@ int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QStrin return -1; } +int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule) +{ + NXMRequestInfo requestInfo(NXMRequestInfo::TYPE_TRACKEDMODS, userData, subModule); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmTrackedModsAvailable(QVariant, QVariant, int)), + receiver, SLOT(nxmTrackedModsAvailable(QVariant, QVariant, int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + +int NexusInterface::requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, + const QString &subModule, MOBase::IPluginGame const *game) +{ + if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) { + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game); + requestInfo.m_Track = track; + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)), + receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; + } + qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.") + .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests)); + return -1; +} + IPluginGame* NexusInterface::getGame(QString gameName) const { auto gamePlugins = m_PluginContainer->plugins(); @@ -555,6 +615,7 @@ void NexusInterface::nextRequest() QJsonObject postObject; QJsonDocument postData(postObject); + bool requestIsDelete = false; QString url; if (!info.m_Reroute) { @@ -603,6 +664,15 @@ void NexusInterface::nextRequest() postObject.insert("Version", info.m_ModVersion); postData.setObject(postObject); } break; + case NXMRequestInfo::TYPE_TOGGLETRACKING: { + url = QStringLiteral("%1/user/tracked_mods?domain_name=%2").arg(info.m_URL).arg(info.m_GameName); + postObject.insert("mod_id", info.m_ModID); + postData.setObject(postObject); + requestIsDelete = !info.m_Track; + } break; + case NXMRequestInfo::TYPE_TRACKEDMODS: { + url = QStringLiteral("%1/user/tracked_mods").arg(info.m_URL); + } break; } } else { url = info.m_URL; @@ -617,10 +687,18 @@ void NexusInterface::nextRequest() request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", QApplication::applicationVersion().toUtf8()); - if (postData.object().isEmpty()) - info.m_Reply = m_AccessManager->get(request); - else + if (postData.object().isEmpty()) { + if (!requestIsDelete) { + info.m_Reply = m_AccessManager->get(request); + } else { + info.m_Reply = m_AccessManager->deleteResource(request); + } + } else if (!requestIsDelete) { info.m_Reply = m_AccessManager->post(request, postData.toJson()); + } else { + // Qt doesn't support DELETE with a payload as that's technically against the HTTP standard... + info.m_Reply = m_AccessManager->sendCustomRequest(request, "DELETE", postData.toJson()); + } connect(info.m_Reply, SIGNAL(finished()), this, SLOT(requestFinished())); connect(info.m_Reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); @@ -701,6 +779,20 @@ void NexusInterface::requestFinished(std::list::iterator iter) case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { emit nxmEndorsementToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); } break; + case NXMRequestInfo::TYPE_TOGGLETRACKING: { + auto results = result.toMap(); + auto message = results["message"].toString(); + if (message.contains(QRegularExpression("User [0-9]+ is already Tracking Mod: [0-9]+")) || + message.contains(QRegularExpression("User [0-9]+ is now Tracking Mod: [0-9]+"))) { + emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, true, iter->m_ID); + } else if (message.contains(QRegularExpression("User [0-9]+ is no longer tracking [0-9]+")) || + message.contains(QRegularExpression("Users is not tracking mod. Unable to untrack."))) { + emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, false, iter->m_ID); + } + } break; + case NXMRequestInfo::TYPE_TRACKEDMODS: { + emit nxmTrackedModsAvailable(iter->m_UserData, result, iter->m_ID); + } break; } m_RemainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt(); diff --git a/src/nexusinterface.h b/src/nexusinterface.h index c1f9da41..a0f94562 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -98,6 +98,13 @@ public: */ virtual void requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData); + /** + * @brief requestToggleTracking + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + */ + virtual void requestToggleTracking(QString gameName, int modID, bool track, QVariant userData); + public slots: void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); @@ -106,6 +113,8 @@ public slots: void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); + void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorMessage); private: @@ -330,6 +339,36 @@ public: int requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + int requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule); + + /** + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestToggleTracking(gameName, modID, track, receiver, userData, subModule, getGame(gameName)); + } + + /** + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game); + /** * @param directory the directory to store cache files **/ @@ -402,6 +441,8 @@ signals: void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); + void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString); void requestsChanged(int queueCount, std::tuple requestsRemaining); @@ -436,7 +477,9 @@ private: TYPE_ENDORSEMENTS, TYPE_TOGGLEENDORSEMENT, TYPE_GETUPDATES, - TYPE_CHECKUPDATES + TYPE_CHECKUPDATES, + TYPE_TOGGLETRACKING, + TYPE_TRACKEDMODS, } m_Type; UpdatePeriod m_UpdatePeriod; QVariant m_UserData; @@ -448,6 +491,7 @@ private: bool m_Reroute; int m_ID; int m_Endorse; + int m_Track; NXMRequestInfo(int modID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); NXMRequestInfo(int modID, QString modVersion, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 7f8d3a9b..c8e7c286 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -829,58 +829,58 @@ File %3: %4
- + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1497,7 +1497,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - + Categories @@ -1563,20 +1563,20 @@ p, li { white-space: pre-wrap; } - + Restore Backup... - - + + Create Backup - + Active: @@ -1586,60 +1586,60 @@ p, li { white-space: pre-wrap; } - + List of available mods. - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - + + + + Filter - + Nexus API Queued and Remaining Requests - + <html><head/><body><p>This tracks the number of queued Nexus API requests on the left (<span style=" font-weight:600;">Q</span>) and the remaining daily (<span style=" font-weight:600;">D</span>) and hourly (<span style=" font-weight:600;">H</span>) requests on the right. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens.</p></body></html> - + API: Q: 0 | D: 0 | H: 0 - + Clear all Filters - + No groups - + Nexus IDs - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1649,12 +1649,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1663,17 +1663,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1682,32 +1682,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1716,27 +1716,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1744,72 +1744,72 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1820,197 +1820,197 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Tool Bar - + Install Mod - + Install &Mod - + Install a new mod from an archive - + Ctrl+M - + Profiles - + &Profiles - + Configure Profiles - + Ctrl+P - + Executables - + &Executables - + Configure the executables that can be started through Mod Organizer - + Ctrl+E - - + + Tools - + &Tools - + Ctrl+I - + Settings - + &Settings - + Configure settings and workarounds - + Ctrl+S - + Nexus - + Search nexus network for more mods - + Ctrl+N - - + + Update - + Mod Organizer is up-to-date - - + + No Notifications - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Help - + Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Change Game - + Open the Instance selection dialog to manage a different Game @@ -2035,860 +2035,860 @@ p, li { white-space: pre-wrap; } - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + Notifications - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + 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. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + You need to be logged in with Nexus to resume a download - - - - + + + + You need to be logged in with Nexus to endorse - - + + Endorsing multiple mods will take a while. Please wait... - - + + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this Mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + Web page for this mod is unknown - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit web page - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -2896,12 +2896,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2909,22 +2909,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -2932,373 +2932,373 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Select binary - + Binary - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4043,18 +4043,18 @@ p, li { white-space: pre-wrap; } ModInfoRegular - + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -4205,178 +4205,183 @@ p, li { white-space: pre-wrap; } - + + Mod is being tracked on the website + + + + Non-MO - + invalid - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + This file has been marked as "Old". There is most likely an updated version of this file available. - + This file has been marked as "Deleted"! You may want to check for an update or remove the nexus ID from this mod! - + %1 minute(s) and %2 second(s) - + This mod will be available to check in %2. - + Categories: <br> - + Invalid name - + Name is already in use by another mod - + drag&drop failed: %1 - + Confirm - + Are you sure you want to remove "%1"? - + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + Source Game - + Nexus ID - + Installation - + Notes - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Depicts the content of the mod:<br><table cellspacing=7><tr><td><img src=":/MO/gui/content/plugin" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr><tr><td><img src=":/MO/gui/content/interface" width=32/></td><td>Interface</td></tr><tr><td><img src=":/MO/gui/content/mesh" width=32/></td><td>Meshes</td></tr><tr><td><img src=":/MO/gui/content/bsa" width=32/></td><td>BSA</td></tr><tr><td><img src=":/MO/gui/content/texture" width=32/></td><td>Textures</td></tr><tr><td><img src=":/MO/gui/content/sound" width=32/></td><td>Sounds</td></tr><tr><td><img src=":/MO/gui/content/music" width=32/></td><td>Music</td></tr><tr><td><img src=":/MO/gui/content/string" width=32/></td><td>Strings</td></tr><tr><td><img src=":/MO/gui/content/script" width=32/></td><td>Scripts (Papyrus)</td></tr><tr><td><img src=":/MO/gui/content/skse" width=32/></td><td>Script Extender plugins</td></tr><tr><td><img src=":/MO/gui/content/skyproc" width=32/></td><td>SkyProc Patcher</td></tr><tr><td><img src=":/MO/gui/content/menu" width=32/></td><td>Mod Configuration Menu</td></tr><tr><td><img src=":/MO/gui/content/inifile" width=32/></td><td>INI files</td></tr><tr><td><img src=":/MO/gui/content/modgroup" width=32/></td><td>ModGroup files</td></tr></table> - + Time this mod was installed - + User notes about the mod @@ -4418,37 +4423,37 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Validating Nexus Connection - + Verifying Nexus login - + There was a timeout during the request - + Unknown error - + Validation failed, please reauthenticate in the Settings -> Nexus tab: %1 - + Could not parse response. Invalid JSON. - + Unknown error. @@ -4464,17 +4469,17 @@ p, li { white-space: pre-wrap; } NexusInterface - + Failed to guess mod id for "%1", please pick the correct one - + empty response - + invalid response @@ -4482,234 +4487,234 @@ p, li { white-space: pre-wrap; } OrganizerCore - - + + Failed to write settings - + An error occured trying to update MO settings to %1: %2 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + An error occured trying to write back MO settings to %1: %2 - - + + Download started - + Download failed - - - - + + + + Installation cancelled - - + + Another installation is currently in progress. - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod not found: %1 - - + + The mod was not installed completely. - + Executable not found: %1 - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Steam: Access Denied - + MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely neccessary. Restart MO as administrator? - + Error - + Windows Event Log Error - + The Windows Event Log service is disabled and/or not running. This prevents USVFS from running properly. Your mods may not be working in the executable that you are launching. Note that you may have to restart MO and/or your PC after the service is fixed. Continue launching %1? - + Blacklisted Executable - + The executable you are attempted to launch is blacklisted in the virtual file system. This will likely prevent the executable, and any executables that are launched by this one, from seeing any mods. This could extend to INI files, save games and any other virtualized files. Continue launching %1? - + No profile set - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. @@ -5367,7 +5372,7 @@ p, li { white-space: pre-wrap; } - + Error @@ -5689,7 +5694,7 @@ If the folder was still in use, restart MO and try again. - + Failed to create "%1". Your user account probably lacks permission. @@ -5711,59 +5716,59 @@ If the folder was still in use, restart MO and try again. - + Canceled finding game in "%1". - + No game identified in "%1". The directory is required to contain the game binary. - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + failed to start shortcut: %1 - + failed to start application: %1 - - + + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -5814,12 +5819,12 @@ If the folder was still in use, restart MO and try again. - + Script Extender - + Proxy DLL @@ -5966,58 +5971,58 @@ You will be asked if you want to allow helper.exe to make changes to the system. - + New update available (%1) - + Do you want to install update? All your mods and setup will be left untouched. Select Show Details option to see the full change-log. - + Install - + Download failed - + Failed to find correct download, please try again later. - + Update - + Download in progress - + Download failed: %1 - + Failed to install update: %1 - + Failed to start %1: %2 - + Error @@ -6035,35 +6040,35 @@ Select Show Details option to see the full change-log. - - + + attempt to store setting for unknown plugin "%1" - - + + Error - + Failed to retrieve a Nexus API key! Please try again. A browser window should open asking you to authorize. Failed to retrieve a Nexus API key! Please try again.A browser window should open asking you to authorize. - + Failed to create "%1", you may not have the necessary permission. path remains unchanged. - + Restart Mod Organizer? - + In order to reset the window geometries, MO must be restarted. Restart it now? @@ -6391,92 +6396,103 @@ p, li { white-space: pre-wrap; } - + + + <html><head/><body><p>By default, a counter is displayed under the mod list. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> + + + + + Hide API Request Counter + + + + Associate with "Download with manager" links - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Steam - + Username - + Password - + If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure. - + Plugins - + Author: - + Version: - + Description: - + Key - + Value - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -6492,17 +6508,17 @@ p, li { white-space: pre-wrap; } - + Load Mechanism - + Select loading mechanism. See help for details. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. @@ -6513,47 +6529,28 @@ If you use the Steam version of Oblivion the default will NOT work. In this case - - NMM Version - - - - - The Version of Nexus Mod Manager to impersonate. - - - - - Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. - -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - - - - + Enforces that inactive ESPs and ESMs are never loaded. - + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - + Hide inactive ESPs/ESMs - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. @@ -6561,66 +6558,66 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Display mods installed outside MO - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable parsing of Archives (Experimental Feature) - - + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + Back-date BSAs - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -6629,48 +6626,48 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Configure Executables Blacklist - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - + Max Dumps To Keep - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. Set "Crash Dumps" above to None to disable crash dump collection. @@ -6678,12 +6675,12 @@ programs you are intentionally running. - + Hint: right click link and copy link location - + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. @@ -6693,17 +6690,17 @@ programs you are intentionally running. - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -6714,37 +6711,37 @@ programs you are intentionally running. - + None - + Mini (recommended) - + Data - + Full - + Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". "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 regluar use. On the "Error" level the log file usually remains empty. @@ -6752,22 +6749,22 @@ programs you are intentionally running. - + Debug - + Info (recommended) - + Warning - + Error @@ -6782,12 +6779,12 @@ programs you are intentionally running. - + Executables Blacklist - + Enter one executable per line to be blacklisted from the virtual file system. Mods and other virtualized files will not be visible to these executables and any executables launched by them. @@ -6798,47 +6795,47 @@ Example: - + Select base directory - + Select download directory - + Select mod directory - + Select cache directory - + Select profiles directory - + Select overwrite directory - + Select game executable - + Confirm? - + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? diff --git a/src/resources.qrc b/src/resources.qrc index 5ed1780e..8645b27e 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -82,6 +82,7 @@ resources/archive-conflict-winner.png resources/game-warning.png resources/game-warning-16.png + resources/tracked.png
resources/contents/jigsaw-piece.png diff --git a/src/resources/tracked.png b/src/resources/tracked.png new file mode 100644 index 00000000..11b1e8c8 Binary files /dev/null and b/src/resources/tracked.png differ -- cgit v1.3.1 From e8e7f70dedd8808d44cc3156ec2687c83153f5e9 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 9 Mar 2019 21:41:20 -0600 Subject: Allow selecting overwrite to trigger mod conflict highlights --- src/modflagicondelegate.cpp | 4 ++++ src/modinfo.cpp | 7 ++++--- src/modinfo.h | 3 ++- src/modinfooverwrite.cpp | 5 ++++- src/modinfooverwrite.h | 6 +++--- src/modinfowithconflictinfo.cpp | 20 ++++++++++---------- 6 files changed, 27 insertions(+), 18 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index fbd951eb..c3142962 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -36,6 +36,10 @@ QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const { ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); std::vector flags = info->getFlags(); + // Don't do flags for overwrite + if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE) != flags.end()) + return result; + // insert conflict icons to provide nicer alignment { // insert loose file conflicts first auto iter = std::find_first_of(flags.begin(), flags.end(), diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 661681d8..13b40aba 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -114,11 +114,12 @@ QString ModInfo::getContentTypeName(int contentType) } } -void ModInfo::createFromOverwrite(PluginContainer *pluginContainer) +void ModInfo::createFromOverwrite(PluginContainer *pluginContainer, + MOShared::DirectoryEntry **directoryStructure) { QMutexLocker locker(&s_Mutex); - s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite(pluginContainer))); + s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite(pluginContainer, directoryStructure))); } unsigned int ModInfo::getNumMods() @@ -255,7 +256,7 @@ void ModInfo::updateFromDisc(const QString &modDirectory, } } - createFromOverwrite(pluginContainer); + createFromOverwrite(pluginContainer, directoryStructure); std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); diff --git a/src/modinfo.h b/src/modinfo.h index 365dfe50..81f59b61 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -750,7 +750,8 @@ protected: private: - static void createFromOverwrite(PluginContainer *pluginContainer); + static void createFromOverwrite(PluginContainer *pluginContainer, + MOShared::DirectoryEntry **directoryStructure); protected: diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 0e428483..37c8c650 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -6,7 +6,8 @@ #include #include -ModInfoOverwrite::ModInfoOverwrite(PluginContainer *pluginContainer) : ModInfo(pluginContainer) +ModInfoOverwrite::ModInfoOverwrite(PluginContainer *pluginContainer, MOShared::DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(pluginContainer, directoryStructure) { testValid(); } @@ -31,6 +32,8 @@ std::vector ModInfoOverwrite::getFlags() const result.push_back(FLAG_OVERWRITE); if (m_PluginSelected) result.push_back(FLAG_PLUGIN_SELECTED); + for (auto flag : ModInfoWithConflictInfo::getFlags()) + result.push_back(flag); return result; } diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index ff0d8cd4..c5f58c2e 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -1,11 +1,11 @@ #ifndef MODINFOOVERWRITE_H #define MODINFOOVERWRITE_H -#include "modinfo.h" +#include "modinfowithconflictinfo.h" #include -class ModInfoOverwrite : public ModInfo +class ModInfoOverwrite : public ModInfoWithConflictInfo { Q_OBJECT @@ -67,7 +67,7 @@ public: private: - ModInfoOverwrite(PluginContainer *pluginContainer); + ModInfoOverwrite(PluginContainer *pluginContainer, MOShared::DirectoryEntry **directoryStructure ); }; diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 744f323c..7a2b0071 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -46,16 +46,16 @@ std::vector ModInfoWithConflictInfo::getFlags() const default: { /* NOP */ } } switch (isArchiveConflicted()) { - case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED); - } break; - case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE); - } break; - case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN); - } break; - default: { /* NOP */ } + case CONFLICT_MIXED: { + result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED); + } break; + case CONFLICT_OVERWRITE: { + result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE); + } break; + case CONFLICT_OVERWRITTEN: { + result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN); + } break; + default: { /* NOP */ } } return result; } -- cgit v1.3.1 From c3ff652ff8c612054fa102234677c74fcd19a51c Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 22 Mar 2019 17:33:10 -0500 Subject: Only change to the update filter if mods are being checked for update or mods have updates available --- src/mainwindow.cpp | 24 ++++++++++++++++++------ src/modinfo.cpp | 18 ++++++++++++++++-- src/modinfo.h | 12 +++++++++++- 3 files changed, 45 insertions(+), 9 deletions(-) (limited to 'src/modinfo.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 669820be..8fe786ba 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4075,28 +4075,40 @@ void MainWindow::saveArchiveList() void MainWindow::checkModsForUpdates() { - statusBar()->show(); + bool checkingModsForUpdate = false; if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - ModInfo::checkAllForUpdate(&m_PluginContainer, this); + checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this); NexusInterface::instance(&m_PluginContainer)->requestEndorsementInfo(this, QVariant(), QString()); NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); + statusBar()->show(); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); } } - m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + bool updatesAvailable = false; + for (auto mod : m_OrganizerCore.modList()->allMods()) { + ModInfo::Ptr modInfo = ModInfo::getByName(mod); + if (modInfo->updateAvailable()) { + updatesAvailable = true; break; } } + + if (updatesAvailable || checkingModsForUpdate) { + m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); + for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { + if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + break; + } + } + } } void MainWindow::changeVersioningScheme() { diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 0afd92e6..28e9b8f2 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -166,6 +166,14 @@ std::vector ModInfo::getByModID(QString game, int modID) } +ModInfo::Ptr ModInfo::getByName(const QString &name) +{ + QMutexLocker locker(&s_Mutex); + + return s_Collection[ModInfo::getIndex(name)]; +} + + bool ModInfo::removeMod(unsigned int index) { QMutexLocker locker(&s_Mutex); @@ -285,8 +293,10 @@ ModInfo::ModInfo(PluginContainer *pluginContainer) } -void ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver) +bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver) { + bool updatesAvailable = true; + QDateTime earliest = QDateTime::currentDateTimeUtc(); QDateTime latest; std::set games; @@ -308,8 +318,10 @@ void ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei } } - if (organizedGames.empty()) + if (organizedGames.empty()) { qWarning("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + updatesAvailable = false; + } for (auto game : organizedGames) { NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); @@ -327,6 +339,8 @@ void ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei for (auto gameName : games) NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::DAY, receiver, QVariant(false), QString()); } + + return updatesAvailable; } std::set> ModInfo::filteredMods(QString gameName, QVariantList updateData, bool addOldMods, bool markUpdated) diff --git a/src/modinfo.h b/src/modinfo.h index 81f59b61..f1d816fe 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -167,6 +167,15 @@ public: **/ static std::vector getByModID(QString game, int modID); + /** + * @brief retrieve a ModInfo object based on its name + * + * @param name the name to look up + * @return a reference counting pointer to the mod info + * @note since the pointer is reference counter, the pointer remains valid even if the collection is refreshed in a different thread + **/ + static ModInfo::Ptr getByName(const QString &name); + /** * @brief remove a mod by index * @@ -199,8 +208,9 @@ public: /** * @brief query nexus information for every mod and update the "newest version" information + * @return true if any mods are checked for update **/ - static void checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); + static bool checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); static std::set> filteredMods(QString gameName, QVariantList updateData, bool addOldMods = false, bool markUpdated = false); -- cgit v1.3.1