From 31378fff844669fe1ed3c0a8ff5de1c28e5f87e2 Mon Sep 17 00:00:00 2001 From: APTX Date: Sat, 13 Apr 2013 14:35:55 +0200 Subject: [PATCH] Base structure for dynamic model --- localmylist/database.cpp | 16 +- localmylist/dynamicmodel/data.cpp | 53 +++++ localmylist/dynamicmodel/data.h | 52 +++++ localmylist/dynamicmodel/datamanager.cpp | 36 +++ localmylist/dynamicmodel/datamanager.h | 46 ++++ localmylist/dynamicmodel/datatype.cpp | 55 +++++ localmylist/dynamicmodel/datatype.h | 52 +++++ .../dynamicmodel/dynamicmodel_global.h | 22 ++ localmylist/dynamicmodel/model.cpp | 154 +++++++++++++ localmylist/dynamicmodel/model.h | 47 ++++ localmylist/dynamicmodel/node.cpp | 207 ++++++++++++++++++ localmylist/dynamicmodel/node.h | 56 +++++ localmylist/dynamicmodel/types.cpp | 7 + localmylist/dynamicmodel/types.h | 10 + localmylist/localmylist.pro | 21 +- 15 files changed, 822 insertions(+), 12 deletions(-) create mode 100644 localmylist/dynamicmodel/data.cpp create mode 100644 localmylist/dynamicmodel/data.h create mode 100644 localmylist/dynamicmodel/datamanager.cpp create mode 100644 localmylist/dynamicmodel/datamanager.h create mode 100644 localmylist/dynamicmodel/datatype.cpp create mode 100644 localmylist/dynamicmodel/datatype.h create mode 100644 localmylist/dynamicmodel/dynamicmodel_global.h create mode 100644 localmylist/dynamicmodel/model.cpp create mode 100644 localmylist/dynamicmodel/model.h create mode 100644 localmylist/dynamicmodel/node.cpp create mode 100644 localmylist/dynamicmodel/node.h create mode 100644 localmylist/dynamicmodel/types.cpp create mode 100644 localmylist/dynamicmodel/types.h diff --git a/localmylist/database.cpp b/localmylist/database.cpp index 20c16e1..bfb476e 100644 --- a/localmylist/database.cpp +++ b/localmylist/database.cpp @@ -1587,6 +1587,14 @@ bool Database::connect() { d = new DatabaseInternal(); d->db = QSqlDatabase::addDatabase("QPSQL", connectionName); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + QObject::connect(d->db.driver(), SIGNAL(notification(QString,QSqlDriver::NotificationSource,QVariant)), + this, SLOT(handleNotification(QString,QSqlDriver::NotificationSource,QVariant))); +#else + QObject::connect(d->db.driver(), SIGNAL(notification(QString)), + this, SLOT(handleNotification(QString))); +#endif } else if (d->db.isOpen()) { @@ -1608,13 +1616,7 @@ bool Database::connect() qWarning() << "Failed opening database connection." << d->db.lastError(); return success; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(d->db.driver(), SIGNAL(notification(QString,QSqlDriver::NotificationSource,QVariant)), - this, SLOT(handleNotification(QString,QSqlDriver::NotificationSource,QVariant))); -#else - QObject::connect(d->db.driver(), SIGNAL(notification(QString)), - this, SLOT(handleNotification(QString))); -#endif + // Workaround for https://bugreports.qt-project.org/browse/QTBUG-30076 #if QT_VERSION <= QT_VERSION_CHECK(4, 8, 4) || (QT_VERSION > QT_VERSION_CHECK(5, 0, 0) && QT_VERSION <= QT_VERSION_CHECK(5, 0, 2)) diff --git a/localmylist/dynamicmodel/data.cpp b/localmylist/dynamicmodel/data.cpp new file mode 100644 index 0000000..4baf303 --- /dev/null +++ b/localmylist/dynamicmodel/data.cpp @@ -0,0 +1,53 @@ +#include "data.h" + +#include "node.h" +#include "datatype.h" + +namespace LocalMyList { +namespace DynamicModel { + +Data::Data(DataType *dataType) : m_type(dataType) +{ +} + +Data::~Data() +{ + Q_ASSERT(references.isEmpty()); +} + +QVariant Data::data(int row, int role) const +{ + Q_UNUSED(row); + Q_UNUSED(role); + return QVariant(); +} + +void Data::ref(Node *node) +{ + Q_ASSERT(!references.contains(node)); + + references.append(node); +} + +void Data::deref(Node *node) +{ + Q_ASSERT(references.isEmpty()); + + bool removed = references.removeOne(node); + + Q_ASSERT_X(removed, "deref", "Removing node that was not referenced"); + + if (references.isEmpty()) + type()->released(this); +} + +void Data::updated() +{ + foreach (Node *node, references) + { + node->updated(UpdateOperation); + } +} + +} // namespace DynamicModel +} // namespace LocalMyList diff --git a/localmylist/dynamicmodel/data.h b/localmylist/dynamicmodel/data.h new file mode 100644 index 0000000..cd58619 --- /dev/null +++ b/localmylist/dynamicmodel/data.h @@ -0,0 +1,52 @@ +#ifndef DATA_H +#define DATA_H + +#include +#include + +#include "../databaseclasses.h" + +namespace LocalMyList { +namespace DynamicModel { + +class DataType; +class Node; + +class Data +{ +public: + Data(DataType *dataType); + ~Data(); + + virtual int id() const = 0; + virtual QVariant data(int row, int role) const; + + DataType *type() const { return m_type; } + // Referencing + void ref(Node *node); + void deref(Node *node); + + void updated(); + +private: + QList references; + DataType * const m_type; +}; + +class AnimeData : public Data +{ +public: + AnimeData(DataType *dataType); + + int id() const; + QVariant data(int row, int role) const; + + Anime animeData; + int episodesInMyList; + int watchedEpisodes; +}; + +} // namespace DynamicModel +} // namespace LocalMyList + +#endif // DATA_H diff --git a/localmylist/dynamicmodel/datamanager.cpp b/localmylist/dynamicmodel/datamanager.cpp new file mode 100644 index 0000000..ff67d4c --- /dev/null +++ b/localmylist/dynamicmodel/datamanager.cpp @@ -0,0 +1,36 @@ +#include "datamanager.h" + +#include "datatype.h" + +namespace LocalMyList { +namespace DynamicModel { + +DataManager::DataManager(QObject *parent) : QObject(parent) +{ +} + +DataManager::~DataManager() +{ + qDeleteAll(dataTypes); +} + +bool DataManager::registerDataType(DataType *dataType) +{ + if (dataTypes.contains(dataType)) + return true; + + if (dataTypeNames.contains(dataType->name())) + return false; + + dataTypes.insert(dataType); + dataTypeNames.insert(dataType->name(), dataType); + return true; +} + +DataType *DataManager::dataType(const QString &name) const +{ + return dataTypeNames.value(name, 0); +} + +} // namespace DynamicModel +} // namespace LocalMyList diff --git a/localmylist/dynamicmodel/datamanager.h b/localmylist/dynamicmodel/datamanager.h new file mode 100644 index 0000000..7e96640 --- /dev/null +++ b/localmylist/dynamicmodel/datamanager.h @@ -0,0 +1,46 @@ +#ifndef DATAMANAGER_H +#define DATAMANAGER_H + +#include "dynamicmodel_global.h" + +#include +#include +#include + +namespace LocalMyList { +namespace DynamicModel { + +class DataType; + +class DataManager : public QObject +{ + Q_OBJECT + +public: + DataManager(QObject *parent = 0); + ~DataManager(); + + bool registerDataType(DataType *dataType); + + DataType *dataType(const QString &name) const; + +private slots: +/* void animeUpdate(int aid); + void episodeUpdate(int eid, int aid); + void fileUpdate(int fid, int eid, int aid); + void fileLocationUpdate(int id); + + void animeInsert(int aid); + void episodeInsert(int eid, int aid); + void fileInsert(int fid, int eid, int aid); + void fileLocationInsert(int locationId, int fid); +*/ +private: + QHash dataTypeNames; + QSet dataTypes; +}; + +} // namespace DynamicModel +} // namespace LocalMyList + +#endif // DATAMANAGER_H diff --git a/localmylist/dynamicmodel/datatype.cpp b/localmylist/dynamicmodel/datatype.cpp new file mode 100644 index 0000000..c069d78 --- /dev/null +++ b/localmylist/dynamicmodel/datatype.cpp @@ -0,0 +1,55 @@ +#include "datatype.h" + +#include + +#include "data.h" + +namespace LocalMyList { +namespace DynamicModel { + +DataType::DataType(QObject *parent) : QObject(parent) +{ +} + +QStringList DataType::availableChildRelations() const +{ + return QStringList(); +} + +Data *DataType::data(int key) const +{ + return m_dataStore.value(key, 0); +} + +bool DataType::canGetChildren(const QString &childTypeName) const +{ + Q_UNUSED(childTypeName); + return false; +} + +void DataType::update(Data *data) +{ + Q_UNUSED(data); +} + +void DataType::childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation) +{ + Q_UNUSED(parentData); + Q_UNUSED(oldData); + Q_UNUSED(newData); + Q_UNUSED(operation); +} + +void DataType::released(Data *data) +{ + Q_ASSERT(data != 0); + + bool removed = m_dataStore.remove(data->id()); + + Q_ASSERT_X(removed, "released", "releasing node not in data store"); + + delete data; +} + +} // namespace DynamicModel +} // namespace LocalMyList diff --git a/localmylist/dynamicmodel/datatype.h b/localmylist/dynamicmodel/datatype.h new file mode 100644 index 0000000..cea939c --- /dev/null +++ b/localmylist/dynamicmodel/datatype.h @@ -0,0 +1,52 @@ +#ifndef ABSTRACTDATATYPE_H +#define ABSTRACTDATATYPE_H + +#include "datamanager.h" + +#include +#include +#include +namespace LocalMyList { +namespace DynamicModel { + +class Data; +class Node; + +typedef bool (*NodeCompare)(Node *a, Node *b); + +class DataType : public QObject +{ + Q_OBJECT +public: + DataType(QObject *parent = 0); + + virtual QString name() const = 0; + virtual QStringList availableChildRelations() const; + + virtual QString baseQuery() const = 0; + + Data *data(int key) const; + virtual int size() const = 0; + // InternalData *internalData(int key) const; + + // Acquire + virtual bool canGetChildren(const QString &childTypeName) const; + virtual void getChildren(const Data *parent, const QString &childTypeName, int offset) = 0; + + // Update + virtual void update(Data *data); + virtual void childUpdate(Data *parentData, const Data *oldData, const Data *newData, Operation operation); + + // Release + void released(Data *data); + + virtual NodeCompare nodeCompareFunction() const = 0; + +private: + QHash m_dataStore; +}; + +} // namespace DynamicModel +} // namespace LocalMyList + +#endif // ABSTRACTDATATYPE_H diff --git a/localmylist/dynamicmodel/dynamicmodel_global.h b/localmylist/dynamicmodel/dynamicmodel_global.h new file mode 100644 index 0000000..757a847 --- /dev/null +++ b/localmylist/dynamicmodel/dynamicmodel_global.h @@ -0,0 +1,22 @@ +#ifndef DYNAMICMODEL_GLOBAL_H +#define DYNAMICMODEL_GLOBAL_H + +namespace LocalMyList { +namespace DynamicModel { + +enum Operation { + UpdateOperation, + InsertOperation, + DeleteOperation +}; + +enum MoveType { + NoMove, + SuccessfulMove, + OutOfBoundsMove +}; + +} // namespace DynamicModel +} // namespace LocalMyList + +#endif // DYNAMICMODEL_GLOBAL_H diff --git a/localmylist/dynamicmodel/model.cpp b/localmylist/dynamicmodel/model.cpp new file mode 100644 index 0000000..711182c --- /dev/null +++ b/localmylist/dynamicmodel/model.cpp @@ -0,0 +1,154 @@ +#include "model.h" + +#include "node.h" + +namespace LocalMyList { +namespace DynamicModel { + +Model::Model(QObject *parent) : + QAbstractItemModel(parent) +{ +} + +Model::~Model() +{ + delete rootItem; +} + +QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + return rootItem->data(section, role); + + return QVariant(); +} + +Qt::ItemFlags Model::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant Model::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + Node *item = static_cast(index.internalPointer()); + + return item->data(index.column(), role); +} + +QModelIndex Model::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + Node *parentItem; + + if (!parent.isValid()) + parentItem = rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + Node *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex Model::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + Node *childItem = static_cast(index.internalPointer()); + Node *parentItem = childItem->parent(); + + if (parentItem == rootItem) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +int Model::rowCount(const QModelIndex &parent) const +{ + Node *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + return parentItem->childCount(); +} + +int Model::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return static_cast(parent.internalPointer())->columnCount(); + else + return rootItem->columnCount(); +} + +bool Model::canFetchMore(const QModelIndex &parent) const +{ + Node *parentItem; + if (parent.isValid()) + parentItem = static_cast(parent.internalPointer()); + else + parentItem = rootItem; + + return parentItem->canFetchMore(); +} + +void Model::fetchMore(const QModelIndex &parent) +{ + Node *node; + + if (parent.isValid()) + node = static_cast(parent.internalPointer()); + else + node = rootItem; + + node->fetchMore(); +} + +bool Model::hasChildren(const QModelIndex &parent) const +{ + if (parent.isValid()) + return static_cast(parent.internalPointer())->hasChildren(); + else + return rootItem->hasChildren(); +} + +Node *Model::node(const QModelIndex &idx) const +{ + if (!idx.isValid()) + return 0; + return static_cast(idx.internalPointer()); +} + +QModelIndex Model::index(Node *node) const +{ + if (!node || node == rootItem) + return QModelIndex(); + return createIndex(node->row(), 0, node); +} + +void Model::reload() +{ + beginResetModel(); + delete rootItem; + rootItem = new Node(this, 0, 0, 0); + endResetModel(); +} + + +} // namespace DynamicModel +} // namespace Local diff --git a/localmylist/dynamicmodel/model.h b/localmylist/dynamicmodel/model.h new file mode 100644 index 0000000..9305014 --- /dev/null +++ b/localmylist/dynamicmodel/model.h @@ -0,0 +1,47 @@ +#ifndef MODEL_H +#define MODEL_H + +#include + +namespace LocalMyList { +namespace DynamicModel { + +class Node; + +class Model : public QAbstractItemModel +{ + Q_OBJECT + friend class Node; + +public: + explicit Model(QObject *parent = 0); + ~Model(); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + // Lazy loading + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + Node *node(const QModelIndex &idx) const; + QModelIndex index(Node *node) const; + +public slots: + void reload(); + +private: + Node *rootItem; +}; + +} // namespace DynamicModel +} // namespace LocalMyList + +#endif // MODEL_H diff --git a/localmylist/dynamicmodel/node.cpp b/localmylist/dynamicmodel/node.cpp new file mode 100644 index 0000000..146d37d --- /dev/null +++ b/localmylist/dynamicmodel/node.cpp @@ -0,0 +1,207 @@ +#include "node.h" +#include "datatype.h" + +#include "dynamicmodel_global.h" +#include "data.h" +#include "model.h" +#include + +#include + +namespace LocalMyList { +namespace DynamicModel { + +Node::Node(Model *model, Node *parent, int totalRowCount, Data *data) + : m_model(model), m_parent(parent), m_totalRowCount(totalRowCount), + m_data(data) +{ + m_data->ref(this); +} + +Node::~Node() +{ + m_data->deref(this); + qDeleteAll(m_children); +} + +Node *Node::parent() const +{ + return m_parent; +} + +Node *Node::child(int row) const +{ + return m_children.value(row); +} + +int Node::childCount() const +{ + return m_children.count(); +} + +int Node::columnCount() const +{ + return 5; +} + +int Node::row() const +{ + if (m_parent) + return m_parent->m_children.indexOf(const_cast(this)); + + return 0; +} + +bool Node::hasChildren() const +{ + return totalRowCount() > 0; +} + +QVariant Node::data(int column, int role) const +{ + if (parent()) + return m_data->data(column, role); + + if (role != Qt::DisplayRole) + return QVariant(); + + switch (column) + { + case 0: + return QObject::tr("Title"); + case 1: + return QObject::tr("Episode / Version"); + case 2: + return QObject::tr("Rating / Quality"); + case 3: + return QObject::tr("Vote"); + case 4: + return QObject::tr("Watched / Renamed"); + } + + return QVariant(); +} + +Data *Node::data() const +{ + return m_data; +} + +int Node::totalRowCount() const +{ + return m_totalRowCount; +} + +bool Node::canFetchMore() const +{ + if (childCount() < totalRowCount()) + return true; + return false; +} + +void Node::fetchMore() +{ + m_data->type()->getChildren(m_data, m_childType->name(), childCount()); +} + +void Node::fetchComplete() +{ + +} + +MoveType Node::moveChild(Node *child, Operation type) +{ + const QModelIndex idx = m_model->index(this); + + if (type == InsertOperation) + { + auto it = std::upper_bound(m_children.begin(), m_children.end(), child, m_data->type()->nodeCompareFunction()); + + if (it == m_children.end() && canFetchMore()) + { + delete child; + return OutOfBoundsMove; + } + + const int newRow = qMax(0, (it - m_children.begin()) - 1); + + m_model->beginInsertRows(idx, newRow, newRow); + it = m_children.insert(it, child); + m_model->endInsertRows(); + return SuccessfulMove; + } + + const auto oldPos = std::find(m_children.begin(), m_children.end(), child); + const int oldRow = oldPos == m_children.end() ? -1 : oldPos - m_children.begin(); + + if (type == DeleteOperation) + { + if (oldRow != -1) + { + m_model->beginRemoveRows(idx, oldRow, oldRow); + m_children.removeAt(oldRow); + m_model->endRemoveRows(); + } + delete child; + return SuccessfulMove; + } + + const auto lower = std::upper_bound(m_children.begin(), oldPos, child, m_data->type()->nodeCompareFunction()); + const auto upper = std::lower_bound(oldPos, m_children.end(), child, m_data->type()->nodeCompareFunction()); + + // No move needed + if (lower == upper) + { + return NoMove; + } + + const auto it = (lower == oldPos) ? upper : lower; + + // Added item is not in the currently loaded data + if (it == m_children.end() && canFetchMore()) + { + m_model->beginRemoveRows(idx, oldRow, oldRow); + m_children.removeAt(oldRow); + m_model->endRemoveRows(); + delete child; + + return OutOfBoundsMove; + } + + const int nextRow = it - m_children.begin(); + const int newRow = qMax(0, nextRow - 1); + + if (oldRow < newRow) + { + m_model->beginMoveRows(idx, oldRow, oldRow, idx, nextRow); + m_children.move(oldRow, newRow); + m_model->endMoveRows(); + } + else + { + m_model->beginMoveRows(idx, oldRow, oldRow, idx, newRow); + m_children.move(oldRow, nextRow); + m_model->endMoveRows(); + } + + return SuccessfulMove; +} + +int Node::id() const +{ + return m_data->id(); +} + +void Node::childAdded(int id) +{ + qDebug() << "childAdded" << id; +} + +bool Node::updated(Operation type) +{ + Q_UNUSED(type) + return false; +} + +} // namespace DynamicModel +} // namespace LocalMyList diff --git a/localmylist/dynamicmodel/node.h b/localmylist/dynamicmodel/node.h new file mode 100644 index 0000000..ddf295b --- /dev/null +++ b/localmylist/dynamicmodel/node.h @@ -0,0 +1,56 @@ +#ifndef NODE_H +#define NODE_H + +#include "dynamicmodel_global.h" + +#include + +namespace LocalMyList { +namespace DynamicModel { + +class Model; +class Data; +class DataType; + +class Node { +public: + Node(Model *model, Node *parent, int totalRowCount, Data *data); + ~Node(); + + // Structure + Node *parent() const; + Node *child(int row) const; + int childCount() const; + int columnCount() const; + int row() const; + bool hasChildren() const; + + // Data + int id() const; + QVariant data(int column, int role) const; + Data *data() const; + int totalRowCount() const; + + bool canFetchMore() const; + void fetchMore(); + void fetchComplete(); + + // Changes + void childAdded(int id); + bool updated(Operation type); + MoveType moveChild(Node *child, Operation type); + +protected: + Node *m_parent; + QList m_children; + int m_totalRowCount; + Model *m_model; + + Data *m_data; + DataType *m_childType; +}; + +} // namespace DynamicModel +} // namespace LocalMyList + +#endif // NODE_H diff --git a/localmylist/dynamicmodel/types.cpp b/localmylist/dynamicmodel/types.cpp new file mode 100644 index 0000000..5ca5b78 --- /dev/null +++ b/localmylist/dynamicmodel/types.cpp @@ -0,0 +1,7 @@ +#include "types.h" + +namespace LocalMyList { +namespace DynamicModel { + +} // namespace DynamicModel +} // namespace LocalMyList diff --git a/localmylist/dynamicmodel/types.h b/localmylist/dynamicmodel/types.h new file mode 100644 index 0000000..147f89a --- /dev/null +++ b/localmylist/dynamicmodel/types.h @@ -0,0 +1,10 @@ +#ifndef TYPES_H +#define TYPES_H + +namespace LocalMyList { +namespace DynamicModel { + +} // namespace DynamicModel +} // namespace LocalMyList + +#endif // TYPES_H diff --git a/localmylist/localmylist.pro b/localmylist/localmylist.pro index 030b99a..423a15d 100644 --- a/localmylist/localmylist.pro +++ b/localmylist/localmylist.pro @@ -31,9 +31,15 @@ SOURCES += \ sqlquery.cpp \ sqlasyncquery.cpp \ sqlasyncqueryinternal.cpp \ - asyncquerytask.cpp \ filelocationchecktask.cpp \ - messagehandler.cpp + messagehandler.cpp \ + asyncquerytask.cpp \ + dynamicmodel/datamanager.cpp \ + dynamicmodel/data.cpp \ + dynamicmodel/node.cpp \ + dynamicmodel/model.cpp \ + dynamicmodel/datatype.cpp \ + dynamicmodel/types.cpp HEADERS += \ localmylist_global.h \ @@ -60,9 +66,13 @@ HEADERS += \ sqlasyncqueryinternal.h \ asyncquerytask.h \ sqlresultiteratorinterface.h \ - filelocationchecktask.h \ - messagehandler.h - + dynamicmodel/datamanager.h \ + dynamicmodel/data.h \ + dynamicmodel/node.h \ + dynamicmodel/model.h \ + dynamicmodel/datatype.h \ + dynamicmodel/dynamicmodel_global.h \ + dynamicmodel/types.h CONV_HEADERS += \ include/LocalMyList/AbstractTask \ include/LocalMyList/AddFileTask \ @@ -77,6 +87,7 @@ CONV_HEADERS += \ include/LocalMyList/MyListNode \ include/LocalMyList/Settings \ include/LocalMyList/UnknownFileLookupTask \ + include/LocalMyList/UnknownFileLookupTask \ include/LocalMyList/FileLocationCheckTask \ include/LocalMyList/RequestHandler \ include/LocalMyList/DirectoryWatcher -- 2.52.0