initial commit
This commit is contained in:
commit
4b60ece582
327 changed files with 28286 additions and 0 deletions
35
lib/wController/CMakeLists.txt
Normal file
35
lib/wController/CMakeLists.txt
Normal file
|
@ -0,0 +1,35 @@
|
|||
cmake_minimum_required(VERSION 2.8.12)
|
||||
project(controller)
|
||||
|
||||
find_package(Qt5Core REQUIRED)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
set(HEADERS
|
||||
controller.h
|
||||
controllerstring.h
|
||||
list.h
|
||||
vocabulary.h
|
||||
attributes.h
|
||||
catalogue.h
|
||||
collection.h
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
controller.cpp
|
||||
controllerstring.cpp
|
||||
list.cpp
|
||||
vocabulary.cpp
|
||||
attributes.cpp
|
||||
catalogue.cpp
|
||||
collection.cpp
|
||||
)
|
||||
|
||||
add_library(wController STATIC ${HEADERS} ${SOURCES})
|
||||
|
||||
target_link_libraries(wController Qt5::Core)
|
||||
target_link_libraries(wController wSocket)
|
||||
target_link_libraries(wController wDispatcher)
|
||||
target_link_libraries(wController wType)
|
||||
|
87
lib/wController/attributes.cpp
Normal file
87
lib/wController/attributes.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
#include "attributes.h"
|
||||
|
||||
uint64_t C::Attributes::counter = 0;
|
||||
|
||||
C::Attributes::Attributes(const W::Address& p_address, QObject* parent):
|
||||
C::Vocabulary(p_address, W::Address({W::String(u"attributes") += counter++}), parent),
|
||||
attributes(new Map()),
|
||||
reversed(new RMap())
|
||||
{
|
||||
}
|
||||
|
||||
C::Attributes::~Attributes()
|
||||
{
|
||||
delete attributes;
|
||||
delete reversed;
|
||||
}
|
||||
|
||||
void C::Attributes::_newElement(const W::String& key, const W::Object& element)
|
||||
{
|
||||
const W::Vocabulary& evc = static_cast<const W::Vocabulary&>(element);
|
||||
const W::Uint64& type = static_cast<const W::Uint64&>(evc.at(u"type"));
|
||||
const W::Address& addr = static_cast<const W::Address&>(evc.at(u"address"));
|
||||
|
||||
C::Controller* child = C::Controller::createByType(type, addr);
|
||||
attributes->insert(std::make_pair(key, child));
|
||||
reversed->insert(std::make_pair(child, key));
|
||||
addController(child);
|
||||
connect(child, SIGNAL(modification(const W::Object&)), SLOT(onAttrModification(const W::Object&)));
|
||||
|
||||
C::Vocabulary::_newElement(key, element);
|
||||
}
|
||||
|
||||
void C::Attributes::_removeElement(const W::String& key)
|
||||
{
|
||||
C::Vocabulary::_removeElement(key);
|
||||
|
||||
Map::iterator itr = attributes->find(key);
|
||||
C::Controller* ctrl = itr->second;
|
||||
ctrl->setProperty("name", QString::fromStdString(key.toString()));
|
||||
RMap::iterator ritr = reversed->find(ctrl);
|
||||
|
||||
removeController(ctrl);
|
||||
attributes->erase(itr);
|
||||
reversed->erase(ritr);
|
||||
delete ctrl;
|
||||
}
|
||||
|
||||
void C::Attributes::_clear()
|
||||
{
|
||||
C::Vocabulary::_clear();
|
||||
|
||||
Map::iterator itr = attributes->begin();
|
||||
Map::iterator end = attributes->end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
removeController(itr->second);
|
||||
delete itr->second;
|
||||
}
|
||||
|
||||
attributes->clear();
|
||||
reversed->clear();
|
||||
}
|
||||
|
||||
|
||||
void C::Attributes::onAttrModification(const W::Object& data)
|
||||
{
|
||||
C::Controller* ctrl = static_cast<C::Controller*>(sender());
|
||||
|
||||
RMap::iterator ritr = reversed->find(ctrl);
|
||||
|
||||
emit attributeChange(ritr->second, data);
|
||||
}
|
||||
|
||||
void C::Attributes::unsubscribe()
|
||||
{
|
||||
C::Controller::unsubscribe();
|
||||
|
||||
_clear();
|
||||
}
|
||||
|
||||
void C::Attributes::onSocketDisconnected()
|
||||
{
|
||||
C::Controller::onSocketDisconnected();
|
||||
|
||||
dropSubscribed();
|
||||
_clear();
|
||||
}
|
43
lib/wController/attributes.h
Normal file
43
lib/wController/attributes.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef ATTRIBUTES_H
|
||||
#define ATTRIBUTES_H
|
||||
|
||||
#include "vocabulary.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <wType/uint64.h>
|
||||
|
||||
namespace C {
|
||||
class Attributes : public C::Vocabulary
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Attributes(const W::Address& p_address, QObject* parent = 0);
|
||||
~Attributes();
|
||||
|
||||
void unsubscribe();
|
||||
|
||||
signals:
|
||||
void attributeChange(const W::String& atteName, const W::Object& value);
|
||||
|
||||
protected:
|
||||
void _newElement(const W::String & key, const W::Object & element) override;
|
||||
void _removeElement(const W::String & key) override;
|
||||
void _clear() override;
|
||||
|
||||
protected slots:
|
||||
void onAttrModification(const W::Object& data);
|
||||
void onSocketDisconnected() override;
|
||||
|
||||
private:
|
||||
typedef std::map<W::String, C::Controller*> Map;
|
||||
typedef std::map<C::Controller*, W::String> RMap;
|
||||
|
||||
static uint64_t counter;
|
||||
|
||||
Map* attributes;
|
||||
RMap* reversed;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ATTRIBUTES_H
|
220
lib/wController/catalogue.cpp
Normal file
220
lib/wController/catalogue.cpp
Normal file
|
@ -0,0 +1,220 @@
|
|||
#include "catalogue.h"
|
||||
|
||||
uint64_t C::Catalogue::counter = 0;
|
||||
|
||||
C::Catalogue::Catalogue(const W::Address p_address, QObject* parent):
|
||||
C::Controller(p_address, W::Address({W::String(u"catalogue") += counter++}), parent),
|
||||
order(),
|
||||
hasSorting(false),
|
||||
hasFilter(false),
|
||||
hasData(true),
|
||||
sorting(0),
|
||||
filter(0)
|
||||
{
|
||||
W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &C::Catalogue::_h_get);
|
||||
W::Handler* addElement = W::Handler::create(address + W::Address({u"addElement"}), this, &C::Catalogue::_h_addElement);
|
||||
W::Handler* removeElement = W::Handler::create(address + W::Address({u"removeElement"}), this, &C::Catalogue::_h_removeElement);
|
||||
W::Handler* moveElement = W::Handler::create(address + W::Address({u"moveElement"}), this, &C::Catalogue::_h_moveElement);
|
||||
W::Handler* clear = W::Handler::create(address + W::Address({u"clear"}), this, &C::Catalogue::_h_clear);
|
||||
|
||||
addHandler(get);
|
||||
addHandler(addElement);
|
||||
addHandler(removeElement);
|
||||
addHandler(moveElement);
|
||||
addHandler(clear);
|
||||
}
|
||||
|
||||
C::Catalogue::~Catalogue()
|
||||
{
|
||||
if (hasFilter) {
|
||||
delete filter;
|
||||
}
|
||||
|
||||
if (hasSorting) {
|
||||
delete sorting;
|
||||
}
|
||||
}
|
||||
|
||||
void C::Catalogue::setSorting(const W::String& field, bool ascending)
|
||||
{
|
||||
if (!hasSorting) {
|
||||
sorting = new W::Vocabulary();
|
||||
hasSorting = true;
|
||||
}
|
||||
sorting->insert(u"field", field);
|
||||
sorting->insert(u"ascending", W::Boolean(ascending));
|
||||
|
||||
if (hasData) {
|
||||
clearCatalogue();
|
||||
}
|
||||
|
||||
if (subscribed) {
|
||||
getData();
|
||||
}
|
||||
}
|
||||
|
||||
void C::Catalogue::clearSorting()
|
||||
{
|
||||
if (hasSorting) {
|
||||
delete sorting;
|
||||
hasSorting = false;
|
||||
|
||||
if (hasData) {
|
||||
clearCatalogue();
|
||||
}
|
||||
|
||||
if (subscribed) {
|
||||
getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void C::Catalogue::addElement(const W::Uint64& id, const W::Uint64& before)
|
||||
{
|
||||
if (before == 0) {
|
||||
order.push_back(id);
|
||||
} else {
|
||||
W::Order<uint64_t>::const_iterator pos = order.find(before);
|
||||
order.insert(pos, id);
|
||||
}
|
||||
|
||||
emit addedElement(id, before);
|
||||
}
|
||||
|
||||
void C::Catalogue::h_get(const W::Event& ev)
|
||||
{
|
||||
if (hasData) {
|
||||
clearCatalogue();
|
||||
}
|
||||
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
const W::Vector& ord = static_cast<const W::Vector&>(vc.at(u"data"));
|
||||
|
||||
W::Vector::size_type size = ord.length();
|
||||
for (uint64_t i = 0; i < size; ++i) {
|
||||
const W::Uint64& id = static_cast<const W::Uint64&>(ord.at(i));
|
||||
addElement(id);
|
||||
}
|
||||
hasData = true;
|
||||
|
||||
emit data();
|
||||
}
|
||||
|
||||
void C::Catalogue::h_addElement(const W::Event& ev)
|
||||
{
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
const W::Uint64& id = static_cast<const W::Uint64&>(vc.at(u"id"));
|
||||
if (vc.has(u"before")) {
|
||||
const W::Uint64& before = static_cast<const W::Uint64&>(vc.at(u"before"));
|
||||
|
||||
addElement(id, before);
|
||||
} else {
|
||||
addElement(id);
|
||||
}
|
||||
}
|
||||
|
||||
void C::Catalogue::clearCatalogue()
|
||||
{
|
||||
order.clear();
|
||||
hasData = false;
|
||||
emit clear();
|
||||
}
|
||||
|
||||
|
||||
void C::Catalogue::h_clear(const W::Event& ev)
|
||||
{
|
||||
clearCatalogue();
|
||||
}
|
||||
|
||||
void C::Catalogue::h_removeElement(const W::Event& ev)
|
||||
{
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
const W::Uint64& id = static_cast<const W::Uint64&>(vc.at(u"id"));
|
||||
|
||||
removeElement(id);
|
||||
}
|
||||
|
||||
void C::Catalogue::removeElement(const W::Uint64& id)
|
||||
{
|
||||
W::Order<uint64_t>::const_iterator pos = order.find(id);
|
||||
if (pos == order.end()) {
|
||||
emit serviceMessage(QString("Recieved event to remove element with id ") + id.toString().c_str() + " but element under such id isn't present in catalogue, skipping");
|
||||
return;
|
||||
}
|
||||
order.erase(id);
|
||||
|
||||
uint64_t pid;
|
||||
emit removedElement(pid);
|
||||
}
|
||||
|
||||
W::Vocabulary * C::Catalogue::createSubscriptionVC() const
|
||||
{
|
||||
W::Vocabulary* vc = C::Controller::createSubscriptionVC();
|
||||
|
||||
if (hasSorting) {
|
||||
vc->insert(u"sorting", sorting->copy());
|
||||
}
|
||||
|
||||
if (hasFilter) {
|
||||
vc->insert(u"filter", filter->copy());
|
||||
}
|
||||
|
||||
return vc;
|
||||
}
|
||||
|
||||
void C::Catalogue::h_moveElement(const W::Event& ev)
|
||||
{
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
const W::Uint64& id = static_cast<const W::Uint64&>(vc.at(u"id"));
|
||||
|
||||
W::Order<uint64_t>::const_iterator pos = order.find(id);
|
||||
if (pos == order.end()) {
|
||||
emit serviceMessage(QString("Recieved event to move element with id ") + id.toString().c_str() + " but element under such id isn't present in catalogue, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
order.erase(id);
|
||||
if (vc.has(u"before")) {
|
||||
const W::Uint64& before = static_cast<const W::Uint64&>(vc.at(u"before"));
|
||||
|
||||
W::Order<uint64_t>::const_iterator beforePosition = order.find(before);
|
||||
if (beforePosition == order.end()) {
|
||||
emit serviceMessage(QString("Recieved event to move element with id ") +
|
||||
id.toString().c_str() + " before element with id " + before.toString().c_str() +
|
||||
" but element under id " + before.toString().c_str() +
|
||||
" isn't present in catalogue, inserting to the end");
|
||||
|
||||
order.push_back(id);
|
||||
emit movedElement(id);
|
||||
|
||||
return;
|
||||
}
|
||||
order.insert(beforePosition, id);
|
||||
emit movedElement(id, before);
|
||||
|
||||
} else {
|
||||
order.push_back(id);
|
||||
emit movedElement(id);
|
||||
}
|
||||
}
|
||||
|
||||
void C::Catalogue::getData()
|
||||
{
|
||||
W::Vocabulary* vc = new W::Vocabulary();
|
||||
vc->insert(u"params", createSubscriptionVC());
|
||||
send(vc, W::Address{u"get"});
|
||||
}
|
||||
|
||||
|
||||
void C::Catalogue::addRemoteElement(const W::Vocabulary& element) const
|
||||
{
|
||||
send(static_cast<W::Vocabulary*>(element.copy()), W::Address{u"add"});
|
||||
}
|
||||
|
||||
void C::Catalogue::updateRemoteElement(const W::Uint64& id, const W::Vocabulary& newValue) const
|
||||
{
|
||||
W::Vocabulary* vc = new W::Vocabulary();
|
||||
vc->insert(u"id", id);
|
||||
vc->insert(u"value", newValue);
|
||||
send(vc, W::Address{u"update"});
|
||||
}
|
63
lib/wController/catalogue.h
Normal file
63
lib/wController/catalogue.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
#ifndef CATALOGUE_H
|
||||
#define CATALOGUE_H
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
#include "controller.h"
|
||||
|
||||
#include <wType/address.h>
|
||||
#include <wType/string.h>
|
||||
#include <wType/vocabulary.h>
|
||||
#include <wType/boolean.h>
|
||||
|
||||
#include <wContainer/order.h>
|
||||
|
||||
namespace C {
|
||||
class Catalogue : public Controller {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Catalogue(const W::Address p_address, QObject* parent);
|
||||
~Catalogue();
|
||||
|
||||
void setSorting(const W::String& field, bool ascending = true);
|
||||
void clearSorting();
|
||||
|
||||
void addRemoteElement(const W::Vocabulary& element) const;
|
||||
void updateRemoteElement(const W::Uint64& id, const W::Vocabulary& newValue) const;
|
||||
|
||||
signals:
|
||||
void addedElement(uint64_t id, uint64_t before = 0);
|
||||
void movedElement(uint64_t id, uint64_t before = 0);
|
||||
void removedElement(uint64_t id);
|
||||
void clear();
|
||||
void data();
|
||||
|
||||
protected:
|
||||
handler(get)
|
||||
handler(addElement)
|
||||
handler(removeElement)
|
||||
handler(moveElement)
|
||||
handler(clear)
|
||||
|
||||
virtual void addElement(const W::Uint64& id, const W::Uint64& before = W::Uint64(0));
|
||||
virtual void clearCatalogue();
|
||||
virtual void removeElement(const W::Uint64& id);
|
||||
virtual void getData();
|
||||
W::Vocabulary* createSubscriptionVC() const override;
|
||||
|
||||
protected:
|
||||
W::Order<uint64_t> order;
|
||||
|
||||
private:
|
||||
bool hasSorting;
|
||||
bool hasFilter;
|
||||
bool hasData;
|
||||
W::Vocabulary* sorting;
|
||||
W::Vocabulary* filter;
|
||||
|
||||
static uint64_t counter;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // CATALOGUE_H
|
136
lib/wController/collection.cpp
Normal file
136
lib/wController/collection.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "collection.h"
|
||||
|
||||
C::Collection::Collection(const W::Address p_address, QObject* parent):
|
||||
C::Catalogue(p_address, parent),
|
||||
elements(),
|
||||
waitingElements(),
|
||||
hasData(false)
|
||||
{
|
||||
}
|
||||
|
||||
C::Collection::~Collection()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void C::Collection::addChildVocabulary(const W::Uint64& id)
|
||||
{
|
||||
C::Vocabulary* ctrl = new C::Vocabulary(pairAddress + id);
|
||||
elements.insert(std::make_pair(id, ctrl));
|
||||
waitingElements.insert(ctrl);
|
||||
addController(ctrl);
|
||||
|
||||
connect(ctrl, SIGNAL(data()), this, SLOT(onChildVCData()));
|
||||
|
||||
if (hasData) {
|
||||
hasData = false;
|
||||
}
|
||||
}
|
||||
|
||||
void C::Collection::addElement(const W::Uint64& id, const W::Uint64& before)
|
||||
{
|
||||
C::Catalogue::addElement(id, before);
|
||||
addChildVocabulary(id);
|
||||
}
|
||||
|
||||
void C::Collection::clearCatalogue()
|
||||
{
|
||||
C::Catalogue::clearCatalogue();
|
||||
|
||||
std::set<C::Vocabulary*>::const_iterator itr = waitingElements.begin();
|
||||
std::set<C::Vocabulary*>::const_iterator end = waitingElements.end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
disconnect(*itr, SIGNAL(data()), this, SLOT(onChildVCData()));
|
||||
}
|
||||
|
||||
elements.clear();
|
||||
waitingElements.clear();
|
||||
cleanChildren();
|
||||
}
|
||||
|
||||
void C::Collection::removeElement(const W::Uint64& id)
|
||||
{
|
||||
C::Catalogue::removeElement(id);
|
||||
|
||||
Elements::const_iterator itr = elements.find(id);
|
||||
C::Vocabulary* ctrl = itr->second;
|
||||
|
||||
removeController(ctrl);
|
||||
elements.erase(itr);
|
||||
|
||||
if (!hasData) {
|
||||
std::set<C::Vocabulary*>::const_iterator witr = waitingElements.find(ctrl);
|
||||
if (witr != waitingElements.end()) {
|
||||
disconnect(ctrl, SIGNAL(data()), this, SLOT(onChildVCData()));
|
||||
waitingElements.erase(witr);
|
||||
|
||||
if (waitingElements.size() == 0) {
|
||||
hasData = true;
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
}
|
||||
delete ctrl;
|
||||
}
|
||||
|
||||
void C::Collection::h_get(const W::Event& ev)
|
||||
{
|
||||
hasData = false;
|
||||
C::Catalogue::h_get(ev);
|
||||
|
||||
if (waitingElements.size() == 0) {
|
||||
hasData = true;
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
|
||||
void C::Collection::h_clear(const W::Event& ev)
|
||||
{
|
||||
C::Catalogue::h_clear(ev);
|
||||
if (!hasData) {
|
||||
hasData = true;
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void C::Collection::onChildVCData()
|
||||
{
|
||||
C::Vocabulary* child = static_cast<C::Vocabulary*>(sender());
|
||||
|
||||
std::set<C::Vocabulary*>::const_iterator itr = waitingElements.find(child);
|
||||
waitingElements.erase(itr);
|
||||
|
||||
disconnect(child, SIGNAL(data()), this, SLOT(onChildVCData()));
|
||||
|
||||
if (waitingElements.size() == 0) {
|
||||
hasData = true;
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
|
||||
std::set<uint64_t> C::Collection::find(const W::String& field, const W::Object& value)
|
||||
{
|
||||
if (!hasData) {
|
||||
emit serviceMessage(QString("An attempt to look for record where ") + field.toString().c_str() + " == " + value.toString().c_str() + " in " + address.toString().c_str() + " but controller has no data yet");
|
||||
throw 2;
|
||||
}
|
||||
|
||||
std::set<uint64_t> response;
|
||||
Elements::const_iterator itr = elements.begin();
|
||||
Elements::const_iterator end = elements.end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
if (itr->second->at(field) == value) {
|
||||
response.insert(itr->first);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
const C::Vocabulary & C::Collection::get(uint64_t id) const
|
||||
{
|
||||
return *(elements.find(id)->second);
|
||||
}
|
52
lib/wController/collection.h
Normal file
52
lib/wController/collection.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef COLLECTION_H
|
||||
#define COLLECTION_H
|
||||
|
||||
#include "catalogue.h"
|
||||
#include "vocabulary.h"
|
||||
|
||||
#include <wType/address.h>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace C {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class Collection : public C::Catalogue {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Collection(const W::Address p_address, QObject* parent = 0);
|
||||
~Collection();
|
||||
|
||||
std::set<uint64_t> find(const W::String& field, const W::Object& value);
|
||||
const C::Vocabulary& get(uint64_t id) const;
|
||||
|
||||
signals:
|
||||
void ready(); //emits when every VC received their data;
|
||||
|
||||
protected:
|
||||
void addElement(const W::Uint64 & id, const W::Uint64 & before) override;
|
||||
void clearCatalogue() override;
|
||||
void removeElement(const W::Uint64 & id) override;
|
||||
|
||||
void h_get(const W::Event & ev) override;
|
||||
void h_clear(const W::Event & ev) override;
|
||||
|
||||
private:
|
||||
typedef std::map<uint64_t, C::Vocabulary*> Elements;
|
||||
Elements elements;
|
||||
std::set<C::Vocabulary*> waitingElements;
|
||||
bool hasData;
|
||||
|
||||
void addChildVocabulary(const W::Uint64& id);
|
||||
|
||||
private slots:
|
||||
void onChildVCData();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // COLLECTION_H
|
278
lib/wController/controller.cpp
Normal file
278
lib/wController/controller.cpp
Normal file
|
@ -0,0 +1,278 @@
|
|||
#include "controller.h"
|
||||
|
||||
#include "controllerstring.h"
|
||||
#include "list.h"
|
||||
#include "vocabulary.h"
|
||||
#include "attributes.h"
|
||||
#include "catalogue.h"
|
||||
|
||||
C::Controller::Controller(const W::Address& p_address, const W::Address& my_address, QObject* parent):
|
||||
QObject(parent),
|
||||
pairAddress(p_address),
|
||||
address(my_address),
|
||||
subscribed(false),
|
||||
dispatcher(0),
|
||||
socket(0),
|
||||
registered(false),
|
||||
controllers(new CList()),
|
||||
handlers(new HList()),
|
||||
properties(new W::Vector())
|
||||
{
|
||||
W::Handler* props = W::Handler::create(address + W::Address({u"properties"}), this, &C::Controller::_h_properties);
|
||||
addHandler(props);
|
||||
}
|
||||
|
||||
C::Controller::~Controller()
|
||||
{
|
||||
if (subscribed) {
|
||||
unsubscribe();
|
||||
}
|
||||
if (registered) {
|
||||
unregisterController();
|
||||
}
|
||||
|
||||
CList::iterator itr = controllers->begin();
|
||||
CList::iterator end = controllers->end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
delete *itr;
|
||||
}
|
||||
|
||||
HList::iterator hItr = handlers->begin();
|
||||
HList::iterator hEnd = handlers->end();
|
||||
|
||||
for (; hItr != hEnd; ++hItr) {
|
||||
delete *hItr;
|
||||
}
|
||||
|
||||
delete controllers;
|
||||
delete handlers;
|
||||
delete properties;
|
||||
}
|
||||
|
||||
void C::Controller::addController(C::Controller* ctrl)
|
||||
{
|
||||
controllers->push_back(ctrl);
|
||||
connect(ctrl, SIGNAL(serviceMessage(const QString&)), SIGNAL(serviceMessage(const QString&)));
|
||||
if (registered) {
|
||||
ctrl->registerController(dispatcher, socket);
|
||||
}
|
||||
if (subscribed && !ctrl->subscribed) {
|
||||
ctrl->subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
void C::Controller::addHandler(W::Handler* handler)
|
||||
{
|
||||
handlers->push_back(handler);
|
||||
if (registered) {
|
||||
dispatcher->registerHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
void C::Controller::removeHandler(W::Handler* handler)
|
||||
{
|
||||
handlers->erase(handler);
|
||||
if (registered) {
|
||||
dispatcher->unregisterHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
void C::Controller::removeController(C::Controller* ctrl)
|
||||
{
|
||||
if (subscribed && !ctrl->subscribed) {
|
||||
ctrl->unsubscribe();
|
||||
}
|
||||
if (registered) {
|
||||
ctrl->unregisterController();
|
||||
}
|
||||
disconnect(ctrl, SIGNAL(serviceMessage(const QString&)), this, SIGNAL(serviceMessage(const QString&)));
|
||||
controllers->erase(ctrl);
|
||||
}
|
||||
|
||||
|
||||
void C::Controller::h_properties(const W::Event& event)
|
||||
{
|
||||
delete properties;
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(event.getData());
|
||||
properties = static_cast<W::Vector*>(vc.at(u"properties").copy());
|
||||
|
||||
//emit serviceMessage("successfully received properties");
|
||||
}
|
||||
|
||||
void C::Controller::registerController(W::Dispatcher* dp, const W::Socket* sock)
|
||||
{
|
||||
if (registered) {
|
||||
emit serviceMessage(QString("Controller ") + address.toString().c_str() + " is already registered");
|
||||
throw 1;
|
||||
} else {
|
||||
dispatcher = dp;
|
||||
socket = sock;
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
|
||||
|
||||
CList::iterator itr = controllers->begin();
|
||||
CList::iterator end = controllers->end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
C::Controller* ctrl = *itr;
|
||||
ctrl->registerController(dispatcher, socket);
|
||||
}
|
||||
|
||||
HList::iterator hItr = handlers->begin();
|
||||
HList::iterator hEnd = handlers->end();
|
||||
|
||||
for (; hItr != hEnd; ++hItr) {
|
||||
W::Handler* handler = *hItr;
|
||||
dispatcher->registerHandler(handler);
|
||||
}
|
||||
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
void C::Controller::unregisterController()
|
||||
{
|
||||
if (!registered) {
|
||||
emit serviceMessage(QString("Controller ") + address.toString().c_str() + " is already unregistered");
|
||||
throw 2;
|
||||
} else {
|
||||
CList::iterator itr = controllers->begin();
|
||||
CList::iterator end = controllers->end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
Controller* ctrl = *itr;
|
||||
ctrl->unregisterController();
|
||||
}
|
||||
|
||||
HList::iterator hItr = handlers->begin();
|
||||
HList::iterator hEnd = handlers->end();
|
||||
|
||||
for (; hItr != hEnd; ++hItr) {
|
||||
W::Handler* handler = *hItr;
|
||||
dispatcher->unregisterHandler(handler);
|
||||
}
|
||||
|
||||
disconnect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
|
||||
dispatcher = 0;
|
||||
socket = 0;
|
||||
|
||||
registered = false;
|
||||
}
|
||||
}
|
||||
|
||||
void C::Controller::send(W::Vocabulary* vc, const W::Address& handlerAddress) const
|
||||
{
|
||||
if (!registered) {
|
||||
emit serviceMessage(QString("An attempt to send event from model ") + address.toString().c_str() + " which was not registered");
|
||||
throw 3;
|
||||
}
|
||||
vc->insert(u"source", address);
|
||||
W::Event ev(pairAddress + handlerAddress, vc);
|
||||
ev.setSenderId(socket->getId());
|
||||
socket->send(ev);
|
||||
}
|
||||
|
||||
void C::Controller::subscribe()
|
||||
{
|
||||
if (subscribed) {
|
||||
emit serviceMessage(QString("An attempt to subscribe model ") + address.toString().c_str() + " which is already subscribed");
|
||||
throw 3;
|
||||
}
|
||||
W::Vocabulary* vc = new W::Vocabulary();
|
||||
vc->insert(u"params", createSubscriptionVC());
|
||||
send(vc, W::Address{u"subscribe"});
|
||||
|
||||
CList::iterator itr = controllers->begin();
|
||||
CList::iterator end = controllers->end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
(*itr)->subscribe();
|
||||
}
|
||||
|
||||
subscribed = true;
|
||||
}
|
||||
|
||||
void C::Controller::unsubscribe()
|
||||
{
|
||||
if (!subscribed) {
|
||||
emit serviceMessage(QString("An attempt to unsubscribe model ") + address.toString().c_str() + " which not subscribed");
|
||||
throw 3;
|
||||
}
|
||||
send(new W::Vocabulary(), W::Address{u"unsubscribe"});
|
||||
|
||||
CList::iterator itr = controllers->begin();
|
||||
CList::iterator end = controllers->end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
(*itr)->unsubscribe();
|
||||
}
|
||||
|
||||
subscribed = false;
|
||||
}
|
||||
|
||||
void C::Controller::onSocketDisconnected()
|
||||
{
|
||||
subscribed = false;
|
||||
}
|
||||
|
||||
C::Controller * C::Controller::createByType(int type, const W::Address& address, QObject* parent)
|
||||
{
|
||||
C::Controller* ptr;
|
||||
switch (type) {
|
||||
case string:
|
||||
ptr = new C::String(address, parent);
|
||||
break;
|
||||
case list:
|
||||
ptr = new C::List(address, parent);
|
||||
break;
|
||||
case vocabulary:
|
||||
ptr = new C::Vocabulary(address, parent);
|
||||
break;
|
||||
case catalogue:
|
||||
ptr = new C::Catalogue(address, parent);
|
||||
break;
|
||||
|
||||
case attributes:
|
||||
ptr = new C::Attributes(address, parent);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw 1;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void C::Controller::dropSubscribed()
|
||||
{
|
||||
subscribed = false;
|
||||
CList::iterator itr = controllers->begin();
|
||||
CList::iterator end = controllers->end();
|
||||
|
||||
for (; itr != end; ++itr) {
|
||||
(*itr)->dropSubscribed();
|
||||
}
|
||||
}
|
||||
|
||||
W::Vocabulary * C::Controller::createSubscriptionVC() const
|
||||
{
|
||||
return new W::Vocabulary();
|
||||
}
|
||||
|
||||
void C::Controller::cleanChildren()
|
||||
{
|
||||
CList::const_iterator beg = controllers->begin();
|
||||
CList::const_iterator end = controllers->end();
|
||||
|
||||
while (beg != end) {
|
||||
C::Controller* ctrl = *beg;
|
||||
removeController(ctrl);
|
||||
delete ctrl;
|
||||
beg = controllers->begin();
|
||||
}
|
||||
}
|
||||
|
||||
bool C::Controller::isSubscribed()
|
||||
{
|
||||
return subscribed;
|
||||
}
|
82
lib/wController/controller.h
Normal file
82
lib/wController/controller.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
#ifndef CONTROLLER_H
|
||||
#define CONTROLLER_H
|
||||
|
||||
#include <utils/defines.h>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include <wType/address.h>
|
||||
#include <wType/event.h>
|
||||
#include <wType/vector.h>
|
||||
#include <wType/vocabulary.h>
|
||||
#include <wType/string.h>
|
||||
#include <wDispatcher/dispatcher.h>
|
||||
#include <wDispatcher/handler.h>
|
||||
#include <wSocket/socket.h>
|
||||
#include <wContainer/order.h>
|
||||
|
||||
namespace C {
|
||||
class Controller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ModelType {
|
||||
string,
|
||||
list,
|
||||
vocabulary,
|
||||
catalogue,
|
||||
|
||||
attributes = 50
|
||||
};
|
||||
|
||||
Controller(const W::Address& p_address, const W::Address& my_address, QObject* parent = 0);
|
||||
virtual ~Controller();
|
||||
|
||||
void addController(C::Controller* ctrl);
|
||||
void addHandler(W::Handler* handler);
|
||||
void registerController(W::Dispatcher* dp, const W::Socket* sock);
|
||||
void unregisterController();
|
||||
void subscribe();
|
||||
void unsubscribe();
|
||||
bool isSubscribed();
|
||||
|
||||
void removeHandler(W::Handler* handler);
|
||||
void removeController(C::Controller* ctrl);
|
||||
|
||||
static C::Controller* createByType(int type, const W::Address& address, QObject* parent = 0);
|
||||
|
||||
signals:
|
||||
void serviceMessage(const QString& msg) const;
|
||||
void modification(const W::Object& data);
|
||||
|
||||
protected:
|
||||
W::Address pairAddress;
|
||||
W::Address address;
|
||||
bool subscribed;
|
||||
|
||||
void send(W::Vocabulary* vc, const W::Address& handlerAddress) const;
|
||||
handler(properties)
|
||||
|
||||
void dropSubscribed();
|
||||
virtual W::Vocabulary* createSubscriptionVC() const;
|
||||
void cleanChildren();
|
||||
|
||||
private:
|
||||
typedef W::Order<W::Handler*> HList;
|
||||
typedef W::Order<C::Controller*> CList;
|
||||
|
||||
W::Dispatcher* dispatcher;
|
||||
const W::Socket* socket;
|
||||
bool registered;
|
||||
CList* controllers;
|
||||
HList* handlers;
|
||||
W::Vector* properties;
|
||||
|
||||
protected slots:
|
||||
virtual void onSocketDisconnected();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif // CONTROLLER_H
|
25
lib/wController/controllerstring.cpp
Normal file
25
lib/wController/controllerstring.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "controllerstring.h"
|
||||
|
||||
uint64_t C::String::counter = 0;
|
||||
|
||||
C::String::String(const W::Address p_address, QObject* parent):
|
||||
C::Controller(p_address, W::Address({W::String(u"string") += counter++}), parent),
|
||||
data(u"")
|
||||
{
|
||||
W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &C::String::_h_get);
|
||||
addHandler(get);
|
||||
}
|
||||
|
||||
C::String::~String()
|
||||
{
|
||||
}
|
||||
|
||||
void C::String::h_get(const W::Event& ev)
|
||||
{
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
|
||||
data = static_cast<const W::String&>(vc.at(u"data"));
|
||||
|
||||
emit change(QString(data.toString().c_str()));
|
||||
emit modification(data);
|
||||
}
|
33
lib/wController/controllerstring.h
Normal file
33
lib/wController/controllerstring.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef CONTROLLER_STRING_H
|
||||
#define CONTROLLER_STRING_H
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <wType/string.h>
|
||||
#include <wType/vocabulary.h>
|
||||
#include <wType/event.h>
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace C {
|
||||
class String : public C::Controller
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
String(const W::Address p_address, QObject* parent = 0);
|
||||
~String();
|
||||
|
||||
signals:
|
||||
void change(const QString& str);
|
||||
|
||||
protected:
|
||||
W::String data;
|
||||
|
||||
handler(get)
|
||||
|
||||
private:
|
||||
static uint64_t counter;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // CONTROLLER_STRING_H
|
57
lib/wController/list.cpp
Normal file
57
lib/wController/list.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#include "list.h"
|
||||
|
||||
uint64_t C::List::counter = 0;
|
||||
|
||||
C::List::List(const W::Address p_address, QObject* parent):
|
||||
C::Controller(p_address, W::Address({W::String(u"list") += counter++}), parent),
|
||||
data(new W::Vector())
|
||||
{
|
||||
W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &C::List::_h_get);
|
||||
W::Handler* push = W::Handler::create(address + W::Address({u"push"}), this, &C::List::_h_push);
|
||||
W::Handler* clear = W::Handler::create(address + W::Address({u"clear"}), this, &C::List::_h_clear);
|
||||
addHandler(get);
|
||||
addHandler(push);
|
||||
addHandler(clear);
|
||||
}
|
||||
|
||||
C::List::~List()
|
||||
{
|
||||
delete data;
|
||||
}
|
||||
|
||||
|
||||
void C::List::h_get(const W::Event& ev)
|
||||
{
|
||||
emit clear();
|
||||
data->clear();
|
||||
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
const W::Vector& edata = static_cast<const W::Vector&>(vc.at(u"data"));
|
||||
|
||||
int size = edata.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
data->push(edata.at(i));
|
||||
emit newElement(edata.at(i));
|
||||
}
|
||||
|
||||
emit modification(*data);
|
||||
}
|
||||
|
||||
void C::List::h_push(const W::Event& ev)
|
||||
{
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
const W::Object& el = vc.at(u"data");
|
||||
|
||||
data->push(el);
|
||||
emit newElement(el);
|
||||
|
||||
emit modification(*data);
|
||||
}
|
||||
|
||||
void C::List::h_clear(const W::Event& ev)
|
||||
{
|
||||
emit clear();
|
||||
data->clear();
|
||||
|
||||
emit modification(*data);
|
||||
}
|
36
lib/wController/list.h
Normal file
36
lib/wController/list.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef CONTROLLER_LIST_H
|
||||
#define CONTROLLER_LIST_H
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <wType/address.h>
|
||||
#include <wType/object.h>
|
||||
#include <wType/event.h>
|
||||
#include <wType/vector.h>
|
||||
|
||||
namespace C {
|
||||
class List : public C::Controller
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
List(const W::Address p_address, QObject* parent);
|
||||
~List();
|
||||
|
||||
signals:
|
||||
void clear();
|
||||
void newElement(const W::Object& element);
|
||||
|
||||
protected:
|
||||
W::Vector* data;
|
||||
|
||||
handler(get)
|
||||
handler(push)
|
||||
handler(clear)
|
||||
private:
|
||||
static uint64_t counter;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // CONTROLLER_LIST_H
|
119
lib/wController/vocabulary.cpp
Normal file
119
lib/wController/vocabulary.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
#include "vocabulary.h"
|
||||
|
||||
uint64_t C::Vocabulary::counter = 0;
|
||||
|
||||
C::Vocabulary::Vocabulary(const W::Address p_address, QObject* parent):
|
||||
C::Controller(p_address, W::Address({W::String(u"vocabulary") += counter++}), parent),
|
||||
p_data(new W::Vocabulary())
|
||||
{
|
||||
W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &C::Vocabulary::_h_get);
|
||||
W::Handler* change = W::Handler::create(address + W::Address({u"change"}), this, &C::Vocabulary::_h_change);
|
||||
W::Handler* clear = W::Handler::create(address + W::Address({u"clear"}), this, &C::Vocabulary::_h_clear);
|
||||
addHandler(get);
|
||||
addHandler(change);
|
||||
addHandler(clear);
|
||||
}
|
||||
|
||||
C::Vocabulary::~Vocabulary()
|
||||
{
|
||||
delete p_data;
|
||||
}
|
||||
|
||||
C::Vocabulary::Vocabulary(const W::Address p_address, const W::Address& my_address, QObject* parent):
|
||||
C::Controller(p_address, my_address, parent),
|
||||
p_data(new W::Vocabulary())
|
||||
{
|
||||
W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &C::Vocabulary::_h_get);
|
||||
W::Handler* change = W::Handler::create(address + W::Address({u"change"}), this, &C::Vocabulary::_h_change);
|
||||
W::Handler* clear = W::Handler::create(address + W::Address({u"clear"}), this, &C::Vocabulary::_h_clear);
|
||||
addHandler(get);
|
||||
addHandler(change);
|
||||
addHandler(clear);
|
||||
}
|
||||
|
||||
|
||||
void C::Vocabulary::h_get(const W::Event& ev)
|
||||
{
|
||||
_clear();
|
||||
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
const W::Vocabulary& e_data = static_cast<const W::Vocabulary&>(vc.at(u"data"));
|
||||
|
||||
W::Vector keys = e_data.keys();
|
||||
int size = keys.length();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const W::String& key = static_cast<const W::String&>(keys.at(i));
|
||||
_newElement(key, e_data.at(key));
|
||||
}
|
||||
|
||||
emit modification(*p_data);
|
||||
emit data();
|
||||
}
|
||||
|
||||
void C::Vocabulary::h_change(const W::Event& ev)
|
||||
{
|
||||
const W::Vocabulary& vc = static_cast<const W::Vocabulary&>(ev.getData());
|
||||
|
||||
const W::Vector& erase = static_cast<const W::Vector&>(vc.at(u"erase"));
|
||||
const W::Vocabulary& insert = static_cast<const W::Vocabulary&>(vc.at(u"insert"));
|
||||
|
||||
int eSize = erase.length();
|
||||
for (int i = 0; i < eSize; ++i) {
|
||||
const W::String& key = static_cast<const W::String&>(erase.at(i));
|
||||
_removeElement(key);
|
||||
}
|
||||
|
||||
W::Vector keys = insert.keys();
|
||||
int iSize = keys.length();
|
||||
for (int i = 0; i < iSize; ++i) {
|
||||
const W::String& key = static_cast<const W::String&>(keys.at(i));
|
||||
_newElement(key, insert.at(key));
|
||||
}
|
||||
|
||||
emit modification(*p_data);
|
||||
}
|
||||
|
||||
void C::Vocabulary::h_clear(const W::Event& ev)
|
||||
{
|
||||
_clear();
|
||||
emit modification(*p_data);
|
||||
}
|
||||
|
||||
void C::Vocabulary::_newElement(const W::String& key, const W::Object& element)
|
||||
{
|
||||
p_data->insert(key, element);
|
||||
emit newElement(key, element);
|
||||
}
|
||||
|
||||
void C::Vocabulary::_removeElement(const W::String& key)
|
||||
{
|
||||
emit removeElement(key);
|
||||
p_data->erase(key);
|
||||
}
|
||||
|
||||
|
||||
void C::Vocabulary::_clear()
|
||||
{
|
||||
emit clear();
|
||||
p_data->clear();
|
||||
}
|
||||
|
||||
const W::Object & C::Vocabulary::at(const W::String& key) const
|
||||
{
|
||||
return p_data->at(key);
|
||||
}
|
||||
|
||||
const W::Object & C::Vocabulary::at(const W::String::u16string& key) const
|
||||
{
|
||||
return p_data->at(key);
|
||||
}
|
||||
|
||||
bool C::Vocabulary::has(const W::String& key) const
|
||||
{
|
||||
return p_data->has(key);
|
||||
}
|
||||
|
||||
bool C::Vocabulary::has(const W::String::u16string& key) const
|
||||
{
|
||||
return p_data->has(key);
|
||||
}
|
49
lib/wController/vocabulary.h
Normal file
49
lib/wController/vocabulary.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
#ifndef CONTROLLER_VOCABULARY_H
|
||||
#define CONTROLLER_VOCABULARY_H
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <wType/address.h>
|
||||
#include <wType/object.h>
|
||||
#include <wType/event.h>
|
||||
#include <wType/vector.h>
|
||||
#include <wType/vocabulary.h>
|
||||
|
||||
namespace C {
|
||||
class Vocabulary : public C::Controller
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
Vocabulary(const W::Address p_address, const W::Address& my_address, QObject* parent = 0); //for inheritors
|
||||
public:
|
||||
Vocabulary(const W::Address p_address, QObject* parent = 0);
|
||||
~Vocabulary();
|
||||
|
||||
const W::Object& at(const W::String& key) const;
|
||||
const W::Object& at(const W::String::u16string& key) const;
|
||||
bool has(const W::String& key) const;
|
||||
bool has(const W::String::u16string& key) const;
|
||||
|
||||
signals:
|
||||
void clear();
|
||||
void newElement(const W::String& key, const W::Object& element);
|
||||
void removeElement(const W::String& key);
|
||||
void data();
|
||||
|
||||
protected:
|
||||
W::Vocabulary* p_data;
|
||||
|
||||
handler(get)
|
||||
handler(change)
|
||||
handler(clear)
|
||||
|
||||
virtual void _newElement(const W::String& key, const W::Object& element);
|
||||
virtual void _removeElement(const W::String& key);
|
||||
virtual void _clear();
|
||||
|
||||
private:
|
||||
static uint64_t counter;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // VOCABULARY_H
|
Loading…
Add table
Add a link
Reference in a new issue