diff options
| -rw-r--r-- | src/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | src/installationmanager.cpp | 5 | ||||
| -rw-r--r-- | src/installationmanager.h | 5 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 21 | ||||
| -rw-r--r-- | src/mainwindow.h | 1 | ||||
| -rw-r--r-- | src/modinfo.cpp | 787 | ||||
| -rw-r--r-- | src/modinfo.h | 561 | ||||
| -rw-r--r-- | src/modinfobackup.cpp | 20 | ||||
| -rw-r--r-- | src/modinfobackup.h | 40 | ||||
| -rw-r--r-- | src/modinfoforeign.cpp | 50 | ||||
| -rw-r--r-- | src/modinfoforeign.h | 67 | ||||
| -rw-r--r-- | src/modinfooverwrite.cpp | 53 | ||||
| -rw-r--r-- | src/modinfooverwrite.h | 59 | ||||
| -rw-r--r-- | src/modinforegular.cpp | 571 | ||||
| -rw-r--r-- | src/modinforegular.h | 349 | ||||
| -rw-r--r-- | src/modinfowithconflictinfo.cpp | 130 | ||||
| -rw-r--r-- | src/modinfowithconflictinfo.h | 64 | ||||
| -rw-r--r-- | src/organizer.pro | 14 |
18 files changed, 1479 insertions, 1328 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22b725de..e8acd89d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,11 @@ SET(organizer_SRCS modlist.cpp modinfodialog.cpp modinfo.cpp + modinfobackup.cpp + modinfoforeign.cpp + modinfooverwrite.cpp + modinforegular.cpp + modinfowithconflictinfo.cpp messagedialog.cpp mainwindow.cpp main.cpp @@ -125,6 +130,11 @@ SET(organizer_HDRS modlist.h modinfodialog.h modinfo.h + modinfobackup.h + modinfoforeign.h + modinfooverwrite.h + modinforegular.h + modinfowithconflictinfo.h messagedialog.h mainwindow.h loghighlighter.h diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index a6af1a7f..3b1703d0 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -100,6 +100,10 @@ void InstallationManager::setParentWidget(QWidget *widget) }
}
+void InstallationManager::setURL(QString const &url)
+{
+ m_URL = url;
+}
void InstallationManager::queryPassword(LPSTR password)
{
@@ -581,6 +585,7 @@ bool InstallationManager::doInstall(GuessedValue<QString> &modName, int modID, }
settingsFile.setValue("installationFile", m_CurrentFile);
settingsFile.setValue("repository", repository);
+ settingsFile.setValue("url", m_URL);
if (!merge) {
// this does not clear the list we have in memory but the mod is going to have to be re-read anyway
diff --git a/src/installationmanager.h b/src/installationmanager.h index 111b41f5..e5e5346f 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -59,6 +59,8 @@ public: void setParentWidget(QWidget *widget);
+ void setURL(const QString &url);
+
/**
* @brief update the directory where mods are to be installed
* @param modsDirectory the mod directory
@@ -168,8 +170,6 @@ private: bool isSimpleArchiveTopLayer(const MOBase::DirectoryTree::Node *node, bool bainStyle);
MOBase::DirectoryTree::Node *getSimpleArchiveBase(MOBase::DirectoryTree *dataTree);
- //bool testOverwrite(const QString &modsDirectory, MOBase::GuessedValue<QString> &modName, bool *merge = nullptr);
-
bool doInstall(MOBase::GuessedValue<QString> &modName,
int modID, const QString &version, const QString &newestVersion, int categoryID, const QString &repository);
@@ -212,6 +212,7 @@ private: std::set<QString> m_TempFilesToDelete;
+ QString m_URL;
};
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 26ff4b98..1dca599d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2525,12 +2525,22 @@ void MainWindow::visitOnNexus_clicked() {
int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt();
if (modID > 0) {
- nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID));
+ linkClicked(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID));
} else {
MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this);
}
}
+void MainWindow::visitWebPage_clicked()
+{
+ ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
+ if (info->getURL() != "") {
+ linkClicked(info->getURL());
+ } else {
+ MessageDialog::showMessage(tr("Web page for this mod is unknown"), this);
+ }
+}
+
void MainWindow::openExplorer_clicked()
{
ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
@@ -3106,7 +3116,14 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu->addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked()));
}
- menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
+ if (info->getNexusID() > 0) {
+ menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
+ }
+
+ if (info->getURL() != "") {
+ menu->addAction(tr("Visit web page"), this, SLOT(visitWebPage_clicked()));
+ }
+
menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked()));
}
diff --git a/src/mainwindow.h b/src/mainwindow.h index 55ae40b9..3c7f2258 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -361,6 +361,7 @@ private slots: void unendorse_clicked();
void ignoreMissingData_clicked();
void visitOnNexus_clicked();
+ void visitWebPage_clicked();
void openExplorer_clicked();
void information_clicked();
// savegame context menu
diff --git a/src/modinfo.cpp b/src/modinfo.cpp index e0d888e6..fe5098e8 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -19,29 +19,23 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "modinfo.h" -#include "utility.h" +#include "modinfobackup.h" +#include "modinforegular.h" +#include "modinfoforeign.h" +#include "modinfooverwrite.h" + #include "installationtester.h" #include "categories.h" -#include "report.h" #include "modinfodialog.h" #include "overwriteinfodialog.h" #include "json.h" -#include "messagedialog.h" #include "filenamestring.h" - -#include <gameinfo.h> -#include <iplugingame.h> -#include <versioninfo.h> -#include <appconfig.h> -#include <scriptextender.h> +#include "versioninfo.h" #include <QApplication> - #include <QDirIterator> #include <QMutexLocker> #include <QSettings> -#include <sstream> - using namespace MOBase; using namespace MOShared; @@ -386,772 +380,3 @@ void ModInfo::testValid() dirIter.next(); } } - - -ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure) - : m_DirectoryStructure(directoryStructure) {} - -void ModInfoWithConflictInfo::clearCaches() -{ - m_LastConflictCheck = QTime(); -} - -std::vector<ModInfo::EFlag> ModInfoWithConflictInfo::getFlags() const -{ - std::vector<ModInfo::EFlag> result; - switch (isConflicted()) { - case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_CONFLICT_MIXED); - } break; - case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); - } break; - case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); - } break; - case CONFLICT_REDUNDANT: { - result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); - } break; - default: { /* NOP */ } - } - return result; -} - - -void ModInfoWithConflictInfo::doConflictCheck() const -{ - m_OverwriteList.clear(); - m_OverwrittenList.clear(); - - bool providesAnything = false; - - int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); - } - - std::wstring name = ToWString(this->name()); - - m_CurrentConflictState = CONFLICT_NONE; - - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector<FileEntry::Ptr> files = origin.getFiles(); - // for all files in this origin - for (FileEntry::Ptr file : files) { - const std::vector<int> &alternatives = file->getAlternatives(); - if ((alternatives.size() == 0) || (alternatives[0] == dataID)) { - // 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())); - m_OverwrittenList.insert(altIndex); - } else { - providesAnything = true; - } - - // for all non-providing alternative origins - for (int altId : alternatives) { - if ((altId != dataID) && (altId != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altId); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - if (origin.getPriority() > altOrigin.getPriority()) { - m_OverwriteList.insert(altIndex); - } else { - m_OverwrittenList.insert(altIndex); - } - } - } - } - } - m_LastConflictCheck = QTime::currentTime(); - - if (files.size() != 0) { - if (!providesAnything) - m_CurrentConflictState = CONFLICT_REDUNDANT; - else if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) - m_CurrentConflictState = CONFLICT_MIXED; - else if (!m_OverwriteList.empty()) - m_CurrentConflictState = CONFLICT_OVERWRITE; - else if (!m_OverwrittenList.empty()) - m_CurrentConflictState = CONFLICT_OVERWRITTEN; - } - } -} - -ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const -{ - // this is costy so cache the result - QTime now = QTime::currentTime(); - if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { - doConflictCheck(); - } - - return m_CurrentConflictState; -} - - -bool ModInfoWithConflictInfo::isRedundant() const -{ - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector<FileEntry::Ptr> files = origin.getFiles(); - bool ignore = false; - for (auto iter = files.begin(); iter != files.end(); ++iter) { - if ((*iter)->getOrigin(ignore) == origin.getID()) { - return false; - } - } - return true; - } else { - return false; - } -} - - - -ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_Name(path.dirName()) - , m_Path(path.absolutePath()) - , m_Repository() - , m_MetaInfoChanged(false) - , m_EndorsedState(ENDORSED_UNKNOWN) - , m_NexusBridge() -{ - testValid(); - m_CreationTime = QFileInfo(path.absolutePath()).created(); - // read out the meta-file for information - readMeta(); - - connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)) - , this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)) - , this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)) - , this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); -} - - -ModInfoRegular::~ModInfoRegular() -{ - try { - saveMeta(); - } catch (const std::exception &e) { - qCritical("failed to save meta information for \"%s\": %s", - m_Name.toUtf8().constData(), e.what()); - } -} - -bool ModInfoRegular::isEmpty() const -{ - QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; - return false; -} - - -void ModInfoRegular::readMeta() -{ - QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); - m_Notes = metaFile.value("notes", "").toString(); - m_NexusID = metaFile.value("modid", -1).toInt(); - m_Version.parse(metaFile.value("version", "").toString()); - m_NewestVersion = metaFile.value("newestVersion", "").toString(); - m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); - m_InstallationFile = metaFile.value("installationFile", "").toString(); - m_NexusDescription = metaFile.value("nexusDescription", "").toString(); - m_Repository = metaFile.value("repository", "Nexus").toString(); - m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); - if (metaFile.contains("endorsed")) { - if (metaFile.value("endorsed").canConvert<int>()) { - switch (metaFile.value("endorsed").toInt()) { - case ENDORSED_FALSE: m_EndorsedState = ENDORSED_FALSE; break; - case ENDORSED_TRUE: m_EndorsedState = ENDORSED_TRUE; break; - case ENDORSED_NEVER: m_EndorsedState = ENDORSED_NEVER; break; - default: m_EndorsedState = ENDORSED_UNKNOWN; break; - } - } else { - m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - } - } - - QString categoriesString = metaFile.value("category", "").toString(); - - QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); - for (QStringList::iterator iter = categories.begin(); iter != categories.end(); ++iter) { - bool ok = false; - int categoryID = iter->toInt(&ok); - if (categoryID < 0) { - // ignore invalid id - continue; - } - if (ok && (categoryID != 0) && (CategoryFactory::instance().categoryExists(categoryID))) { - m_Categories.insert(categoryID); - if (iter == categories.begin()) { - m_PrimaryCategory = categoryID; - } - } - } - - int numFiles = metaFile.beginReadArray("installedFiles"); - for (int i = 0; i < numFiles; ++i) { - metaFile.setArrayIndex(i); - m_InstalledFileIDs.insert(std::make_pair(metaFile.value("modid").toInt(), metaFile.value("fileid").toInt())); - } - metaFile.endArray(); - - m_MetaInfoChanged = false; -} - -void ModInfoRegular::saveMeta() -{ - // only write meta data if the mod directory exists - if (m_MetaInfoChanged && QFile::exists(absolutePath())) { - QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); - if (metaFile.status() == QSettings::NoError) { - std::set<int> temp = m_Categories; - temp.erase(m_PrimaryCategory); - metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); - metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); - metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); - metaFile.setValue("version", m_Version.canonicalString()); - metaFile.setValue("installationFile", m_InstallationFile); - metaFile.setValue("repository", m_Repository); - metaFile.setValue("modid", m_NexusID); - metaFile.setValue("notes", m_Notes); - metaFile.setValue("nexusDescription", m_NexusDescription); - metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); - if (m_EndorsedState != ENDORSED_UNKNOWN) { - metaFile.setValue("endorsed", m_EndorsedState); - } - - metaFile.beginWriteArray("installedFiles"); - int idx = 0; - for (auto iter = m_InstalledFileIDs.begin(); iter != m_InstalledFileIDs.end(); ++iter) { - metaFile.setArrayIndex(idx++); - metaFile.setValue("modid", iter->first); - metaFile.setValue("fileid", iter->second); - } - metaFile.endArray(); - - metaFile.sync(); // sync needs to be called to ensure the file is created - - if (metaFile.status() == QSettings::NoError) { - m_MetaInfoChanged = false; - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } -} - - -bool ModInfoRegular::updateAvailable() const -{ - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); -} - - -bool ModInfoRegular::downgradeAvailable() const -{ - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); -} - - -void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) -{ - QVariantMap result = resultData.toMap(); - setNewestVersion(VersionInfo(result["version"].toString())); - setNexusDescription(result["description"].toString()); - - if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { - setEndorsedState(result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE); - } - m_LastNexusQuery = QDateTime::currentDateTime(); - //m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); -} - - -void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) -{ - m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); -} - - -void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, const QString &errorMessage) -{ - QString fullMessage = errorMessage; - if (userData.canConvert<int>() && (userData.toInt() == 1)) { - fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; - } - if (QApplication::activeWindow() != nullptr) { - MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); - } - emit modDetailsUpdated(false); -} - - -bool ModInfoRegular::updateNXMInfo() -{ - if (m_NexusID > 0) { - m_NexusBridge.requestDescription(m_NexusID, QVariant()); - return true; - } - return false; -} - - -void ModInfoRegular::setCategory(int categoryID, bool active) -{ - m_MetaInfoChanged = true; - - if (active) { - m_Categories.insert(categoryID); - if (m_PrimaryCategory == -1) { - m_PrimaryCategory = categoryID; - } - } else { - std::set<int>::iterator iter = m_Categories.find(categoryID); - if (iter != m_Categories.end()) { - m_Categories.erase(iter); - } - if (categoryID == m_PrimaryCategory) { - if (m_Categories.size() == 0) { - m_PrimaryCategory = -1; - } else { - m_PrimaryCategory = *(m_Categories.begin()); - } - } - } -} - - -bool ModInfoRegular::setName(const QString &name) -{ - if (name.contains('/') || name.contains('\\')) { - return false; - } - - QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); - QDir modDir(m_Path.mid(0, m_Path.length() - m_Name.length())); - - if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { - QString tempName = name; - tempName.append("_temp"); - while (modDir.exists(tempName)) { - tempName.append("_"); - } - if (!modDir.rename(m_Name, tempName)) { - return false; - } - if (!modDir.rename(tempName, name)) { - qCritical("rename to final name failed after successful rename to intermediate name"); - modDir.rename(tempName, m_Name); - return false; - } - } else { - if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { - qCritical("failed to rename mod %s (errorcode %d)", - qPrintable(name), ::GetLastError()); - return false; - } - } - - std::map<QString, unsigned int>::iterator nameIter = s_ModsByName.find(m_Name); - if (nameIter != s_ModsByName.end()) { - unsigned int index = nameIter->second; - s_ModsByName.erase(nameIter); - - m_Name = name; - m_Path = newPath; - - s_ModsByName[m_Name] = index; - - std::sort(s_Collection.begin(), s_Collection.end(), ByName); - updateIndices(); - } else { // otherwise mod isn't registered yet? - m_Name = name; - m_Path = newPath; - } - - return true; -} - -void ModInfoRegular::setNotes(const QString ¬es) -{ - m_Notes = notes; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setNexusID(int modID) -{ - m_NexusID = modID; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setVersion(const VersionInfo &version) -{ - m_Version = version; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setNewestVersion(const VersionInfo &version) -{ - if (version != m_NewestVersion) { - m_NewestVersion = version; - m_MetaInfoChanged = true; - } -} - -void ModInfoRegular::setNexusDescription(const QString &description) -{ - if (qHash(description) != qHash(m_NexusDescription)) { - m_NexusDescription = description; - m_MetaInfoChanged = true; - } -} - -void ModInfoRegular::setEndorsedState(EEndorsedState endorsedState) -{ - if (endorsedState != m_EndorsedState) { - m_EndorsedState = endorsedState; - m_MetaInfoChanged = true; - } -} - -void ModInfoRegular::setInstallationFile(const QString &fileName) -{ - m_InstallationFile = fileName; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::addNexusCategory(int categoryID) -{ - m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); -} - -void ModInfoRegular::setIsEndorsed(bool endorsed) -{ - if (m_EndorsedState != ENDORSED_NEVER) { - m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - } -} - - -void ModInfoRegular::setNeverEndorse() -{ - m_EndorsedState = ENDORSED_NEVER; - m_MetaInfoChanged = true; -} - - -bool ModInfoRegular::remove() -{ - m_MetaInfoChanged = false; - return shellDelete(QStringList(absolutePath()), true); -} - -void ModInfoRegular::endorse(bool doEndorse) -{ - if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { - m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); - } -} - - -QString ModInfoRegular::absolutePath() const -{ - return m_Path; -} - -void ModInfoRegular::ignoreUpdate(bool ignore) -{ - if (ignore) { - m_IgnoredVersion = m_NewestVersion; - } else { - m_IgnoredVersion.clear(); - } - m_MetaInfoChanged = true; -} - - -std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const -{ - std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags(); - if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE)) { - result.push_back(ModInfo::FLAG_NOTENDORSED); - } - if (!isValid()) { - result.push_back(ModInfo::FLAG_INVALID); - } - if (m_Notes.length() != 0) { - result.push_back(ModInfo::FLAG_NOTES); - } - return result; -} - - -std::vector<ModInfo::EContent> ModInfoRegular::getContents() const -{ - QTime now = QTime::currentTime(); - if (m_LastContentCheck.isNull() || (m_LastContentCheck.secsTo(now) > 60)) { - m_CachedContent.clear(); - QDir dir(absolutePath()); - if (dir.entryList(QStringList() << "*.esp" << "*.esm").size() > 0) { - m_CachedContent.push_back(CONTENT_PLUGIN); - } - if (dir.entryList(QStringList() << "*.bsa").size() > 0) { - m_CachedContent.push_back(CONTENT_BSA); - } - - ScriptExtender *extender = qApp->property("managed_game").value<IPluginGame*>()->feature<ScriptExtender>(); - - if (extender != nullptr) { - QString sePluginPath = extender->name() + "/plugins"; - if (dir.exists(sePluginPath)) m_CachedContent.push_back(CONTENT_SKSE); - } - if (dir.exists("textures")) m_CachedContent.push_back(CONTENT_TEXTURE); - if (dir.exists("meshes")) m_CachedContent.push_back(CONTENT_MESH); - if (dir.exists("interface") - || dir.exists("menus")) m_CachedContent.push_back(CONTENT_INTERFACE); - if (dir.exists("music")) m_CachedContent.push_back(CONTENT_MUSIC); - if (dir.exists("sound")) m_CachedContent.push_back(CONTENT_SOUND); - if (dir.exists("scripts")) m_CachedContent.push_back(CONTENT_SCRIPT); - if (dir.exists("strings")) m_CachedContent.push_back(CONTENT_STRING); - if (dir.exists("SkyProc Patchers")) m_CachedContent.push_back(CONTENT_SKYPROC); - - m_LastContentCheck = QTime::currentTime(); - } - - return m_CachedContent; - -} - - -int ModInfoRegular::getHighlight() const -{ - return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; -} - - -QString ModInfoRegular::getDescription() const -{ - if (!isValid()) { - return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name()); - } else { - const std::set<int> &categories = getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories: <br>")); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (std::set<int>::const_iterator catIter = categories.begin(); - catIter != categories.end(); ++catIter) { - if (catIter != categories.begin()) { - categoryString << " , "; - } - categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>"; - } - - return ToQString(categoryString.str()); - } -} - -QString ModInfoRegular::notes() const -{ - return m_Notes; -} - -QDateTime ModInfoRegular::creationTime() const -{ - return m_CreationTime; -} - -QString ModInfoRegular::getNexusDescription() const -{ - return m_NexusDescription; -} - -QString ModInfoRegular::repository() const -{ - return m_Repository; -} - -ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const -{ - return m_EndorsedState; -} - -QDateTime ModInfoRegular::getLastNexusQuery() const -{ - return m_LastNexusQuery; -} - - -QStringList ModInfoRegular::archives() const -{ - QStringList result; - QDir dir(this->absolutePath()); - for (const QString &archive : dir.entryList(QStringList("*.bsa"))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; -} - -void ModInfoRegular::addInstalledFile(int modId, int fileId) -{ - m_InstalledFileIDs.insert(std::make_pair(modId, fileId)); - m_MetaInfoChanged = true; -} - -std::vector<QString> ModInfoRegular::getIniTweaks() const -{ - QString metaFileName = absolutePath().append("/meta.ini"); - QSettings metaFile(metaFileName, QSettings::IniFormat); - - std::vector<QString> result; - - int numTweaks = metaFile.beginReadArray("INI Tweaks"); - - if (numTweaks != 0) { - qDebug("%d active ini tweaks in %s", - numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); - } - - for (int i = 0; i < numTweaks; ++i) { - metaFile.setArrayIndex(i); - QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); - result.push_back(filename); - } - metaFile.endArray(); - return result; -} - -std::vector<ModInfo::EFlag> ModInfoBackup::getFlags() const -{ - std::vector<ModInfo::EFlag> result = ModInfoRegular::getFlags(); - result.insert(result.begin(), ModInfo::FLAG_BACKUP); - return result; -} - - -QString ModInfoBackup::getDescription() const -{ - return tr("This is the backup of a mod"); -} - - -ModInfoBackup::ModInfoBackup(const QDir &path, DirectoryEntry **directoryStructure) - : ModInfoRegular(path, directoryStructure) -{ -} - - -ModInfoOverwrite::ModInfoOverwrite() -{ - testValid(); -} - - -bool ModInfoOverwrite::isEmpty() const -{ - QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; - return false; -} - -QString ModInfoOverwrite::absolutePath() const -{ - return QDir::fromNativeSeparators(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::overwritePath())); -} - -std::vector<ModInfo::EFlag> ModInfoOverwrite::getFlags() const -{ - std::vector<ModInfo::EFlag> result; - result.push_back(FLAG_OVERWRITE); - return result; -} - -int ModInfoOverwrite::getHighlight() const -{ - return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; -} - -QString ModInfoOverwrite::getDescription() const -{ - return tr("This pseudo mod contains files from the virtual data tree that got " - "modified (i.e. by the construction kit)"); -} - -QStringList ModInfoOverwrite::archives() const -{ - QStringList result; - QDir dir(this->absolutePath()); - for (const QString &archive : dir.entryList(QStringList("*.bsa"))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; -} - -QString ModInfoForeign::name() const -{ - return m_Name; -} - -QDateTime ModInfoForeign::creationTime() const -{ - return m_CreationTime; -} - -QString ModInfoForeign::absolutePath() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"; -} - -std::vector<ModInfo::EFlag> ModInfoForeign::getFlags() const -{ - std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags(); - result.push_back(FLAG_FOREIGN); - - return result; -} - -int ModInfoForeign::getHighlight() const -{ - return 0; -} - -QString ModInfoForeign::getDescription() const -{ - return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); -} - -ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives, - DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_ReferenceFile(referenceFile) - , m_Archives(archives) -{ - m_CreationTime = QFileInfo(referenceFile).created(); - m_Name = "Unmanaged: " + QFileInfo(m_ReferenceFile).baseName(); -} diff --git a/src/modinfo.h b/src/modinfo.h index da97b09b..7d305c11 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -20,22 +20,23 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #ifndef MODINFO_H
#define MODINFO_H
-#include "nexusinterface.h"
-#include <versioninfo.h>
-#include <imodinterface.h>
+#include "imodinterface.h"
+#include "versioninfo.h"
-#include <QString>
+class QDateTime;
+class QDir;
#include <QMutex>
-#include <QIcon>
-#include <QDir>
#include <QSharedPointer>
-#include <QDateTime>
+#include <QString>
+#include <QStringList>
+
+#include <boost/function.hpp>
+
#include <map>
#include <set>
#include <vector>
-#include <directoryentry.h>
-using MOBase::ModRepositoryFileInfo;
+namespace MOShared { class DirectoryEntry; }
/**
* @brief Represents meta information about a single mod.
@@ -556,6 +557,16 @@ public: */
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:
/**
@@ -596,536 +607,4 @@ private: };
-class ModInfoWithConflictInfo : public ModInfo
-{
-
-public:
-
- ModInfoWithConflictInfo(MOShared::DirectoryEntry **directoryStructure);
-
- std::vector<ModInfo::EFlag> getFlags() const;
-
- /**
- * @brief clear all caches held for this mod
- */
- virtual void clearCaches();
-
- virtual std::set<unsigned int> getModOverwrite() { return m_OverwriteList; }
-
- virtual std::set<unsigned int> getModOverwritten() { return m_OverwrittenList; }
-
- virtual void doConflictCheck() const;
-
-private:
-
- enum EConflictType {
- CONFLICT_NONE,
- CONFLICT_OVERWRITE,
- CONFLICT_OVERWRITTEN,
- CONFLICT_MIXED,
- CONFLICT_REDUNDANT
- };
-
-private:
-
- /**
- * @return true if there is a conflict for files in this mod
- */
- EConflictType isConflicted() const;
-
- /**
- * @return true if this mod is completely replaced by others
- */
- bool isRedundant() const;
-
-private:
-
- MOShared::DirectoryEntry **m_DirectoryStructure;
-
- mutable EConflictType m_CurrentConflictState;
- mutable QTime m_LastConflictCheck;
-
- mutable std::set<unsigned int> m_OverwriteList; // indices of mods overritten by this mod
- mutable std::set<unsigned int> m_OverwrittenList; // indices of mods overwriting this mod
-
-};
-
-
-/**
- * @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 ModInfoRegular : public ModInfoWithConflictInfo
-{
-
- Q_OBJECT
-
- friend class ModInfo;
-
-public:
-
- ~ModInfoRegular();
-
- virtual bool isRegular() const { return true; }
-
- virtual bool isEmpty() const;
-
- /**
- * @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
- **/
- bool updateAvailable() const;
-
- /**
- * @return true if the current update is being ignored
- */
- virtual bool updateIgnored() const { return m_IgnoredVersion == m_NewestVersion; }
-
- /**
- * @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
- **/
- bool downgradeAvailable() const;
-
- /**
- * @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.
- *
- * @return returns true if information for this mod will be updated, false if there is no nexus mod id to use
- **/
- bool updateNXMInfo();
-
- /**
- * @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
- **/
- void setCategory(int categoryID, bool active);
-
- /**
- * @brief set the name of this mod
- *
- * set the name of this mod. This will also update the name of the
- * directory that contains this mod
- *
- * @param name new name of the mod
- * @return true on success, false if the new name can't be used (i.e. because the new
- * directory name wouldn't be valid)
- **/
- bool setName(const QString &name);
-
- /**
- * @brief change the notes (manually set information) for this mod
- * @param notes new notes
- */
- void setNotes(const QString ¬es);
-
- /**
- * @brief set/change the nexus mod id of this mod
- *
- * @param modID the nexus mod id
- **/
- void setNexusID(int modID);
-
- /**
- * @brief set the version of this mod
- *
- * this can be used to overwrite the version of a mod without actually
- * updating the mod
- *
- * @param version the new version to use
- **/
- void setVersion(const MOBase::VersionInfo &version);
-
- /**
- * @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
- **/
- void setNewestVersion(const MOBase::VersionInfo &version);
-
- /**
- * @brief changes/updates the nexus description text
- * @param description the current description text
- */
- virtual void setNexusDescription(const QString &description);
-
- virtual void setInstallationFile(const QString &fileName);
-
- /**
- * @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);
-
- /**
- * @brief sets the new primary category of the mod
- * @param categoryID the category to set
- */
- virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; m_MetaInfoChanged = true; }
-
- /**
- * @brief sets the download repository
- * @param repository
- */
- virtual void setRepository(const QString &repository) { m_Repository = repository; }
-
- /**
- * 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);
-
- /**
- * 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();
-
- /**
- * @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
- **/
- bool remove();
-
- /**
- * @brief endorse or un-endorse the mod
- * @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);
-
- /**
- * @brief getter for the mod name
- *
- * @return the mod name
- **/
- QString name() const { return m_Name; }
-
- /**
- * @brief getter for the mod path
- *
- * @return the (absolute) path to the mod
- **/
- QString absolutePath() const;
-
- /**
- * @brief getter for the newest version number of this mod
- *
- * @return newest version of the mod
- **/
- MOBase::VersionInfo getNewestVersion() const { return m_NewestVersion; }
-
- /**
- * @brief ignore the newest version for updates
- */
- void ignoreUpdate(bool ignore);
-
- /**
- * @brief getter for the installation file
- *
- * @return file used to install this mod from
- */
- virtual QString getInstallationFile() const { return m_InstallationFile; }
- /**
- * @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
- **/
- int getNexusID() const { return m_NexusID; }
-
- /**
- * @return the fixed priority of mods of this type or INT_MIN if the priority of mods
- * needs to be user-modifiable
- */
- virtual int getFixedPriority() const { return INT_MIN; }
-
- /**
- * @return true if the mod can be updated
- */
- virtual bool canBeUpdated() const { return m_NexusID > 0; }
-
- /**
- * @return true if the mod can be enabled/disabled
- */
- virtual bool canBeEnabled() const { return true; }
-
- /**
- * @return a list of flags for this mod
- */
- virtual std::vector<EFlag> getFlags() const;
-
- virtual std::vector<EContent> getContents() const;
-
- /**
- * @return an indicator if and how this mod should be highlighted by the UI
- */
- virtual int getHighlight() const;
-
- /**
- * @return list of names of ini tweaks
- **/
- std::vector<QString> getIniTweaks() const;
-
- /**
- * @return a description about the mod, to be displayed in the ui
- */
- virtual QString getDescription() const;
-
- /**
- * @return manually set notes for this mod
- */
- virtual QString notes() const;
-
- /**
- * @return time this mod was created (file time of the directory)
- */
- virtual QDateTime creationTime() const;
-
- /**
- * @return nexus description of the mod (html)
- */
- QString getNexusDescription() const;
-
- /**
- * @return repository from which the file was downloaded
- */
- virtual QString repository() const;
-
- /**
- * @return true if the file has been endorsed on nexus
- */
- virtual EEndorsedState endorsedState() const;
-
- /**
- * @return last time nexus was queried for infos on this mod
- */
- virtual QDateTime getLastNexusQuery() const;
-
- virtual QStringList archives() const;
-
- virtual void addInstalledFile(int modId, int fileId);
-
- /**
- * @brief stores meta information back to disk
- */
- virtual void saveMeta();
-
- void readMeta();
-
-private:
-
- void setEndorsedState(EEndorsedState endorsedState);
-
-private slots:
-
- void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData);
- void nxmEndorsementToggled(int, QVariant userData, QVariant resultData);
- void nxmRequestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage);
-
-protected:
-
- ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure);
-
-private:
-
- QString m_Name;
- QString m_Path;
- QString m_InstallationFile;
- QString m_Notes;
- QString m_NexusDescription;
- QString m_Repository;
-
- QDateTime m_CreationTime;
- QDateTime m_LastNexusQuery;
-
- int m_NexusID;
- std::set<std::pair<int, int>> m_InstalledFileIDs;
-
- bool m_MetaInfoChanged;
- MOBase::VersionInfo m_NewestVersion;
- MOBase::VersionInfo m_IgnoredVersion;
-
- EEndorsedState m_EndorsedState;
-
- NexusBridge m_NexusBridge;
-
- mutable std::vector<ModInfo::EContent> m_CachedContent;
- mutable QTime m_LastContentCheck;
-
-};
-
-
-class ModInfoBackup : public ModInfoRegular
-{
-
- friend class ModInfo;
-
-public:
-
- virtual bool updateAvailable() const { return false; }
- virtual bool updateIgnored() const { return false; }
- virtual bool downgradeAvailable() const { return false; }
- virtual bool updateNXMInfo() { return false; }
- virtual void setNexusID(int) {}
- virtual void endorse(bool) {}
- virtual int getFixedPriority() const { return -1; }
- virtual void ignoreUpdate(bool) {}
- virtual bool canBeUpdated() const { return false; }
- virtual bool canBeEnabled() const { return false; }
- virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); }
- virtual std::vector<EFlag> getFlags() const;
- virtual QString getDescription() const;
- virtual QDateTime getLastNexusQuery() const { return QDateTime(); }
- virtual void getNexusFiles(QList<MOBase::ModRepositoryFileInfo*>::const_iterator&,
- QList<MOBase::ModRepositoryFileInfo*>::const_iterator&) {}
- virtual QString getNexusDescription() const { return QString(); }
-
- virtual void addInstalledFile(int, int) {}
-
-private:
-
- ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure);
-
-};
-
-
-class ModInfoOverwrite : public ModInfo
-{
-
- Q_OBJECT
-
- friend class ModInfo;
-
-public:
-
- virtual bool updateAvailable() const { return false; }
- virtual bool updateIgnored() const { return false; }
- virtual bool downgradeAvailable() const { return false; }
- virtual bool updateNXMInfo() { return false; }
- virtual void setCategory(int, bool) {}
- virtual bool setName(const QString&) { return false; }
- virtual void setNotes(const QString&) {}
- virtual void setNexusID(int) {}
- virtual void setNewestVersion(const MOBase::VersionInfo&) {}
- virtual void ignoreUpdate(bool) {}
- virtual void setNexusDescription(const QString&) {}
- virtual void setInstallationFile(const QString&) {}
- virtual void addNexusCategory(int) {}
- virtual void setIsEndorsed(bool) {}
- virtual void setNeverEndorse() {}
- virtual bool remove() { return false; }
- virtual void endorse(bool) {}
- virtual bool alwaysEnabled() const { return true; }
- virtual bool isEmpty() const;
- virtual QString name() const { return "Overwrite"; }
- virtual QString notes() const { return ""; }
- virtual QDateTime creationTime() const { return QDateTime(); }
- virtual QString absolutePath() const;
- virtual MOBase::VersionInfo getNewestVersion() const { return ""; }
- virtual QString getInstallationFile() const { return ""; }
- virtual int getFixedPriority() const { return INT_MAX; }
- virtual int getNexusID() const { return -1; }
- virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); }
- virtual std::vector<ModInfo::EFlag> getFlags() const;
- virtual int getHighlight() const;
- virtual QString getDescription() const;
- virtual QDateTime getLastNexusQuery() const { return QDateTime(); }
- virtual QString getNexusDescription() const { return QString(); }
- virtual QStringList archives() const;
- virtual void addInstalledFile(int, int) {}
-
-private:
-
- ModInfoOverwrite();
-
-};
-
-
-class ModInfoForeign : public ModInfoWithConflictInfo
-{
-
- Q_OBJECT
-
- friend class ModInfo;
-
-public:
-
- virtual bool updateAvailable() const { return false; }
- virtual bool updateIgnored() const { return false; }
- virtual bool downgradeAvailable() const { return false; }
- virtual bool updateNXMInfo() { return false; }
- virtual void setCategory(int, bool) {}
- virtual bool setName(const QString&) { return false; }
- virtual void setNotes(const QString&) {}
- virtual void setNexusID(int) {}
- virtual void setNewestVersion(const MOBase::VersionInfo&) {}
- virtual void ignoreUpdate(bool) {}
- virtual void setNexusDescription(const QString&) {}
- virtual void setInstallationFile(const QString&) {}
- virtual void addNexusCategory(int) {}
- virtual void setIsEndorsed(bool) {}
- virtual void setNeverEndorse() {}
- virtual bool remove() { return false; }
- virtual void endorse(bool) {}
- virtual bool isEmpty() const { return false; }
- virtual QString name() const;
- virtual QString internalName() const { return name(); }
- virtual QString notes() const { return ""; }
- virtual QDateTime creationTime() const;
- virtual QString absolutePath() const;
- virtual MOBase::VersionInfo getNewestVersion() const { return ""; }
- virtual QString getInstallationFile() const { return ""; }
- virtual int getNexusID() const { return -1; }
- virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); }
- virtual std::vector<ModInfo::EFlag> getFlags() const;
- virtual int getHighlight() const;
- virtual QString getDescription() const;
- 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 stealFiles() const { return m_Archives + QStringList(m_ReferenceFile); }
- virtual bool alwaysEnabled() const { return true; }
- virtual void addInstalledFile(int, int) {}
-
-protected:
-
- ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure);
-
-private:
-
- QString m_Name;
- QString m_ReferenceFile;
- QStringList m_Archives;
- QDateTime m_CreationTime;
- int m_Priority;
-
-};
-
#endif // MODINFO_H
diff --git a/src/modinfobackup.cpp b/src/modinfobackup.cpp new file mode 100644 index 00000000..a57b84fe --- /dev/null +++ b/src/modinfobackup.cpp @@ -0,0 +1,20 @@ +#include "modinfobackup.h" + +std::vector<ModInfo::EFlag> ModInfoBackup::getFlags() const +{ + std::vector<ModInfo::EFlag> result = ModInfoRegular::getFlags(); + result.insert(result.begin(), ModInfo::FLAG_BACKUP); + return result; +} + + +QString ModInfoBackup::getDescription() const +{ + return tr("This is the backup of a mod"); +} + + +ModInfoBackup::ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure) + : ModInfoRegular(path, directoryStructure) +{ +} diff --git a/src/modinfobackup.h b/src/modinfobackup.h new file mode 100644 index 00000000..adbac783 --- /dev/null +++ b/src/modinfobackup.h @@ -0,0 +1,40 @@ +#ifndef MODINFOBACKUP_H +#define MODINFOBACKUP_H + +#include "modinforegular.h" + +class ModInfoBackup : public ModInfoRegular +{ + + friend class ModInfo; + +public: + + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setNexusID(int) {} + virtual void endorse(bool) {} + virtual int getFixedPriority() const { return -1; } + virtual void ignoreUpdate(bool) {} + virtual bool canBeUpdated() const { return false; } + virtual bool canBeEnabled() const { return false; } + virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); } + virtual std::vector<EFlag> getFlags() const; + virtual QString getDescription() const; + virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual void getNexusFiles(QList<MOBase::ModRepositoryFileInfo*>::const_iterator&, + QList<MOBase::ModRepositoryFileInfo*>::const_iterator&) {} + virtual QString getNexusDescription() const { return QString(); } + + virtual void addInstalledFile(int, int) {} + +private: + + ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure); + +}; + + +#endif // MODINFOBACKUP_H diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp new file mode 100644 index 00000000..c0e769c9 --- /dev/null +++ b/src/modinfoforeign.cpp @@ -0,0 +1,50 @@ +#include "modinfoforeign.h" + +#include "gameinfo.h" +#include "utility.h" + +using namespace MOBase; +using namespace MOShared; + +QString ModInfoForeign::name() const +{ + return m_Name; +} + +QDateTime ModInfoForeign::creationTime() const +{ + return m_CreationTime; +} + +QString ModInfoForeign::absolutePath() const +{ + return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"; +} + +std::vector<ModInfo::EFlag> ModInfoForeign::getFlags() const +{ + std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags(); + result.push_back(FLAG_FOREIGN); + + return result; +} + +int ModInfoForeign::getHighlight() const +{ + return 0; +} + +QString ModInfoForeign::getDescription() const +{ + return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); +} + +ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives, + DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(directoryStructure) + , m_ReferenceFile(referenceFile) + , m_Archives(archives) +{ + m_CreationTime = QFileInfo(referenceFile).created(); + m_Name = "Unmanaged: " + QFileInfo(m_ReferenceFile).baseName(); +} diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h new file mode 100644 index 00000000..b8ff2671 --- /dev/null +++ b/src/modinfoforeign.h @@ -0,0 +1,67 @@ +#ifndef MODINFOFOREIGN_H +#define MODINFOFOREIGN_H + +#include "modinfowithconflictinfo.h" + +class ModInfoForeign : public ModInfoWithConflictInfo +{ + + Q_OBJECT + + friend class ModInfo; + +public: + + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setCategory(int, bool) {} + virtual bool setName(const QString&) { return false; } + virtual void setNotes(const QString&) {} + virtual void setNexusID(int) {} + virtual void setNewestVersion(const MOBase::VersionInfo&) {} + virtual void ignoreUpdate(bool) {} + virtual void setNexusDescription(const QString&) {} + virtual void setInstallationFile(const QString&) {} + virtual void addNexusCategory(int) {} + virtual void setIsEndorsed(bool) {} + virtual void setNeverEndorse() {} + virtual bool remove() { return false; } + virtual void endorse(bool) {} + virtual bool isEmpty() const { return false; } + virtual QString name() const; + virtual QString internalName() const { return name(); } + virtual QString notes() const { return ""; } + virtual QDateTime creationTime() const; + virtual QString absolutePath() const; + virtual MOBase::VersionInfo getNewestVersion() const { return ""; } + virtual QString getInstallationFile() const { return ""; } + virtual int getNexusID() const { return -1; } + virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); } + virtual std::vector<ModInfo::EFlag> getFlags() const; + virtual int getHighlight() const; + virtual QString getDescription() const; + 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 stealFiles() const { return m_Archives + QStringList(m_ReferenceFile); } + virtual bool alwaysEnabled() const { return true; } + virtual void addInstalledFile(int, int) {} + +protected: + + ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure); + +private: + + QString m_Name; + QString m_ReferenceFile; + QStringList m_Archives; + QDateTime m_CreationTime; + int m_Priority; + +}; + +#endif // MODINFOFOREIGN_H diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp new file mode 100644 index 00000000..4380a255 --- /dev/null +++ b/src/modinfooverwrite.cpp @@ -0,0 +1,53 @@ +#include "modinfooverwrite.h" + +#include "appconfig.h" + +#include <QApplication> +#include <QDirIterator> + +ModInfoOverwrite::ModInfoOverwrite() +{ + testValid(); +} + +bool ModInfoOverwrite::isEmpty() const +{ + QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + +QString ModInfoOverwrite::absolutePath() const +{ + return QDir::fromNativeSeparators(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::overwritePath())); +} + +std::vector<ModInfo::EFlag> ModInfoOverwrite::getFlags() const +{ + std::vector<ModInfo::EFlag> result; + result.push_back(FLAG_OVERWRITE); + return result; +} + +int ModInfoOverwrite::getHighlight() const +{ + return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; +} + +QString ModInfoOverwrite::getDescription() const +{ + return tr("This pseudo mod contains files from the virtual data tree that got " + "modified (i.e. by the construction kit)"); +} + +QStringList ModInfoOverwrite::archives() const +{ + QStringList result; + QDir dir(this->absolutePath()); + for (const QString &archive : dir.entryList(QStringList("*.bsa"))) { + result.append(this->absolutePath() + "/" + archive); + } + return result; +} diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h new file mode 100644 index 00000000..9184a90c --- /dev/null +++ b/src/modinfooverwrite.h @@ -0,0 +1,59 @@ +#ifndef MODINFOOVERWRITE_H +#define MODINFOOVERWRITE_H + +#include "modinfo.h" + +#include <QDateTime> + +class ModInfoOverwrite : public ModInfo +{ + + Q_OBJECT + + friend class ModInfo; + +public: + + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setCategory(int, bool) {} + virtual bool setName(const QString&) { return false; } + virtual void setNotes(const QString&) {} + virtual void setNexusID(int) {} + virtual void setNewestVersion(const MOBase::VersionInfo&) {} + virtual void ignoreUpdate(bool) {} + virtual void setNexusDescription(const QString&) {} + virtual void setInstallationFile(const QString&) {} + virtual void addNexusCategory(int) {} + virtual void setIsEndorsed(bool) {} + virtual void setNeverEndorse() {} + virtual bool remove() { return false; } + virtual void endorse(bool) {} + virtual bool alwaysEnabled() const { return true; } + virtual bool isEmpty() const; + virtual QString name() const { return "Overwrite"; } + virtual QString notes() const { return ""; } + virtual QDateTime creationTime() const { return QDateTime(); } + virtual QString absolutePath() const; + virtual MOBase::VersionInfo getNewestVersion() const { return ""; } + virtual QString getInstallationFile() const { return ""; } + virtual int getFixedPriority() const { return INT_MAX; } + virtual int getNexusID() const { return -1; } + virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); } + virtual std::vector<ModInfo::EFlag> getFlags() const; + virtual int getHighlight() const; + virtual QString getDescription() const; + virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual QString getNexusDescription() const { return QString(); } + virtual QStringList archives() const; + virtual void addInstalledFile(int, int) {} + +private: + + ModInfoOverwrite(); + +}; + +#endif // MODINFOOVERWRITE_H diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp new file mode 100644 index 00000000..b1f73a37 --- /dev/null +++ b/src/modinforegular.cpp @@ -0,0 +1,571 @@ +#include "modinforegular.h" + +#include "categories.h" +#include "iplugingame.h" +#include "messagedialog.h" +#include "report.h" +#include "scriptextender.h" + +#include <QApplication> +#include <QDirIterator> +#include <QSettings> + +#include <sstream> + +using namespace MOBase; +using namespace MOShared; + +namespace { + //Arguably this should be a class static or we should be using FileString rather + //than QString for the names. Or both. + static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) + { + return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; + } +} + +ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(directoryStructure) + , m_Name(path.dirName()) + , m_Path(path.absolutePath()) + , m_Repository() + , m_MetaInfoChanged(false) + , m_EndorsedState(ENDORSED_UNKNOWN) + , m_NexusBridge() +{ + testValid(); + m_CreationTime = QFileInfo(path.absolutePath()).created(); + // read out the meta-file for information + readMeta(); + + connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)) + , this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)) + , this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)) + , this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); +} + + +ModInfoRegular::~ModInfoRegular() +{ + try { + saveMeta(); + } catch (const std::exception &e) { + qCritical("failed to save meta information for \"%s\": %s", + m_Name.toUtf8().constData(), e.what()); + } +} + +bool ModInfoRegular::isEmpty() const +{ + QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + + +void ModInfoRegular::readMeta() +{ + QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); + m_Notes = metaFile.value("notes", "").toString(); + m_NexusID = metaFile.value("modid", -1).toInt(); + m_Version.parse(metaFile.value("version", "").toString()); + m_NewestVersion = metaFile.value("newestVersion", "").toString(); + m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); + m_InstallationFile = metaFile.value("installationFile", "").toString(); + m_NexusDescription = metaFile.value("nexusDescription", "").toString(); + m_Repository = metaFile.value("repository", "Nexus").toString(); + m_URL = metaFile.value("url", "").toString(); + m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); + if (metaFile.contains("endorsed")) { + if (metaFile.value("endorsed").canConvert<int>()) { + switch (metaFile.value("endorsed").toInt()) { + case ENDORSED_FALSE: m_EndorsedState = ENDORSED_FALSE; break; + case ENDORSED_TRUE: m_EndorsedState = ENDORSED_TRUE; break; + case ENDORSED_NEVER: m_EndorsedState = ENDORSED_NEVER; break; + default: m_EndorsedState = ENDORSED_UNKNOWN; break; + } + } else { + m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + } + } + + QString categoriesString = metaFile.value("category", "").toString(); + + QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); + for (QStringList::iterator iter = categories.begin(); iter != categories.end(); ++iter) { + bool ok = false; + int categoryID = iter->toInt(&ok); + if (categoryID < 0) { + // ignore invalid id + continue; + } + if (ok && (categoryID != 0) && (CategoryFactory::instance().categoryExists(categoryID))) { + m_Categories.insert(categoryID); + if (iter == categories.begin()) { + m_PrimaryCategory = categoryID; + } + } + } + + int numFiles = metaFile.beginReadArray("installedFiles"); + for (int i = 0; i < numFiles; ++i) { + metaFile.setArrayIndex(i); + m_InstalledFileIDs.insert(std::make_pair(metaFile.value("modid").toInt(), metaFile.value("fileid").toInt())); + } + metaFile.endArray(); + + m_MetaInfoChanged = false; +} + +void ModInfoRegular::saveMeta() +{ + // only write meta data if the mod directory exists + if (m_MetaInfoChanged && QFile::exists(absolutePath())) { + QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); + if (metaFile.status() == QSettings::NoError) { + std::set<int> temp = m_Categories; + temp.erase(m_PrimaryCategory); + metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); + metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); + metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); + metaFile.setValue("version", m_Version.canonicalString()); + metaFile.setValue("installationFile", m_InstallationFile); + metaFile.setValue("repository", m_Repository); + metaFile.setValue("modid", m_NexusID); + metaFile.setValue("notes", m_Notes); + metaFile.setValue("nexusDescription", m_NexusDescription); + metaFile.setValue("url", m_URL); + metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); + if (m_EndorsedState != ENDORSED_UNKNOWN) { + metaFile.setValue("endorsed", m_EndorsedState); + } + + metaFile.beginWriteArray("installedFiles"); + int idx = 0; + for (auto iter = m_InstalledFileIDs.begin(); iter != m_InstalledFileIDs.end(); ++iter) { + metaFile.setArrayIndex(idx++); + metaFile.setValue("modid", iter->first); + metaFile.setValue("fileid", iter->second); + } + metaFile.endArray(); + + metaFile.sync(); // sync needs to be called to ensure the file is created + + if (metaFile.status() == QSettings::NoError) { + m_MetaInfoChanged = false; + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + } + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + } + } +} + + +bool ModInfoRegular::updateAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } + return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); +} + + +bool ModInfoRegular::downgradeAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } + return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); +} + + +void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) +{ + QVariantMap result = resultData.toMap(); + setNewestVersion(VersionInfo(result["version"].toString())); + setNexusDescription(result["description"].toString()); + + if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { + setEndorsedState(result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE); + } + m_LastNexusQuery = QDateTime::currentDateTime(); + //m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + + +void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) +{ + m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + + +void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, const QString &errorMessage) +{ + QString fullMessage = errorMessage; + if (userData.canConvert<int>() && (userData.toInt() == 1)) { + fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; + } + if (QApplication::activeWindow() != nullptr) { + MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); + } + emit modDetailsUpdated(false); +} + + +bool ModInfoRegular::updateNXMInfo() +{ + if (m_NexusID > 0) { + m_NexusBridge.requestDescription(m_NexusID, QVariant()); + return true; + } + return false; +} + + +void ModInfoRegular::setCategory(int categoryID, bool active) +{ + m_MetaInfoChanged = true; + + if (active) { + m_Categories.insert(categoryID); + if (m_PrimaryCategory == -1) { + m_PrimaryCategory = categoryID; + } + } else { + std::set<int>::iterator iter = m_Categories.find(categoryID); + if (iter != m_Categories.end()) { + m_Categories.erase(iter); + } + if (categoryID == m_PrimaryCategory) { + if (m_Categories.size() == 0) { + m_PrimaryCategory = -1; + } else { + m_PrimaryCategory = *(m_Categories.begin()); + } + } + } +} + + +bool ModInfoRegular::setName(const QString &name) +{ + if (name.contains('/') || name.contains('\\')) { + return false; + } + + QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); + QDir modDir(m_Path.mid(0, m_Path.length() - m_Name.length())); + + if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { + QString tempName = name; + tempName.append("_temp"); + while (modDir.exists(tempName)) { + tempName.append("_"); + } + if (!modDir.rename(m_Name, tempName)) { + return false; + } + if (!modDir.rename(tempName, name)) { + qCritical("rename to final name failed after successful rename to intermediate name"); + modDir.rename(tempName, m_Name); + return false; + } + } else { + if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { + qCritical("failed to rename mod %s (errorcode %d)", + qPrintable(name), ::GetLastError()); + return false; + } + } + + std::map<QString, unsigned int>::iterator nameIter = s_ModsByName.find(m_Name); + if (nameIter != s_ModsByName.end()) { + unsigned int index = nameIter->second; + s_ModsByName.erase(nameIter); + + m_Name = name; + m_Path = newPath; + + s_ModsByName[m_Name] = index; + + std::sort(s_Collection.begin(), s_Collection.end(), ByName); + updateIndices(); + } else { // otherwise mod isn't registered yet? + m_Name = name; + m_Path = newPath; + } + + return true; +} + +void ModInfoRegular::setNotes(const QString ¬es) +{ + m_Notes = notes; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setNexusID(int modID) +{ + m_NexusID = modID; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setVersion(const VersionInfo &version) +{ + m_Version = version; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setNewestVersion(const VersionInfo &version) +{ + if (version != m_NewestVersion) { + m_NewestVersion = version; + m_MetaInfoChanged = true; + } +} + +void ModInfoRegular::setNexusDescription(const QString &description) +{ + if (qHash(description) != qHash(m_NexusDescription)) { + m_NexusDescription = description; + m_MetaInfoChanged = true; + } +} + +void ModInfoRegular::setEndorsedState(EEndorsedState endorsedState) +{ + if (endorsedState != m_EndorsedState) { + m_EndorsedState = endorsedState; + m_MetaInfoChanged = true; + } +} + +void ModInfoRegular::setInstallationFile(const QString &fileName) +{ + m_InstallationFile = fileName; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::addNexusCategory(int categoryID) +{ + m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); +} + +void ModInfoRegular::setIsEndorsed(bool endorsed) +{ + if (m_EndorsedState != ENDORSED_NEVER) { + m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; + m_MetaInfoChanged = true; + } +} + + +void ModInfoRegular::setNeverEndorse() +{ + m_EndorsedState = ENDORSED_NEVER; + m_MetaInfoChanged = true; +} + + +bool ModInfoRegular::remove() +{ + m_MetaInfoChanged = false; + return shellDelete(QStringList(absolutePath()), true); +} + +void ModInfoRegular::endorse(bool doEndorse) +{ + if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { + m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); + } +} + + +QString ModInfoRegular::absolutePath() const +{ + return m_Path; +} + +void ModInfoRegular::ignoreUpdate(bool ignore) +{ + if (ignore) { + m_IgnoredVersion = m_NewestVersion; + } else { + m_IgnoredVersion.clear(); + } + m_MetaInfoChanged = true; +} + + +std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const +{ + std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags(); + if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE)) { + result.push_back(ModInfo::FLAG_NOTENDORSED); + } + if (!isValid()) { + result.push_back(ModInfo::FLAG_INVALID); + } + if (m_Notes.length() != 0) { + result.push_back(ModInfo::FLAG_NOTES); + } + return result; +} + + +std::vector<ModInfo::EContent> ModInfoRegular::getContents() const +{ + QTime now = QTime::currentTime(); + if (m_LastContentCheck.isNull() || (m_LastContentCheck.secsTo(now) > 60)) { + m_CachedContent.clear(); + QDir dir(absolutePath()); + if (dir.entryList(QStringList() << "*.esp" << "*.esm").size() > 0) { + m_CachedContent.push_back(CONTENT_PLUGIN); + } + if (dir.entryList(QStringList() << "*.bsa").size() > 0) { + m_CachedContent.push_back(CONTENT_BSA); + } + + ScriptExtender *extender = qApp->property("managed_game").value<IPluginGame*>()->feature<ScriptExtender>(); + + if (extender != nullptr) { + QString sePluginPath = extender->name() + "/plugins"; + if (dir.exists(sePluginPath)) m_CachedContent.push_back(CONTENT_SKSE); + } + if (dir.exists("textures")) m_CachedContent.push_back(CONTENT_TEXTURE); + if (dir.exists("meshes")) m_CachedContent.push_back(CONTENT_MESH); + if (dir.exists("interface") + || dir.exists("menus")) m_CachedContent.push_back(CONTENT_INTERFACE); + if (dir.exists("music")) m_CachedContent.push_back(CONTENT_MUSIC); + if (dir.exists("sound")) m_CachedContent.push_back(CONTENT_SOUND); + if (dir.exists("scripts")) m_CachedContent.push_back(CONTENT_SCRIPT); + if (dir.exists("strings")) m_CachedContent.push_back(CONTENT_STRING); + if (dir.exists("SkyProc Patchers")) m_CachedContent.push_back(CONTENT_SKYPROC); + + m_LastContentCheck = QTime::currentTime(); + } + + return m_CachedContent; + +} + + +int ModInfoRegular::getHighlight() const +{ + return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; +} + + +QString ModInfoRegular::getDescription() const +{ + if (!isValid()) { + return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name()); + } else { + const std::set<int> &categories = getCategories(); + std::wostringstream categoryString; + categoryString << ToWString(tr("Categories: <br>")); + CategoryFactory &categoryFactory = CategoryFactory::instance(); + for (std::set<int>::const_iterator catIter = categories.begin(); + catIter != categories.end(); ++catIter) { + if (catIter != categories.begin()) { + categoryString << " , "; + } + categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>"; + } + + return ToQString(categoryString.str()); + } +} + +QString ModInfoRegular::notes() const +{ + return m_Notes; +} + +QDateTime ModInfoRegular::creationTime() const +{ + return m_CreationTime; +} + +QString ModInfoRegular::getNexusDescription() const +{ + return m_NexusDescription; +} + +QString ModInfoRegular::repository() const +{ + return m_Repository; +} + +ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const +{ + return m_EndorsedState; +} + +QDateTime ModInfoRegular::getLastNexusQuery() const +{ + return m_LastNexusQuery; +} + +void ModInfoRegular::setURL(QString const &url) +{ + m_URL = url; + m_MetaInfoChanged = true; +} + +QString ModInfoRegular::getURL() const +{ + return m_URL; +} + + + +QStringList ModInfoRegular::archives() const +{ + QStringList result; + QDir dir(this->absolutePath()); + for (const QString &archive : dir.entryList(QStringList("*.bsa"))) { + result.append(this->absolutePath() + "/" + archive); + } + return result; +} + +void ModInfoRegular::addInstalledFile(int modId, int fileId) +{ + m_InstalledFileIDs.insert(std::make_pair(modId, fileId)); + m_MetaInfoChanged = true; +} + +std::vector<QString> ModInfoRegular::getIniTweaks() const +{ + QString metaFileName = absolutePath().append("/meta.ini"); + QSettings metaFile(metaFileName, QSettings::IniFormat); + + std::vector<QString> result; + + int numTweaks = metaFile.beginReadArray("INI Tweaks"); + + if (numTweaks != 0) { + qDebug("%d active ini tweaks in %s", + numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); + } + + for (int i = 0; i < numTweaks; ++i) { + metaFile.setArrayIndex(i); + QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); + result.push_back(filename); + } + metaFile.endArray(); + return result; +} diff --git a/src/modinforegular.h b/src/modinforegular.h new file mode 100644 index 00000000..a94d0363 --- /dev/null +++ b/src/modinforegular.h @@ -0,0 +1,349 @@ +#ifndef MODINFOREGULAR_H +#define MODINFOREGULAR_H + +#include "modinfowithconflictinfo.h" + +#include "nexusinterface.h" + +/** + * @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 ModInfoRegular : public ModInfoWithConflictInfo +{ + + Q_OBJECT + + friend class ModInfo; + +public: + + ~ModInfoRegular(); + + virtual bool isRegular() const { return true; } + + virtual bool isEmpty() const; + + /** + * @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 + **/ + bool updateAvailable() const; + + /** + * @return true if the current update is being ignored + */ + virtual bool updateIgnored() const { return m_IgnoredVersion == m_NewestVersion; } + + /** + * @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 + **/ + bool downgradeAvailable() const; + + /** + * @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. + * + * @return returns true if information for this mod will be updated, false if there is no nexus mod id to use + **/ + bool updateNXMInfo(); + + /** + * @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 + **/ + void setCategory(int categoryID, bool active); + + /** + * @brief set the name of this mod + * + * set the name of this mod. This will also update the name of the + * directory that contains this mod + * + * @param name new name of the mod + * @return true on success, false if the new name can't be used (i.e. because the new + * directory name wouldn't be valid) + **/ + bool setName(const QString &name); + + /** + * @brief change the notes (manually set information) for this mod + * @param notes new notes + */ + void setNotes(const QString ¬es); + + /** + * @brief set/change the nexus mod id of this mod + * + * @param modID the nexus mod id + **/ + void setNexusID(int modID); + + /** + * @brief set the version of this mod + * + * this can be used to overwrite the version of a mod without actually + * updating the mod + * + * @param version the new version to use + **/ + void setVersion(const MOBase::VersionInfo &version); + + /** + * @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 + **/ + void setNewestVersion(const MOBase::VersionInfo &version); + + /** + * @brief changes/updates the nexus description text + * @param description the current description text + */ + virtual void setNexusDescription(const QString &description); + + virtual void setInstallationFile(const QString &fileName); + + /** + * @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); + + /** + * @brief sets the new primary category of the mod + * @param categoryID the category to set + */ + virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; m_MetaInfoChanged = true; } + + /** + * @brief sets the download repository + * @param repository + */ + virtual void setRepository(const QString &repository) { m_Repository = repository; } + + /** + * 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); + + /** + * 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(); + + /** + * @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 + **/ + bool remove(); + + /** + * @brief endorse or un-endorse the mod + * @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); + + /** + * @brief getter for the mod name + * + * @return the mod name + **/ + QString name() const { return m_Name; } + + /** + * @brief getter for the mod path + * + * @return the (absolute) path to the mod + **/ + QString absolutePath() const; + + /** + * @brief getter for the newest version number of this mod + * + * @return newest version of the mod + **/ + MOBase::VersionInfo getNewestVersion() const { return m_NewestVersion; } + + /** + * @brief ignore the newest version for updates + */ + void ignoreUpdate(bool ignore); + + /** + * @brief getter for the installation file + * + * @return file used to install this mod from + */ + virtual QString getInstallationFile() const { return m_InstallationFile; } + /** + * @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 + **/ + int getNexusID() const { return m_NexusID; } + + /** + * @return the fixed priority of mods of this type or INT_MIN if the priority of mods + * needs to be user-modifiable + */ + virtual int getFixedPriority() const { return INT_MIN; } + + /** + * @return true if the mod can be updated + */ + virtual bool canBeUpdated() const { return m_NexusID > 0; } + + /** + * @return true if the mod can be enabled/disabled + */ + virtual bool canBeEnabled() const { return true; } + + /** + * @return a list of flags for this mod + */ + virtual std::vector<EFlag> getFlags() const; + + virtual std::vector<EContent> getContents() const; + + /** + * @return an indicator if and how this mod should be highlighted by the UI + */ + virtual int getHighlight() const; + + /** + * @return list of names of ini tweaks + **/ + std::vector<QString> getIniTweaks() const; + + /** + * @return a description about the mod, to be displayed in the ui + */ + virtual QString getDescription() const; + + /** + * @return manually set notes for this mod + */ + virtual QString notes() const; + + /** + * @return time this mod was created (file time of the directory) + */ + virtual QDateTime creationTime() const; + + /** + * @return nexus description of the mod (html) + */ + QString getNexusDescription() const; + + /** + * @return repository from which the file was downloaded + */ + virtual QString repository() const; + + /** + * @return true if the file has been endorsed on nexus + */ + virtual EEndorsedState endorsedState() const; + + /** + * @return last time nexus was queried for infos on this mod + */ + virtual QDateTime getLastNexusQuery() const; + + virtual QStringList archives() const; + + virtual void addInstalledFile(int modId, int fileId); + + /** + * @brief stores meta information back to disk + */ + virtual void saveMeta(); + + void readMeta(); + + /** + * @brief set the URL for a mod + */ + virtual void setURL(QString const &); + + /** + * @returns the URL for a mod + */ + virtual QString getURL() const; + +private: + + void setEndorsedState(EEndorsedState endorsedState); + +private slots: + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData); + void nxmEndorsementToggled(int, QVariant userData, QVariant resultData); + void nxmRequestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage); + +protected: + + ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure); + +private: + + QString m_Name; + QString m_Path; + QString m_InstallationFile; + QString m_Notes; + QString m_NexusDescription; + QString m_Repository; + QString m_URL; + + QDateTime m_CreationTime; + QDateTime m_LastNexusQuery; + + int m_NexusID; + std::set<std::pair<int, int>> m_InstalledFileIDs; + + bool m_MetaInfoChanged; + MOBase::VersionInfo m_NewestVersion; + MOBase::VersionInfo m_IgnoredVersion; + + EEndorsedState m_EndorsedState; + + NexusBridge m_NexusBridge; + + mutable std::vector<ModInfo::EContent> m_CachedContent; + mutable QTime m_LastContentCheck; + +}; + + +#endif // MODINFOREGULAR_H diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp new file mode 100644 index 00000000..fbf2345f --- /dev/null +++ b/src/modinfowithconflictinfo.cpp @@ -0,0 +1,130 @@ +#include "modinfowithconflictinfo.h" + +#include "directoryentry.h" +#include "utility.h" + +using namespace MOBase; +using namespace MOShared; + +ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure) + : m_DirectoryStructure(directoryStructure) {} + +void ModInfoWithConflictInfo::clearCaches() +{ + m_LastConflictCheck = QTime(); +} + +std::vector<ModInfo::EFlag> ModInfoWithConflictInfo::getFlags() const +{ + std::vector<ModInfo::EFlag> result; + switch (isConflicted()) { + case CONFLICT_MIXED: { + result.push_back(ModInfo::FLAG_CONFLICT_MIXED); + } break; + case CONFLICT_OVERWRITE: { + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); + } break; + case CONFLICT_OVERWRITTEN: { + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); + } break; + case CONFLICT_REDUNDANT: { + result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); + } break; + default: { /* NOP */ } + } + return result; +} + + +void ModInfoWithConflictInfo::doConflictCheck() const +{ + m_OverwriteList.clear(); + m_OverwrittenList.clear(); + + bool providesAnything = false; + + int dataID = 0; + if ((*m_DirectoryStructure)->originExists(L"data")) { + dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); + } + + std::wstring name = ToWString(this->name()); + + m_CurrentConflictState = CONFLICT_NONE; + + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector<FileEntry::Ptr> files = origin.getFiles(); + // for all files in this origin + for (FileEntry::Ptr file : files) { + const std::vector<int> &alternatives = file->getAlternatives(); + if ((alternatives.size() == 0) || (alternatives[0] == dataID)) { + // 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())); + m_OverwrittenList.insert(altIndex); + } else { + providesAnything = true; + } + + // for all non-providing alternative origins + for (int altId : alternatives) { + if ((altId != dataID) && (altId != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altId); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + if (origin.getPriority() > altOrigin.getPriority()) { + m_OverwriteList.insert(altIndex); + } else { + m_OverwrittenList.insert(altIndex); + } + } + } + } + } + m_LastConflictCheck = QTime::currentTime(); + + if (files.size() != 0) { + if (!providesAnything) + m_CurrentConflictState = CONFLICT_REDUNDANT; + else if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_MIXED; + else if (!m_OverwriteList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITE; + else if (!m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITTEN; + } + } +} + +ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const +{ + // this is costy so cache the result + QTime now = QTime::currentTime(); + if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { + doConflictCheck(); + } + + return m_CurrentConflictState; +} + + +bool ModInfoWithConflictInfo::isRedundant() const +{ + std::wstring name = ToWString(this->name()); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector<FileEntry::Ptr> files = origin.getFiles(); + bool ignore = false; + for (auto iter = files.begin(); iter != files.end(); ++iter) { + if ((*iter)->getOrigin(ignore) == origin.getID()) { + return false; + } + } + return true; + } else { + return false; + } +} diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h new file mode 100644 index 00000000..be31f20f --- /dev/null +++ b/src/modinfowithconflictinfo.h @@ -0,0 +1,64 @@ +#ifndef MODINFOWITHCONFLICTINFO_H +#define MODINFOWITHCONFLICTINFO_H + +#include "modinfo.h" + +#include <QTime> + +class ModInfoWithConflictInfo : public ModInfo +{ + +public: + + ModInfoWithConflictInfo(MOShared::DirectoryEntry **directoryStructure); + + std::vector<ModInfo::EFlag> getFlags() const; + + /** + * @brief clear all caches held for this mod + */ + virtual void clearCaches(); + + virtual std::set<unsigned int> getModOverwrite() { return m_OverwriteList; } + + virtual std::set<unsigned int> getModOverwritten() { return m_OverwrittenList; } + + virtual void doConflictCheck() const; + +private: + + enum EConflictType { + CONFLICT_NONE, + CONFLICT_OVERWRITE, + CONFLICT_OVERWRITTEN, + CONFLICT_MIXED, + CONFLICT_REDUNDANT + }; + +private: + + /** + * @return true if there is a conflict for files in this mod + */ + EConflictType isConflicted() const; + + /** + * @return true if this mod is completely replaced by others + */ + bool isRedundant() const; + +private: + + MOShared::DirectoryEntry **m_DirectoryStructure; + + mutable EConflictType m_CurrentConflictState; + mutable QTime m_LastConflictCheck; + + mutable std::set<unsigned int> m_OverwriteList; // indices of mods overritten by this mod + mutable std::set<unsigned int> m_OverwrittenList; // indices of mods overwriting this mod + +}; + + + +#endif // MODINFOWITHCONFLICTINFO_H diff --git a/src/organizer.pro b/src/organizer.pro index 261e3c1c..adc606fb 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -91,7 +91,12 @@ SOURCES += \ organizerproxy.cpp \
viewmarkingscrollbar.cpp \
plugincontainer.cpp \
- organizercore.cpp
+ organizercore.cpp \
+ modinfowithconflictinfo.cpp \
+ modinforegular.cpp \
+ modinfobackup.cpp \
+ modinfooverwrite.cpp \
+ modinfoforeign.cpp
HEADERS += \
@@ -167,7 +172,12 @@ HEADERS += \ viewmarkingscrollbar.h \
plugincontainer.h \
organizercore.h \
- iuserinterface.h
+ iuserinterface.h \
+ modinfowithconflictinfo.h \
+ modinforegular.h \
+ modinfobackup.h \
+ modinfooverwrite.h \
+ modinfoforeign.h
FORMS += \
transfersavesdialog.ui \
|
