/*
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 "modinfo.h"
#include "utility.h"
#include "installationtester.h"
#include "categories.h"
#include "report.h"
#include "modinfodialog.h"
#include "overwriteinfodialog.h"
#include "json.h"
#include "messagedialog.h"
#include
#include
#include
#include
#include
#include
#include
using namespace MOBase;
using namespace MOShared;
std::vector ModInfo::s_Collection;
std::map ModInfo::s_ModsByName;
std::map > ModInfo::s_ModsByModID;
int ModInfo::s_NextID;
QMutex ModInfo::s_Mutex(QMutex::Recursive);
QString ModInfo::s_HiddenExt(".mohidden");
static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS)
{
return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0;
}
ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStructure)
{
QMutexLocker locker(&s_Mutex);
// int id = s_NextID++;
static QRegExp backupExp(".*backup[0-9]*");
ModInfo::Ptr result;
if (backupExp.exactMatch(dir.dirName())) {
result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure));
} else {
result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure));
}
s_Collection.push_back(result);
return result;
}
void ModInfo::createFromOverwrite()
{
QMutexLocker locker(&s_Mutex);
s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite));
}
unsigned int ModInfo::getNumMods()
{
QMutexLocker locker(&s_Mutex);
return s_Collection.size();
}
ModInfo::Ptr ModInfo::getByIndex(unsigned int index)
{
QMutexLocker locker(&s_Mutex);
if (index >= s_Collection.size()) {
throw MyException(tr("invalid index %1").arg(index));
}
return s_Collection[index];
}
std::vector ModInfo::getByModID(int modID)
{
QMutexLocker locker(&s_Mutex);
auto iter = s_ModsByModID.find(modID);
if (iter == s_ModsByModID.end()) {
return std::vector();
}
std::vector result;
for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) {
result.push_back(getByIndex(*idxIter));
}
return result;
}
bool ModInfo::removeMod(unsigned int index)
{
QMutexLocker locker(&s_Mutex);
if (index >= s_Collection.size()) {
throw MyException(tr("invalid index %1").arg(index));
}
// update the indices first
ModInfo::Ptr modInfo = s_Collection[index];
s_ModsByName.erase(s_ModsByName.find(modInfo->name()));
auto iter = s_ModsByModID.find(modInfo->getNexusID());
if (iter != s_ModsByModID.end()) {
std::vector indices = iter->second;
std::remove(indices.begin(), indices.end(), index);
s_ModsByModID[modInfo->getNexusID()] = indices;
}
// physically remove the mod directory
//TODO the return value is ignored because the indices were already removed here, so stopping
// would cause data inconsistencies. Instead we go through with the removal but the mod will show up
// again if the user refreshes
modInfo->remove();
// finally, remove the mod from the collection
s_Collection.erase(s_Collection.begin() + index);
// and update the indices
updateIndices();
return true;
}
unsigned int ModInfo::getIndex(const QString &name)
{
QMutexLocker locker(&s_Mutex);
std::map::iterator iter = s_ModsByName.find(name);
if (iter == s_ModsByName.end()) {
return UINT_MAX;
}
return iter->second;
}
unsigned int ModInfo::findMod(const boost::function &filter)
{
for (unsigned int i = 0U; i < s_Collection.size(); ++i) {
if (filter(s_Collection[i])) {
return i;
}
}
return UINT_MAX;
}
void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure)
{
QMutexLocker lock(&s_Mutex);
s_Collection.clear();
s_NextID = 0;
// list all directories in the mod directory and make a mod out of each
QDir mods(QDir::fromNativeSeparators(modDirectory));
mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
QDirIterator modIter(mods);
while (modIter.hasNext()) {
createFrom(QDir(modIter.next()), directoryStructure);
}
createFromOverwrite();
std::sort(s_Collection.begin(), s_Collection.end(), ByName);
updateIndices();
}
void ModInfo::updateIndices()
{
s_ModsByName.clear();
s_ModsByModID.clear();
QRegExp backupRegEx(".*backup[0-9]*$");
for (unsigned int i = 0; i < s_Collection.size(); ++i) {
QString modName = s_Collection[i]->name();
int modID = s_Collection[i]->getNexusID();
s_ModsByName[modName] = i;
s_ModsByModID[modID].push_back(i);
}
}
ModInfo::ModInfo()
: m_Valid(false), m_PrimaryCategory(-1)
{
}
void ModInfo::checkChunkForUpdate(const std::vector &modIDs, QObject *receiver)
{
if (modIDs.size() != 0) {
NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant());
}
}
int ModInfo::checkAllForUpdate(QObject *receiver)
{
int result = 0;
std::vector modIDs;
modIDs.push_back(GameInfo::instance().getNexusModID());
for (std::vector::iterator iter = s_Collection.begin();
iter != s_Collection.end(); ++iter) {
if ((*iter)->canBeUpdated()) {
modIDs.push_back((*iter)->getNexusID());
if (modIDs.size() >= 255) {
checkChunkForUpdate(modIDs, receiver);
modIDs.clear();
}
}
}
checkChunkForUpdate(modIDs, receiver);
return result;
}
void ModInfo::setVersion(const VersionInfo &version)
{
m_Version = version;
}
bool ModInfo::categorySet(int categoryID) const
{
for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) {
if ((*iter == categoryID) ||
(CategoryFactory::instance().isDecendantOf(*iter, categoryID))) {
return true;
}
}
return false;
}
void ModInfo::testValid()
{
m_Valid = false;
QDirIterator dirIter(absolutePath());
while (dirIter.hasNext()) {
dirIter.next();
if (dirIter.fileInfo().isDir()) {
if (InstallationTester::isTopLevelDirectory(dirIter.fileName())) {
m_Valid = true;
break;
}
} else {
if (InstallationTester::isTopLevelSuffix(dirIter.fileName())) {
m_Valid = true;
break;
}
}
}
// NOTE: in Qt 4.7 it seems that QDirIterator leaves a file handle open if it is not iterated to the
// end
while (dirIter.hasNext()) {
dirIter.next();
}
}
ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure)
: ModInfo(), m_Name(path.dirName()), m_Path(path.absolutePath()), m_MetaInfoChanged(false),
m_EndorsedState(ENDORSED_UNKNOWN), m_DirectoryStructure(directoryStructure)
{
testValid();
m_CreationTime = QFileInfo(path.absolutePath()).created();
// read out the meta-file for information
QString metaFileName = path.absoluteFilePath("meta.ini");
QSettings metaFile(metaFileName, 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_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate);
if (metaFile.contains("endorsed")) {
if (metaFile.value("endorsed").canConvert()) {
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;
}
}
}
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::saveMeta()
{
// only write meta data if the mod directory exists
if (m_MetaInfoChanged && QFile::exists(absolutePath())) {
bool success = false;
{
QSettings metaFile(absolutePath().append("/meta.ini.new"), QSettings::IniFormat);
if (metaFile.status() == QSettings::NoError) {
std::set 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());
if (m_NexusID != -1) {
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.sync(); // sync needs to be called to ensure the file is created
if (metaFile.status() == QSettings::NoError) {
success = true;
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()));
}
}
if (success) {
if (!QFile::remove(absolutePath().append("/meta.ini"))) {
qCritical("failed to remove %s", qPrintable(absolutePath().append("/meta.ini")));
return;
}
if (QFile::rename(absolutePath().append("/meta.ini.new"), absolutePath().append("/meta.ini"))) {
qDebug("%s written", qPrintable(absolutePath().append("/meta.ini")));
} else {
qCritical("writing %s failed", qPrintable(absolutePath().append("/meta.ini")));
}
}
}
}
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();
m_NewestVersion.parse(result["version"].toString());
m_NexusDescription = result["description"].toString();
if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) {
m_EndorsedState = 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() && (userData.toInt() == 1)) {
fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading.";
}
if (QApplication::activeWindow() != NULL) {
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::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::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) {
m_NewestVersion = version;
}
void ModInfoRegular::setNexusDescription(const QString &description)
{
m_NexusDescription = description;
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()));
}
void ModInfoRegular::endorse(bool doEndorse)
{
if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) {
m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1));
}
}
void ModInfoRegular::clearCaches()
{
m_LastConflictCheck = QTime();
}
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 ModInfoRegular::getFlags() const
{
std::vector result;
if (!isValid()) {
result.push_back(ModInfo::FLAG_INVALID);
}
if ((m_NexusID != -1) && (endorsedState() == ENDORSED_FALSE)) {
result.push_back(ModInfo::FLAG_NOTENDORSED);
}
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 */ }
}
if (m_Notes.length() != 0) {
result.push_back(ModInfo::FLAG_NOTES);
}
return result;
}
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 &categories = 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 << " , ";
}
categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "";
}
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;
}
ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const
{
return m_EndorsedState;
}
ModInfoRegular::EConflictType ModInfoRegular::isConflicted() const
{
// this is costy so cache the result
QTime now = QTime::currentTime();
if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) {
bool overwrite = false;
bool overwritten = false;
bool regular = false;
int dataID = 0;
if ((*m_DirectoryStructure)->originExists(L"data")) {
dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID();
}
std::wstring name = ToWString(m_Name);
if ((*m_DirectoryStructure)->originExists(name)) {
FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
std::vector files = origin.getFiles();
for (auto iter = files.begin(); iter != files.end() && (!overwrite || !overwritten || !regular); ++iter) {
const std::vector &alternatives = (*iter)->getAlternatives();
if (alternatives.size() == 0) {
// no alternatives -> no conflict
regular = true;
} else {
for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) {
// don't treat files overwritten in data as "conflict"
if (*altIter != dataID) {
bool ignore = false;
if ((*iter)->getOrigin(ignore) == origin.getID()) {
overwrite = true;
break;
} else {
overwritten = true;
break;
}
} else if (alternatives.size() == 1) {
// only alternative is data -> no conflict
regular = true;
}
}
}
}
}
m_LastConflictCheck = QTime::currentTime();
if (overwrite && overwritten) m_CurrentConflictState = CONFLICT_MIXED;
else if (overwrite) m_CurrentConflictState = CONFLICT_OVERWRITE;
else if (overwritten) {
if (!regular) {
m_CurrentConflictState = CONFLICT_REDUNDANT;
} else {
m_CurrentConflictState = CONFLICT_OVERWRITTEN;
}
}
else m_CurrentConflictState = CONFLICT_NONE;
}
return m_CurrentConflictState;
}
bool ModInfoRegular::isRedundant() const
{
std::wstring name = ToWString(m_Name);
if ((*m_DirectoryStructure)->originExists(name)) {
FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
std::vector 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;
}
}
QDateTime ModInfoRegular::getLastNexusQuery() const
{
return m_LastNexusQuery;
}
std::vector ModInfoRegular::getIniTweaks() const
{
QString metaFileName = absolutePath().append("/meta.ini");
QSettings metaFile(metaFileName, QSettings::IniFormat);
std::vector 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 ModInfoBackup::getFlags() const
{
std::vector 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()
: m_StartupTime(QDateTime::currentDateTime())
{
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(ToQString(GameInfo::instance().getOverwriteDir()));
}
std::vector ModInfoOverwrite::getFlags() const
{
std::vector 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)");
}