pollymc/api/logic/InstanceList.cpp

349 lines
7.3 KiB
C++

/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QDir>
#include <QSet>
#include <QFile>
#include <QThread>
#include <QTextStream>
#include <QXmlStreamReader>
#include <QDebug>
#include "InstanceList.h"
#include "BaseInstance.h"
#include "FolderInstanceProvider.h"
InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
: QAbstractListModel(parent), m_instDir(instDir)
{
m_globalSettings = globalSettings;
resumeWatch();
}
InstanceList::~InstanceList()
{
}
int InstanceList::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_instances.count();
}
QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(parent);
if (row < 0 || row >= m_instances.size())
return QModelIndex();
return createIndex(row, column, (void *)m_instances.at(row).get());
}
QVariant InstanceList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
switch (role)
{
case InstancePointerRole:
{
QVariant v = qVariantFromValue((void *)pdata);
return v;
}
case InstanceIDRole:
{
return pdata->id();
}
case Qt::DisplayRole:
{
return pdata->name();
}
case Qt::ToolTipRole:
{
return pdata->instanceRoot();
}
case Qt::DecorationRole:
{
return pdata->iconKey();
}
// HACK: see GroupView.h in gui!
case GroupRole:
{
return pdata->group();
}
default:
break;
}
return QVariant();
}
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
{
Qt::ItemFlags f;
if (index.isValid())
{
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
return f;
}
QStringList InstanceList::getGroups()
{
return m_groups.toList();
}
void InstanceList::deleteGroup(const QString& name)
{
for(auto & instance: m_instances)
{
auto instGroupName = instance->group();
if(instGroupName == name)
{
instance->setGroupPost(QString());
}
}
}
static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list)
{
QMap<InstanceId, InstanceLocator> out;
int i = 0;
for(auto & item: list)
{
auto id = item->id();
if(out.contains(id))
{
qWarning() << "Duplicate ID" << id << "in instance list";
}
out[id] = std::make_pair(item, i);
i++;
}
return out;
}
InstanceList::InstListError InstanceList::loadList(bool complete)
{
auto existingIds = getIdMapping(m_instances);
QList<InstancePtr> newList;
auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids)
{
for(auto & id: ids)
{
if(existingIds.contains(id))
{
auto instPair = existingIds[id];
/*
auto & instPtr = instPair.first;
auto & instIdx = instPair.second;
*/
existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id;
}
else
{
InstancePtr instPtr = provider->loadInstance(id);
if(instPtr)
{
newList.append(instPtr);
}
}
}
};
if(complete)
{
for(auto & item: m_providers)
{
processIds(item.get(), item->discoverInstances());
}
}
else
{
for (auto & item: m_updatedProviders)
{
processIds(item, item->discoverInstances());
}
}
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
if(!existingIds.isEmpty())
{
// get the list of removed instances and sort it by their original index, from last to first
auto deadList = existingIds.values();
auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool
{
return a.second > b.second;
};
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
// remove the contiguous ranges of rows
int front_bookmark = -1;
int back_bookmark = -1;
int currentItem = -1;
auto removeNow = [&]()
{
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows();
front_bookmark = -1;
back_bookmark = currentItem;
};
for(auto & removedItem: deadList)
{
auto instPtr = removedItem.first;
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
{
continue;
}
instPtr->invalidate();
currentItem = removedItem.second;
if(back_bookmark == -1)
{
// no bookmark yet
back_bookmark = currentItem;
}
else if(currentItem == front_bookmark - 1)
{
// part of contiguous sequence, continue
}
else
{
// seam between previous and current item
removeNow();
}
front_bookmark = currentItem;
}
if(back_bookmark != -1)
{
removeNow();
}
}
if(newList.size())
{
add(newList);
}
m_updatedProviders.clear();
return NoError;
}
void InstanceList::saveNow()
{
for(auto & item: m_instances)
{
item->saveNow();
}
}
void InstanceList::add(const QList<InstancePtr> &t)
{
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
m_instances.append(t);
for(auto & ptr : t)
{
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
}
endInsertRows();
}
void InstanceList::resumeWatch()
{
if(m_watchLevel > 0)
{
qWarning() << "Bad suspend level resume in instance list";
return;
}
m_watchLevel++;
if(m_watchLevel > 0 && !m_updatedProviders.isEmpty())
{
loadList();
}
}
void InstanceList::suspendWatch()
{
m_watchLevel --;
}
void InstanceList::providerUpdated()
{
auto provider = dynamic_cast<BaseInstanceProvider *>(QObject::sender());
if(!provider)
{
qWarning() << "InstanceList::providerUpdated triggered by a non-provider";
return;
}
m_updatedProviders.insert(provider);
if(m_watchLevel == 1)
{
loadList();
}
}
void InstanceList::groupsPublished(QSet<QString> newGroups)
{
m_groups.unite(newGroups);
}
void InstanceList::addInstanceProvider(BaseInstanceProvider* provider)
{
connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated);
connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished);
m_providers.append(provider);
}
InstancePtr InstanceList::getInstanceById(QString instId) const
{
if(instId.isEmpty())
return InstancePtr();
for(auto & inst: m_instances)
{
if (inst->id() == instId)
{
return inst;
}
}
return InstancePtr();
}
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
{
return index(getInstIndex(getInstanceById(id).get()));
}
int InstanceList::getInstIndex(BaseInstance *inst) const
{
int count = m_instances.count();
for (int i = 0; i < count; i++)
{
if (inst == m_instances[i].get())
{
return i;
}
}
return -1;
}
void InstanceList::propertiesChanged(BaseInstance *inst)
{
int i = getInstIndex(inst);
if (i != -1)
{
emit dataChanged(index(i), index(i));
}
}