/*
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 "modinfobackup.h"
#include "modinforegular.h"
#include "modinfoforeign.h"
#include "modinfooverwrite.h"
#include "installationtester.h"
#include "categories.h"
#include "modinfodialog.h"
#include "overwriteinfodialog.h"
#include "filenamestring.h"
#include "versioninfo.h"
#include
#include
#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;
}
ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName,
const QString &espName,
const QStringList &bsaNames,
ModInfo::EModType modType,
DirectoryEntry **directoryStructure) {
QMutexLocker locker(&s_Mutex);
ModInfo::Ptr result = ModInfo::Ptr(
new ModInfoForeign(modName, espName, bsaNames, modType, directoryStructure));
s_Collection.push_back(result);
return result;
}
QString ModInfo::getContentTypeName(int contentType)
{
switch (contentType) {
case CONTENT_PLUGIN: return tr("Plugins");
case CONTENT_TEXTURE: return tr("Textures");
case CONTENT_MESH: return tr("Meshes");
case CONTENT_BSA: return tr("BSA");
case CONTENT_INTERFACE: return tr("UI Changes");
case CONTENT_SOUND: return tr("Sound Effects");
case CONTENT_SCRIPT: return tr("Scripts");
case CONTENT_SKSE: return tr("SKSE Plugins");
case CONTENT_SKYPROC: return tr("SkyProc Tools");
case CONTENT_MCM: return tr("MCM Data");
default: throw MyException(tr("invalid content type %1").arg(contentType));
}
}
void ModInfo::createFromOverwrite()
{
QMutexLocker locker(&s_Mutex);
s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite));
}
unsigned int ModInfo::getNumMods()
{
QMutexLocker locker(&s_Mutex);
return static_cast(s_Collection.size());
}
ModInfo::Ptr ModInfo::getByIndex(unsigned int index)
{
QMutexLocker locker(&s_Mutex);
if (index >= s_Collection.size()) {
throw MyException(tr("invalid mod 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("remove: invalid mod 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;
indices.erase(std::remove(indices.begin(), indices.end(), index), indices.end());
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,
bool displayForeign,
MOBase::IPluginGame const *game)
{
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);
}
}
UnmanagedMods *unmanaged = game->feature();
if (unmanaged != nullptr) {
for (const QString &modName : unmanaged->mods(!displayForeign)) {
ModInfo::EModType modType = game->DLCPlugins().contains(unmanaged->referenceFile(modName).fileName(), Qt::CaseInsensitive) ? ModInfo::EModType::MOD_DLC :
(game->CCPlugins().contains(unmanaged->referenceFile(modName).fileName(), Qt::CaseInsensitive) ? ModInfo::EModType::MOD_CC : ModInfo::EModType::MOD_DEFAULT);
createFromPlugin(unmanaged->displayName(modName),
unmanaged->referenceFile(modName).absoluteFilePath(),
unmanaged->secondaryFiles(modName),
modType,
directoryStructure);
}
}
createFromOverwrite();
std::sort(s_Collection.begin(), s_Collection.end(), ByName);
updateIndices();
}
void ModInfo::updateIndices()
{
s_ModsByName.clear();
s_ModsByModID.clear();
for (unsigned int i = 0; i < s_Collection.size(); ++i) {
QString modName = s_Collection[i]->internalName();
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(), QString());
}
}
int ModInfo::checkAllForUpdate(QObject *receiver)
{
// technically this should be 255 but those requests can take nexus fairly long, produce
// large output and may have been the cause of issue #1166
static const int chunkSize = 64;
int result = 0;
std::vector modIDs;
//I ought to store this, it's used elsewhere
IPluginGame const *game = qApp->property("managed_game").value();
modIDs.push_back(game->nexusModOrganizerID());
for (const ModInfo::Ptr &mod : s_Collection) {
if (mod->canBeUpdated()) {
modIDs.push_back(mod->getNexusID());
if (modIDs.size() >= chunkSize) {
checkChunkForUpdate(modIDs, receiver);
modIDs.clear();
}
}
}
checkChunkForUpdate(modIDs, receiver);
return result;
}
void ModInfo::setVersion(const VersionInfo &version)
{
m_Version = version;
}
void ModInfo::setPluginSelected(const bool &isSelected)
{
m_PluginSelected = isSelected;
}
void ModInfo::addCategory(const QString &categoryName)
{
int id = CategoryFactory::instance().getCategoryID(categoryName);
if (id == -1) {
id = CategoryFactory::instance().addCategory(categoryName, std::vector(), 0);
}
setCategory(id, true);
}
bool ModInfo::removeCategory(const QString &categoryName)
{
int id = CategoryFactory::instance().getCategoryID(categoryName);
if (id == -1) {
return false;
}
if (!categorySet(id)) {
return false;
}
setCategory(id, false);
return true;
}
QStringList ModInfo::categories()
{
QStringList result;
CategoryFactory &catFac = CategoryFactory::instance();
for (int id : m_Categories) {
result.append(catFac.getCategoryName(catFac.getCategoryIndex(id)));
}
return result;
}
bool ModInfo::hasFlag(ModInfo::EFlag flag) const
{
std::vector flags = getFlags();
return std::find(flags.begin(), flags.end(), flag) != flags.end();
}
bool ModInfo::hasContent(ModInfo::EContent content) const
{
std::vector contents = getContents();
return std::find(contents.begin(), contents.end(), content) != contents.end();
}
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();
}
}