NOISSUE simplify.

This commit is contained in:
Petr Mrázek 2018-07-24 00:11:24 +02:00
parent 7b439c85c0
commit 76d6ec91a4
21 changed files with 140 additions and 224 deletions

View File

@ -284,19 +284,3 @@ std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
{ {
return m_launchProcess; return m_launchProcess;
} }
void BaseInstance::setProvider(BaseInstanceProvider* provider)
{
// only once.
assert(!m_provider);
if(m_provider)
{
qWarning() << "Provider set more than once for instance" << id();
}
m_provider = provider;
}
BaseInstanceProvider* BaseInstance::provider() const
{
return m_provider;
}

View File

@ -38,7 +38,6 @@ class QDir;
class Task; class Task;
class LaunchTask; class LaunchTask;
class BaseInstance; class BaseInstance;
class BaseInstanceProvider;
// pointer for lazy people // pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr; typedef std::shared_ptr<BaseInstance> InstancePtr;
@ -89,9 +88,6 @@ public:
int64_t totalTimePlayed() const; int64_t totalTimePlayed() const;
void resetTimePlayed(); void resetTimePlayed();
void setProvider(BaseInstanceProvider * provider);
BaseInstanceProvider * provider() const;
/// get the type of this instance /// get the type of this instance
QString instanceType() const; QString instanceType() const;
@ -271,7 +267,6 @@ protected: /* data */
bool m_isRunning = false; bool m_isRunning = false;
std::shared_ptr<LaunchTask> m_launchProcess; std::shared_ptr<LaunchTask> m_launchProcess;
QDateTime m_timeStarted; QDateTime m_timeStarted;
BaseInstanceProvider * m_provider = nullptr;
private: /* data */ private: /* data */
Status m_status = Status::Present; Status m_status = Status::Present;

View File

@ -1,58 +0,0 @@
#pragma once
#include <QObject>
#include <QString>
#include "BaseInstance.h"
#include "settings/SettingsObject.h"
#include "multimc_logic_export.h"
using InstanceId = QString;
using GroupId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
enum class InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
class MULTIMC_LOGIC_EXPORT BaseInstanceProvider : public QObject
{
Q_OBJECT
public:
BaseInstanceProvider(SettingsObjectPtr settings) : m_globalSettings(settings)
{
// nil
}
public:
virtual QList<InstanceId> discoverInstances() = 0;
virtual InstancePtr loadInstance(const InstanceId &id) = 0;
virtual void loadGroupList() = 0;
virtual void saveGroupList() = 0;
virtual QString getStagedInstancePath()
{
return QString();
}
virtual bool commitStagedInstance(const QString & path, const QString& instanceName, const QString & groupName)
{
return false;
}
virtual bool destroyStagingPath(const QString & path)
{
return true;
}
signals:
// Emit this when the list of provided instances changed
void instancesChanged();
// Emit when the set of groups your provider supplies changes.
void groupsChanged(QSet<QString> groups);
protected:
SettingsObjectPtr m_globalSettings;
};

View File

@ -16,7 +16,6 @@ set(CORE_SOURCES
LoggedProcess.cpp LoggedProcess.cpp
MessageLevel.cpp MessageLevel.cpp
MessageLevel.h MessageLevel.h
BaseInstanceProvider.h
FolderInstanceProvider.h FolderInstanceProvider.h
FolderInstanceProvider.cpp FolderInstanceProvider.cpp
BaseVersion.h BaseVersion.h

View File

@ -0,0 +1,43 @@
#pragma once
template <typename T>
inline void clamp(T& current, T min, T max)
{
if (current < min)
{
current = min;
}
else if(current > max)
{
current = max;
}
}
// List of numbers from min to max. Next is exponent times bigger than previous.
class ExponentialSeries
{
public:
ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
{
m_current = m_min = min;
m_max = max;
m_exponent = exponent;
}
void reset()
{
m_current = m_min;
}
unsigned operator()()
{
unsigned retval = m_current;
m_current *= m_exponent;
clamp(m_current, m_min, m_max);
return retval;
}
unsigned m_current;
unsigned m_min;
unsigned m_max;
unsigned m_exponent;
};

View File

@ -4,6 +4,9 @@
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/legacy/LegacyInstance.h" #include "minecraft/legacy/LegacyInstance.h"
#include "NullInstance.h" #include "NullInstance.h"
#include "ExponentialSeries.h"
#include "WatchLock.h"
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
@ -16,23 +19,8 @@
const static int GROUP_FILE_FORMAT_VERSION = 1; const static int GROUP_FILE_FORMAT_VERSION = 1;
struct WatchLock
{
WatchLock(QFileSystemWatcher * watcher, const QString& instDir)
: m_watcher(watcher), m_instDir(instDir)
{
m_watcher->removePath(m_instDir);
}
~WatchLock()
{
m_watcher->addPath(m_instDir);
}
QFileSystemWatcher * m_watcher;
QString m_instDir;
};
FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir) FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir)
: BaseInstanceProvider(settings) : m_globalSettings(settings)
{ {
// Create aand normalize path // Create aand normalize path
if (!QDir::current().exists(instDir)) if (!QDir::current().exists(instDir))
@ -105,7 +93,6 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
} }
inst->init(); inst->init();
inst->setProvider(this);
auto iter = groupMap.find(id); auto iter = groupMap.find(id);
if (iter != groupMap.end()) if (iter != groupMap.end())
{ {
@ -313,55 +300,13 @@ void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVaria
} }
} }
template <typename T> class InstanceStaging : public Task
static void clamp(T& current, T min, T max)
{
if (current < min)
{
current = min;
}
else if(current > max)
{
current = max;
}
}
namespace {
// List of numbers from min to max. Next is exponent times bigger than previous.
class ExponentialSeries
{
public:
ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
{
m_current = m_min = min;
m_max = max;
m_exponent = exponent;
}
void reset()
{
m_current = m_min;
}
unsigned operator()()
{
unsigned retval = m_current;
m_current *= m_exponent;
clamp(m_current, m_min, m_max);
return retval;
}
unsigned m_current;
unsigned m_min;
unsigned m_max;
unsigned m_exponent;
};
}
class FolderInstanceStaging : public Task
{ {
Q_OBJECT Q_OBJECT
const unsigned minBackoff = 1; const unsigned minBackoff = 1;
const unsigned maxBackoff = 16; const unsigned maxBackoff = 16;
public: public:
FolderInstanceStaging ( InstanceStaging (
FolderInstanceProvider * parent, FolderInstanceProvider * parent,
Task * child, Task * child,
const QString & stagingPath, const QString & stagingPath,
@ -371,18 +316,18 @@ public:
{ {
m_parent = parent; m_parent = parent;
m_child.reset(child); m_child.reset(child);
connect(child, &Task::succeeded, this, &FolderInstanceStaging::childSucceded); connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded);
connect(child, &Task::failed, this, &FolderInstanceStaging::childFailed); connect(child, &Task::failed, this, &InstanceStaging::childFailed);
connect(child, &Task::status, this, &FolderInstanceStaging::setStatus); connect(child, &Task::status, this, &InstanceStaging::setStatus);
connect(child, &Task::progress, this, &FolderInstanceStaging::setProgress); connect(child, &Task::progress, this, &InstanceStaging::setProgress);
m_instanceName = instanceName; m_instanceName = instanceName;
m_groupName = groupName; m_groupName = groupName;
m_stagingPath = stagingPath; m_stagingPath = stagingPath;
m_backoffTimer.setSingleShot(true); m_backoffTimer.setSingleShot(true);
connect(&m_backoffTimer, &QTimer::timeout, this, &FolderInstanceStaging::childSucceded); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
} }
virtual ~FolderInstanceStaging() {}; virtual ~InstanceStaging() {};
protected: protected:
virtual void executeTask() override virtual void executeTask() override
@ -439,7 +384,7 @@ Task * FolderInstanceProvider::wrapInstanceTask(InstanceTask * task)
auto stagingPath = getStagedInstancePath(); auto stagingPath = getStagedInstancePath();
task->setStagingPath(stagingPath); task->setStagingPath(stagingPath);
task->setParentSettings(m_globalSettings); task->setParentSettings(m_globalSettings);
return new FolderInstanceStaging(this, task, stagingPath, task->name(), task->group()); return new InstanceStaging(this, task, stagingPath, task->name(), task->group());
} }
QString FolderInstanceProvider::getStagedInstancePath() QString FolderInstanceProvider::getStagedInstancePath()

View File

@ -1,23 +1,42 @@
#pragma once #pragma once
#include "BaseInstanceProvider.h"
#include <QObject>
#include <QString>
#include <QMap> #include <QMap>
#include "BaseInstance.h"
#include "settings/SettingsObject.h"
#include "multimc_logic_export.h"
class QFileSystemWatcher; class QFileSystemWatcher;
class InstanceTask; class InstanceTask;
using InstanceId = QString;
using GroupId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public BaseInstanceProvider enum class InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
FolderInstanceProvider(SettingsObjectPtr settings, const QString & instDir); FolderInstanceProvider(SettingsObjectPtr settings, const QString & instDir);
virtual ~FolderInstanceProvider() = default;
public: public:
/// used by InstanceList to @return a list of plausible IDs to probe for /// used by InstanceList to @return a list of plausible IDs to probe for
QList<InstanceId> discoverInstances() override; QList<InstanceId> discoverInstances();
/// used by InstanceList to (re)load an instance with the given @id. /// used by InstanceList to (re)load an instance with the given @id.
InstancePtr loadInstance(const InstanceId& id) override; InstancePtr loadInstance(const InstanceId& id);
// Wrap an instance creation task in some more task machinery and make it ready to be used // Wrap an instance creation task in some more task machinery and make it ready to be used
Task * wrapInstanceTask(InstanceTask * task); Task * wrapInstanceTask(InstanceTask * task);
@ -26,17 +45,24 @@ public:
* Create a new empty staging area for instance creation and @return a path/key top commit it later. * Create a new empty staging area for instance creation and @return a path/key top commit it later.
* Used by instance manipulation tasks. * Used by instance manipulation tasks.
*/ */
QString getStagedInstancePath() override; QString getStagedInstancePath();
/** /**
* Commit the staging area given by @keyPath to the provider - used when creation succeeds. * Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* Used by instance manipulation tasks. * Used by instance manipulation tasks.
*/ */
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName) override; bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName);
/** /**
* Destroy a previously created staging area given by @keyPath - used when creation fails. * Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks. * Used by instance manipulation tasks.
*/ */
bool destroyStagingPath(const QString & keyPath) override; bool destroyStagingPath(const QString & keyPath);
signals:
// Emit this when the list of provided instances changed
void instancesChanged();
// Emit when the set of groups your provider supplies changes.
void groupsChanged(QSet<QString> groups);
public slots: public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value); void on_InstFolderChanged(const Setting &setting, QVariant value);
@ -46,10 +72,11 @@ private slots:
void groupChanged(); void groupChanged();
private: /* methods */ private: /* methods */
void loadGroupList() override; void loadGroupList();
void saveGroupList() override; void saveGroupList();
private: /* data */ private: /* data */
SettingsObjectPtr m_globalSettings;
QString m_instDir; QString m_instDir;
QFileSystemWatcher * m_watcher; QFileSystemWatcher * m_watcher;
QMap<InstanceId, GroupId> groupMap; QMap<InstanceId, GroupId> groupMap;

View File

@ -1,5 +1,4 @@
#include "InstanceCopyTask.h" #include "InstanceCopyTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "NullInstance.h" #include "NullInstance.h"

View File

@ -11,8 +11,6 @@
#include "BaseInstance.h" #include "BaseInstance.h"
#include "InstanceTask.h" #include "InstanceTask.h"
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask
{ {
Q_OBJECT Q_OBJECT

View File

@ -1,5 +1,4 @@
#include "InstanceCreationTask.h" #include "InstanceCreationTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "FileSystem.h" #include "FileSystem.h"

View File

@ -1,6 +1,5 @@
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "Env.h" #include "Env.h"
#include "MMCZip.h" #include "MMCZip.h"

View File

@ -10,7 +10,6 @@
#include "QObjectPtr.h" #include "QObjectPtr.h"
class QuaZip; class QuaZip;
class BaseInstanceProvider;
namespace Flame namespace Flame
{ {
class FileResolvingTask; class FileResolvingTask;

View File

@ -155,50 +155,29 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &
return out; return out;
} }
InstanceList::InstListError InstanceList::loadList(bool complete) InstanceList::InstListError InstanceList::loadList()
{ {
auto existingIds = getIdMapping(m_instances); auto existingIds = getIdMapping(m_instances);
QList<InstancePtr> newList; QList<InstancePtr> newList;
auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids) for(auto & id: m_provider->discoverInstances())
{
for(auto & id: ids)
{ {
if(existingIds.contains(id)) if(existingIds.contains(id))
{ {
auto instPair = existingIds[id]; auto instPair = existingIds[id];
/*
auto & instPtr = instPair.first;
auto & instIdx = instPair.second;
*/
existingIds.remove(id); existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id; qDebug() << "Should keep and soft-reload" << id;
} }
else else
{ {
InstancePtr instPtr = provider->loadInstance(id); InstancePtr instPtr = m_provider->loadInstance(id);
if(instPtr) if(instPtr)
{ {
newList.append(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. // TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
if(!existingIds.isEmpty()) if(!existingIds.isEmpty())
@ -225,10 +204,6 @@ InstanceList::InstListError InstanceList::loadList(bool complete)
for(auto & removedItem: deadList) for(auto & removedItem: deadList)
{ {
auto instPtr = removedItem.first; auto instPtr = removedItem.first;
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
{
continue;
}
instPtr->invalidate(); instPtr->invalidate();
currentItem = removedItem.second; currentItem = removedItem.second;
if(back_bookmark == -1) if(back_bookmark == -1)
@ -256,7 +231,7 @@ InstanceList::InstListError InstanceList::loadList(bool complete)
{ {
add(newList); add(newList);
} }
m_updatedProviders.clear(); m_dirty = false;
return NoError; return NoError;
} }
@ -287,7 +262,7 @@ void InstanceList::resumeWatch()
return; return;
} }
m_watchLevel++; m_watchLevel++;
if(m_watchLevel > 0 && !m_updatedProviders.isEmpty()) if(m_watchLevel > 0 && m_dirty)
{ {
loadList(); loadList();
} }
@ -300,13 +275,13 @@ void InstanceList::suspendWatch()
void InstanceList::providerUpdated() void InstanceList::providerUpdated()
{ {
auto provider = dynamic_cast<BaseInstanceProvider *>(QObject::sender()); auto provider = dynamic_cast<FolderInstanceProvider *>(QObject::sender());
if(!provider) if(!provider)
{ {
qWarning() << "InstanceList::providerUpdated triggered by a non-provider"; qWarning() << "InstanceList::providerUpdated triggered by a non-provider";
return; return;
} }
m_updatedProviders.insert(provider); m_dirty = true;
if(m_watchLevel == 1) if(m_watchLevel == 1)
{ {
loadList(); loadList();
@ -318,11 +293,11 @@ void InstanceList::groupsPublished(QSet<QString> newGroups)
m_groups.unite(newGroups); m_groups.unite(newGroups);
} }
void InstanceList::addInstanceProvider(BaseInstanceProvider* provider) void InstanceList::addInstanceProvider(FolderInstanceProvider* provider)
{ {
connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated); connect(provider, &FolderInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated);
connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished); connect(provider, &FolderInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished);
m_providers.append(provider); m_provider = provider;
} }
InstancePtr InstanceList::getInstanceById(QString instId) const InstancePtr InstanceList::getInstanceById(QString instId) const

View File

@ -21,14 +21,12 @@
#include <QList> #include <QList>
#include "BaseInstance.h" #include "BaseInstance.h"
#include "BaseInstanceProvider.h" #include "FolderInstanceProvider.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
class BaseInstance;
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@ -70,11 +68,11 @@ public:
return m_instances.count(); return m_instances.count();
} }
InstListError loadList(bool complete = false); InstListError loadList();
void saveNow(); void saveNow();
/// Add an instance provider. Takes ownership of it. Should only be done before the first load. /// Add an instance provider. Takes ownership of it. Should only be done before the first load.
void addInstanceProvider(BaseInstanceProvider * provider); void addInstanceProvider(FolderInstanceProvider * provider);
InstancePtr getInstanceById(QString id) const; InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const; QModelIndex getInstanceIndexById(const QString &id) const;
@ -99,8 +97,8 @@ private:
protected: protected:
int m_watchLevel = 0; int m_watchLevel = 0;
QSet<BaseInstanceProvider *> m_updatedProviders; bool m_dirty = false;
QList<InstancePtr> m_instances; QList<InstancePtr> m_instances;
QSet<QString> m_groups; QSet<QString> m_groups;
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers; FolderInstanceProvider * m_provider;
}; };

View File

@ -4,8 +4,6 @@
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceTask : public Task class MULTIMC_LOGIC_EXPORT InstanceTask : public Task
{ {
Q_OBJECT Q_OBJECT

20
api/logic/WatchLock.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <QString>
#include <QFileSystemWatcher>
struct WatchLock
{
WatchLock(QFileSystemWatcher * watcher, const QString& directory)
: m_watcher(watcher), m_directory(directory)
{
m_watcher->removePath(m_directory);
}
~WatchLock()
{
m_watcher->addPath(m_directory);
}
QFileSystemWatcher * m_watcher;
QString m_directory;
};

View File

@ -1,5 +1,4 @@
#include "LegacyUpgradeTask.h" #include "LegacyUpgradeTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "NullInstance.h" #include "NullInstance.h"

View File

@ -11,8 +11,6 @@
#include "BaseInstance.h" #include "BaseInstance.h"
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public InstanceTask class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public InstanceTask
{ {
Q_OBJECT Q_OBJECT

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "InstanceTask.h" #include "InstanceTask.h"
#include "BaseInstanceProvider.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "quazip.h" #include "quazip.h"
#include "quazipdir.h" #include "quazipdir.h"

View File

@ -1490,7 +1490,7 @@ void MainWindow::on_actionViewInstanceFolder_triggered()
void MainWindow::refreshInstances() void MainWindow::refreshInstances()
{ {
MMC->instances()->loadList(true); MMC->instances()->loadList();
} }
void MainWindow::on_actionViewCentralModsFolder_triggered() void MainWindow::on_actionViewCentralModsFolder_triggered()
@ -1524,7 +1524,7 @@ void MainWindow::on_actionSettings_triggered()
{ {
SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "global-settings"); SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "global-settings");
// FIXME: quick HACK to make this work. improve, optimize. // FIXME: quick HACK to make this work. improve, optimize.
MMC->instances()->loadList(true); MMC->instances()->loadList();
proxymodel->invalidate(); proxymodel->invalidate();
proxymodel->sort(0); proxymodel->sort(0);
updateToolsMenu(); updateToolsMenu();

View File

@ -602,7 +602,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
connect(InstDirSetting.get(), &Setting::SettingChanged, m_instanceFolder, &FolderInstanceProvider::on_InstFolderChanged); connect(InstDirSetting.get(), &Setting::SettingChanged, m_instanceFolder, &FolderInstanceProvider::on_InstFolderChanged);
m_instances->addInstanceProvider(m_instanceFolder); m_instances->addInstanceProvider(m_instanceFolder);
qDebug() << "Loading Instances..."; qDebug() << "Loading Instances...";
m_instances->loadList(true); m_instances->loadList();
qDebug() << "<> Instances loaded."; qDebug() << "<> Instances loaded.";
} }