commit 4b60ece582da19b34e3d45535d14327ab087fbeb Author: blue Date: Sun Aug 5 00:46:25 2018 +0300 initial commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..df3ead1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 2.8.12) +project(RadioW) + +function(add_jslib file name path arch) + execute_process(COMMAND + node polymorph + ${LIBJS_DIR}/${file} + ${path}/lib/${file} + ${name} + ${arch} + ${path}/lib + WORKING_DIRECTORY ${LIBJS_DIR}) +endfunction(add_jslib) + +include(CheckCXXCompilerFlag) +set(CMAKE_CXX_STANDARD 11) + +include_directories(lib) + +set(LIBJS_DIR ${CMAKE_SOURCE_DIR}/libjs) +set(ROBOUTE_DIR ${CMAKE_BINARY_DIR}/roboute) +set(CORAX_DIR ${CMAKE_BINARY_DIR}/corax) +set(MAGNUS_DIR ${CMAKE_BINARY_DIR}/magnus) +set(LORGAR_DIR ${MAGNUS_DIR}/public) +set(PERTURABO_DIR ${CMAKE_BINARY_DIR}/perturabo) + +add_subdirectory(lib) +add_subdirectory(corax ${CORAX_DIR}) +add_subdirectory(magnus ${MAGNUS_DIR}) +add_subdirectory(lorgar ${LORGAR_DIR}) +add_subdirectory(roboute ${ROBOUTE_DIR}) +add_subdirectory(perturabo ${PERTURABO_DIR}) + +add_subdirectory(test) diff --git a/README.md b/README.md new file mode 100644 index 0000000..86dc7ac --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# RadioW + +## Dependencies + +1. cmake >= 2.8.12 +2. qt 5.* + 1. qt(5)-base + 2. qt(5)-websockets +3. nodejs +4. npm +5. libssh +6. lmdb +7. taglib + +## Building + +Attention! During the first build internet connection is mandatory. There are some nodejs dependencies, which npm is going to unstall during configuration. + +1. Create a build directory and checkout there. For example if you are in project directory, and want to build in subdirectory run + + ```bash + mkdir build + cd build + ``` +2. Run cmake to configure the project, giving the path to project root directory. For example + + ```bash + cmake ../ + ``` +3. Run make to build the project. For example + + ```bash + make + ``` diff --git a/corax/CMakeLists.txt b/corax/CMakeLists.txt new file mode 100644 index 0000000..b0c6a34 --- /dev/null +++ b/corax/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 2.8.12) +project(corax) + +find_package(Qt5Core REQUIRED) +find_package(Qt5Network REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + corax.h + tools/parser.h + tools/audioid.h + tools/audiotag.h +) + +set(SOURCES + main.cpp + corax.cpp + tools/parser.cpp + tools/audioid.cpp + tools/audiotag.cpp +) + +add_executable(corax ${HEADERS} ${SOURCES}) + +target_link_libraries(corax Qt5::Core) +target_link_libraries(corax Qt5::Network) + +target_link_libraries(corax wSocket) +target_link_libraries(corax wDispatcher) +target_link_libraries(corax utils) +target_link_libraries(corax wModel) +target_link_libraries(corax wController) +target_link_libraries(corax wServerUtils) +target_link_libraries(corax wDatabase) +target_link_libraries(corax tag) +target_link_libraries(corax tools) + +install(TARGETS corax RUNTIME DESTINATION bin) diff --git a/corax/corax.cpp b/corax/corax.cpp new file mode 100644 index 0000000..053b391 --- /dev/null +++ b/corax/corax.cpp @@ -0,0 +1,236 @@ +#include "corax.h" + +#include + +using std::cout; +using std::endl; + +Corax* Corax::corax = 0; + +Corax::Corax(QObject *parent): + QObject(parent), + server(new W::Server(W::String(u"Corax"), this)), + logger(new W::Logger()), + parentReporter(new W::ParentReporter()), + attributes(new M::Attributes(W::Address({u"attributes"}))), + commands(new U::Commands(W::Address{u"management"})), + connector(0), + dispatcher(new W::Dispatcher()), + caches(), + parsers() +{ + if (corax != 0) + { + throw SingletonError(); + } + Corax::corax = this; + + connector = new U::Connector(dispatcher, server, commands); + connector->addIgnoredNode(W::String(u"Lorgar")); + connector->addIgnoredNode(W::String(u"Roboute")); + + connect(attributes, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + connect(commands, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + connect(connector, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + connect(connector, SIGNAL(nodeConnected(const W::String&)), SLOT(onNodeConnected(const W::String&))); + connect(connector, SIGNAL(nodeDisconnected(const W::String&)), SLOT(onNodeDisconnected(const W::String&))); + connect(server, SIGNAL(connectionCountChange(uint64_t)), SLOT(onConnectionCountChanged(uint64_t))); + + dispatcher->registerDefaultHandler(parentReporter); + dispatcher->registerDefaultHandler(logger); + + attributes->addAttribute(W::String(u"connectionsCount"), new M::String(W::String(u"0"), W::Address({u"attributes", u"connectionCount"}))); + attributes->addAttribute(W::String(u"name"), new M::String(W::String(u"Corax"), W::Address({u"attributes", u"name"}))); + attributes->addAttribute(W::String(u"version"), new M::String(W::String(u"0.0.2"), W::Address({u"attributes", u"version"}))); + + createCaches(); + createHandlers(); +} + +Corax::~Corax() +{ + std::map::iterator pbeg = parsers.begin(); + std::map::iterator pend = parsers.end(); + + for (; pbeg != pend; ++pbeg) { + delete pbeg->second; + } + + std::map::iterator beg = caches.begin(); + std::map::iterator end = caches.end(); + + for (; beg != end; ++beg) { + delete beg->second; + } + + delete connector; + + dispatcher->unregisterDefaultHandler(logger); + + delete commands; + delete attributes; + + delete logger; + delete dispatcher; + + Corax::corax = 0; +} + +void Corax::onConnectionCountChanged(uint64_t count) +{ + attributes->setAttribute(W::String(u"connectionsCount"), new W::String(std::to_string(count))); +} + +void Corax::start() +{ + std::map::iterator beg = caches.begin(); + std::map::iterator end = caches.end(); + + cout << "Starting corax..." << endl; + server->listen(8080); + + cout << "Registering models..." << endl; + attributes->registerModel(dispatcher, server); + commands->registerModel(dispatcher, server); + + for (; beg != end; ++beg) { + beg->second->registerModel(dispatcher, server); + } + + cout << "Opening caches..." << endl; + + beg = caches.begin(); + for (; beg != end; ++beg) { + beg->second->open(); + } + + commands->enableCommand(W::String(u"clearCache"), true); + + cout << "Corax is ready" << endl; +} + +void Corax::stop() +{ + std::map::iterator beg = caches.begin(); + std::map::iterator end = caches.end(); + + cout << "Stopping corax..." << endl; + commands->unregisterModel(); + attributes->unregisterModel(); + + for (; beg != end; ++beg) { + beg->second->unregisterModel(); + } + + server->stop(); +} + +void Corax::onModelServiceMessage(const QString& msg) +{ + cout << msg.toStdString() << endl; +} + +void Corax::addCache(ResourceCache* cache) +{ + attributes->addAttribute(cache->name, new M::String(W::String(u"0"), W::Address({u"attributes", cache->name}))); + + connect(cache, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + connect(cache, SIGNAL(countChange(uint64_t)), SLOT(onCacheCountChange(uint64_t))); + + parentReporter->registerParent(cache->getAddress(), cache->subscribeMember); + + caches.insert(std::make_pair(cache->name, cache)); +} + +void Corax::h_clearCache(const W::Event& ev) +{ + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::String& name = static_cast(vc.at(u"name")); + + cout << "received command to clear cache " << name.toString() << endl; + + std::map::iterator itr = caches.find(name); + if (itr == caches.end()) { + cout << "cache " << name.toString() << " doesn't exist" << endl; + } else { + itr->second->clear(); + } +} + +void Corax::h_parseDirectory(const W::Event& ev) +{ + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::String& path = static_cast(vc.at(u"path")); + + cout << "received command to parse directory " << path.toString() << endl; + + std::map::const_iterator itr = parsers.find(path); + + if (itr != parsers.end()) { + cout << "directory " << path.toString() << " is already being parsed" << endl; + } else { + const W::Socket& socket = connector->getNodeSocket(W::String(u"Perturabo")); + ResourceCache* music = caches.at(W::String(u"music")); + ResourceCache* images = caches.at(W::String(u"images")); + Parser* parser = new Parser(&socket, dispatcher, music, images); + parsers.insert(std::make_pair(path, parser)); + + connect(parser, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + connect(parser, SIGNAL(done(const W::String&)), SLOT(onParserDone(const W::String&))); + + parser->parse(path); + } +} + +void Corax::createCaches() +{ + ResourceCache* music = new ResourceCache(W::String(u"music")); + ResourceCache* images = new ResourceCache(W::String(u"images")); + + addCache(music); + addCache(images); +} + +void Corax::createHandlers() +{ + W::Handler* clearCache = W::Handler::create(W::Address({u"management", u"clearCache"}), this, &Corax::_h_clearCache); + W::Vocabulary clearArgs; + clearArgs.insert(u"name", W::Uint64(W::Object::string)); + commands->addCommand(W::String(u"clearCache"), clearCache, clearArgs); + + W::Handler* parseDirectory = W::Handler::create(W::Address({u"management", u"parseDirectory"}), this, &Corax::_h_parseDirectory); + W::Vocabulary parseArgs; + parseArgs.insert(u"path", W::Uint64(W::Object::string)); + commands->addCommand(W::String(u"parseDirectory"), parseDirectory, parseArgs); +} + +void Corax::onParserDone(const W::String& path) +{ + std::map::const_iterator itr = parsers.find(path); + + delete itr->second; + parsers.erase(itr); +} + +void Corax::onCacheCountChange(uint64_t count) +{ + ResourceCache* cache = static_cast(sender()); + + attributes->setAttribute(cache->name, W::String(std::to_string(count))); +} + +void Corax::onNodeConnected(const W::String& name) +{ + cout << "connected node " << name.toString() << endl; + if (name == u"Perturabo") { + commands->enableCommand(W::String(u"parseDirectory"), true); + } +} + +void Corax::onNodeDisconnected(const W::String& name) +{ + cout << "disconnected node " << name.toString() << endl; + if (name == u"Perturabo") { + commands->enableCommand(W::String(u"parseDirectory"), false); + } +} diff --git a/corax/corax.h b/corax/corax.h new file mode 100644 index 0000000..179df14 --- /dev/null +++ b/corax/corax.h @@ -0,0 +1,88 @@ +#ifndef CORAX_H +#define CORAX_H + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +#include "tools/parser.h" + +class Corax: public QObject +{ + Q_OBJECT + +public: + Corax(QObject *parent = 0); + ~Corax(); + + static Corax* corax; + +private: + W::Server *server; + W::Logger *logger; + W::ParentReporter* parentReporter; + + M::Attributes* attributes; + U::Commands* commands; + U::Connector* connector; + W::Dispatcher *dispatcher; + + std::map caches; + std::map parsers; + + handler(clearCache); + handler(parseDirectory); + +public slots: + void start(); + void stop(); + +private slots: + void onModelServiceMessage(const QString& msg); + void onConnectionCountChanged(uint64_t count); + void onParserDone(const W::String& path); + void onCacheCountChange(uint64_t count); + void onNodeConnected(const W::String& name); + void onNodeDisconnected(const W::String& name); + +private: + void addCache(ResourceCache* cache); + void createCaches(); + void createHandlers(); + +private: + class SingletonError: + public Utils::Exception + { + public: + SingletonError():Exception(){} + + std::string getMessage() const{return "Corax is a singleton, there was an attempt to construct it at the second time";} + }; +}; + +#endif // CORAX_H diff --git a/corax/main.cpp b/corax/main.cpp new file mode 100644 index 0000000..dea6297 --- /dev/null +++ b/corax/main.cpp @@ -0,0 +1,18 @@ +#include +#include + +#include + +#include "corax.h" + +int main(int argc, char **argv) { + QCoreApplication app(argc, argv); + W::SignalCatcher sc(&app); + + Corax* corax = new Corax(&app); + + QTimer::singleShot(0, corax, SLOT(start())); + QObject::connect(&app, SIGNAL(aboutToQuit()), corax, SLOT(stop())); + + return app.exec(); +} \ No newline at end of file diff --git a/corax/tools/audioid.cpp b/corax/tools/audioid.cpp new file mode 100644 index 0000000..cc46000 --- /dev/null +++ b/corax/tools/audioid.cpp @@ -0,0 +1,61 @@ +#include "audioid.h" +AudioId::AudioId(const W::String& p_artist, const W::String& p_album, const W::String& p_name): + artist(p_artist), + album(p_album), + name(p_name) +{ +} + +AudioId::AudioId(const AudioId& other): + artist(other.artist), + album(other.album), + name(other.name) +{ +} + +bool AudioId::operator==(const AudioId& other) const +{ + return name == other.name && album == other.album && artist == other.artist; +} + +bool AudioId::operator!=(const AudioId& other) const +{ + return operator==(other); +} + +bool AudioId::operator>(const AudioId& other) const +{ + if (name == other.name) { + if (album == other.album) { + return name > other.name; + } else { + return album > other.album; + } + } else { + return name > other.name; + } +} + +bool AudioId::operator<(const AudioId& other) const +{ + if (name == other.name) { + if (album == other.album) { + return name < other.name; + } else { + return album < other.album; + } + } else { + return name < other.name; + } +} + + +bool AudioId::operator>=(const AudioId& other) const +{ + return !operator<(other); +} + +bool AudioId::operator<=(const AudioId& other) const +{ + return !operator>(other); +} diff --git a/corax/tools/audioid.h b/corax/tools/audioid.h new file mode 100644 index 0000000..26a362b --- /dev/null +++ b/corax/tools/audioid.h @@ -0,0 +1,28 @@ +#ifndef AUDIOID_H +#define AUDIOID_H + +/** + * @todo write docs + */ + +#include + +class AudioId +{ +public: + AudioId(const W::String& p_artist, const W::String& p_album, const W::String& p_name); + AudioId(const AudioId& other); + + bool operator==(const AudioId& other) const; + bool operator!=(const AudioId& other) const; + bool operator<(const AudioId& other) const; + bool operator>(const AudioId& other) const; + bool operator<=(const AudioId& other) const; + bool operator>=(const AudioId& other) const; + + const W::String artist; + const W::String album; + const W::String name; +}; + +#endif // AUDIOID_H diff --git a/corax/tools/audiotag.cpp b/corax/tools/audiotag.cpp new file mode 100644 index 0000000..6ad9c93 --- /dev/null +++ b/corax/tools/audiotag.cpp @@ -0,0 +1,35 @@ +#include "audiotag.h" + +AudioTag::AudioTag(const AudioTag& other): + fileRef(other.fileRef) +{ +} + +AudioTag::AudioTag(const T::File& file): + fileRef(file.getPath().toString().c_str()) +{ +} + +AudioTag::~AudioTag() +{ +} + +W::String AudioTag::getTitle() const +{ + return W::String(fileRef.tag()->title().to8Bit(true)); +} + +W::String AudioTag::getAlbum() const +{ + return W::String(fileRef.tag()->album().to8Bit(true)); +} + +W::String AudioTag::getArtist() const +{ + return W::String(fileRef.tag()->artist().to8Bit(true)); +} + +W::Uint64 AudioTag::getYear() const +{ + return W::Uint64(fileRef.tag()->year()); +} diff --git a/corax/tools/audiotag.h b/corax/tools/audiotag.h new file mode 100644 index 0000000..7c50095 --- /dev/null +++ b/corax/tools/audiotag.h @@ -0,0 +1,29 @@ +#ifndef AUDIOTAG_H +#define AUDIOTAG_H + +#include +#include + +#include + +#include +#include + +class AudioTag +{ +public: + AudioTag(const T::File& file); + AudioTag(const AudioTag& other); + ~AudioTag(); + + W::String getTitle() const; + W::String getAlbum() const; + W::String getArtist() const; + W::Uint64 getYear() const; + + +private: + TagLib::FileRef fileRef; +}; + +#endif // AUDIOTAG_H diff --git a/corax/tools/parser.cpp b/corax/tools/parser.cpp new file mode 100644 index 0000000..b6e10ef --- /dev/null +++ b/corax/tools/parser.cpp @@ -0,0 +1,309 @@ +#include "parser.h" + +Parser::Parser(const W::Socket* p_socket, W::Dispatcher* p_dp, ResourceCache* p_audio, ResourceCache* p_images): + QObject(), + socket(p_socket), + dp(p_dp), + songs(W::Address({u"songs"})), + albums(W::Address({u"albums"})), + artists(W::Address({u"artists"})), + audio(p_audio), + images(p_images), + path(), + songsReady(false), + albumsReady(false), + artistsReady(false), + state(idle), + foundImages(), + foundAudios() +{ + connect(&songs, SIGNAL(ready()), this, SLOT(onSongsReady())); + connect(&songs, SIGNAL(serviceMessage(const QString&)), this, SIGNAL(serviceMessage(const QString&))); + connect(&albums, SIGNAL(ready()), this, SLOT(onAlbumsReady())); + connect(&albums, SIGNAL(serviceMessage(const QString&)), this, SIGNAL(serviceMessage(const QString&))); + connect(&artists, SIGNAL(ready()), this, SLOT(onArtistsReady())); + connect(&artists, SIGNAL(serviceMessage(const QString&)), this, SIGNAL(serviceMessage(const QString&))); + + songs.registerController(dp, socket); + albums.registerController(dp, socket); + artists.registerController(dp, socket); +} + +Parser::~Parser() +{ +} + + +void Parser::onSongsReady() +{ + songsReady = true; + emit serviceMessage("Songs are ready"); + checkState(); +} + +void Parser::onAlbumsReady() +{ + albumsReady = true; + emit serviceMessage("Albums are ready"); + checkState(); +} + +void Parser::onArtistsReady() +{ + artistsReady = true; + emit serviceMessage("Artists are ready"); + checkState(); +} + +void Parser::checkState() +{ + switch (state) { + case idle: + break; + case waitingForCollections: + if (songsReady && albumsReady && artistsReady) { + state = parsingDirectory; + parseDirectory(); + } + break; + case parsingDirectory: + parseDirectory(); + break; + case updatingMusicDataBase: + if (songsReady && albumsReady && artistsReady) { + updateMusicDataBase(); + } + break; + case updatingImageDataBase: + if (songsReady && albumsReady && artistsReady) { + updateImageDataBase(); + } + break; + + } + + +} + +void Parser::parse(const W::String& p_path) +{ + if (state != idle) { + emit serviceMessage("An attempt to make parsing while another isn't finished, quitting"); + throw 15; + } + + path = p_path; + + if (!songs.isSubscribed()) { + songs.subscribe(); + } + + if (!albums.isSubscribed()) { + albums.subscribe(); + } + + if (!artists.isSubscribed()) { + artists.subscribe(); + } + + if (!songsReady || !albumsReady || !artistsReady) { + state = waitingForCollections; + } else { + state = parsingDirectory; + } + + checkState(); +} + +void Parser::parseDirectory() +{ + emit serviceMessage(QString("Starting to parse directory ") + path.toString().c_str()); + + std::list *list = new std::list(); + bool success = T::File::readDirectoryRecursive(path, list); + + if (success) { + emit serviceMessage("Successully recursively red the directory"); + std::set presentMusicId = audio->getAllIdentificators(); + std::set presentAudio; + std::set::const_iterator pai(presentMusicId.begin()), pae(presentMusicId.end()); + for (; pai != pae; ++pai) { + presentAudio.insert(audio->getPath(*pai)); + } + + std::set presentImageId = images->getAllIdentificators(); + std::set presentImages; + std::set::const_iterator pii(presentImageId.begin()), pie(presentImageId.end()); + for (; pii != pie; ++pii) { + presentImages.insert(images->getPath(*pii)); + } + + std::list::const_iterator itr = list->begin(); + std::list::const_iterator end = list->end(); + for (; itr != end; ++itr) { + W::String path = itr->getPath(); + emit serviceMessage(QString("Analysing ") + path.toString().c_str()); + + if (itr->suffix() == u"mp3") { + if (presentAudio.find(itr->getPath()) == presentAudio.end()) { + AudioTag tag(*itr); + uint64_t id = audio->addResource(itr->getPath()); + AudioId aid(tag.getArtist(), tag.getAlbum(), tag.getTitle()); + foundAudios.insert(std::make_pair(aid, id)); + } + + } else if (itr->suffix() == u"jpg") { + if (presentImages.find(itr->getPath()) == presentImages.end()) { + uint64_t id = images->addResource(itr->getPath()); + foundImages.insert(std::make_pair(itr->parentDirectory(), id)); + } + } + } + + emit serviceMessage(QString("Found ") + std::to_string(foundAudios.size()).c_str() + " audio files"); + emit serviceMessage(QString("Found ") + std::to_string(foundImages.size()).c_str() + " images"); + + state = updatingMusicDataBase; + + + + updateMusicDataBase(); + } else { + emit serviceMessage("Error parsing the directory"); + } + + delete list; +} + +void Parser::updateMusicDataBase() +{ + while (foundAudios.size() > 0) { + std::map::const_iterator itr = foundAudios.begin(); + + std::set aids = artists.find(W::String(u"name"), itr->first.artist); + if (aids.size() == 0) { + W::Vocabulary art; + art.insert(u"name", itr->first.artist); + artists.addRemoteElement(art); + artistsReady = false; + emit serviceMessage(QString("Creating artist: ") + itr->first.artist.toString().c_str()); + + return; + } + uint64_t artistId = *(aids.begin()); + + uint64_t thumbId = 0; //TODO make some default picture for the case of not found images + std::set alids = albums.find(W::String(u"name"), itr->first.album); + std::map::const_iterator albImageItr = foundImages.find(itr->first.album); + if (albImageItr != foundImages.end()) { + thumbId = albImageItr->second; + } + uint64_t albumId = 0; + bool albumFound = false; + const C::Vocabulary* albCtrl = 0; + while (alids.size() > 0 && !albumFound) { + std::set::const_iterator litr = alids.begin(); + albumId = *litr; + alids.erase(litr); + albCtrl = &albums.get(albumId); + if (static_cast(albCtrl->at(u"artist")) == artistId) { + albumFound = true; + } + } + if (!albumFound) { + W::Vocabulary alb; + alb.insert(u"name", itr->first.album); + alb.insert(u"artist", W::Uint64(artistId)); + if (thumbId != 0) { + alb.insert(u"image", W::Uint64(thumbId)); + emit serviceMessage(QString("Found a cover for album: ") + itr->first.album.toString().c_str()); + } + albums.addRemoteElement(alb); + albumsReady = false; + emit serviceMessage(QString("Creating album: ") + itr->first.album.toString().c_str()); + + return; + } + if (thumbId != 0 && (!albCtrl->has(u"image") || static_cast(albCtrl->at(u"image")) != thumbId)) { + W::Vocabulary alb; + alb.insert(u"image", W::Uint64(thumbId)); + albums.updateRemoteElement(W::Uint64(albumId), alb); + emit serviceMessage(QString("Found a cover for album: ") + itr->first.album.toString().c_str()); + foundImages.erase(albImageItr); + } + + std::set sids = songs.find(W::String(u"name"), itr->first.name); + uint64_t songId = 0; + bool songFound = false; + const C::Vocabulary* songCtrl = 0; + while (sids.size() > 0 && !songFound) { + std::set::const_iterator sitr = sids.begin(); + songId = *sitr; + sids.erase(sitr); + songCtrl = &songs.get(songId); + if (static_cast(songCtrl->at(u"album")) == albumId && static_cast(songCtrl->at(u"artist")) == artistId) { + songFound = true; + } + } + + W::Vocabulary sng; + sng.insert(u"audio", W::Uint64(itr->second)); + + if (!songFound) { + sng.insert(u"name", itr->first.name); + sng.insert(u"album", W::Uint64(albumId)); + sng.insert(u"artist", W::Uint64(artistId)); + songs.addRemoteElement(sng); + songsReady = false; + emit serviceMessage(QString("Creating a song: ") + itr->first.name.toString().c_str()); + } else if (!songCtrl->has(u"audio") || static_cast(songCtrl->at(u"audio")) != itr->second) { + emit serviceMessage(QString("Found missing media for a song: ") + itr->first.name.toString().c_str()); + songs.updateRemoteElement(W::Uint64(songId), sng); + } + + foundAudios.erase(itr); + if (!songFound) { + return; + } + } + + emit serviceMessage("Audio parsing is complete"); + + state = updatingImageDataBase; + emit serviceMessage("Parsing images"); + + updateImageDataBase(); +} + +void Parser::updateImageDataBase() +{ + while (foundImages.size() > 0) { + std::map::const_iterator itr = foundImages.begin(); + + std::set alids = albums.find(W::String(u"name"), itr->first); + if (alids.size() == 0) { + emit serviceMessage(QString("Image in the folder ") + itr->first.toString().c_str() + " doesn't belong to any albumm, skipping"); + } else if (alids.size() > 1) { + emit serviceMessage(QString("Image in the folder ") + itr->first.toString().c_str() + " belongs to " + std::to_string(alids.size()).c_str() + " albums, skipping"); + } else { + uint64_t albumId = *alids.begin(); + const C::Vocabulary& ctrl = albums.get(albumId); + + if (!ctrl.has(u"image") || static_cast(ctrl.at(u"image")) != itr->second) { + W::Vocabulary vc; + vc.insert(u"image", W::Uint64(itr->second)); + emit serviceMessage(QString("Found missing cover for album: ") + itr->first.toString().c_str()); + + albums.updateRemoteElement(W::Uint64(albumId), vc); + } + } + + foundImages.erase(itr); + } + + emit serviceMessage("Parsing is complete"); + + state = idle; + emit done(path); +} + diff --git a/corax/tools/parser.h b/corax/tools/parser.h new file mode 100644 index 0000000..0cb6f35 --- /dev/null +++ b/corax/tools/parser.h @@ -0,0 +1,71 @@ +#ifndef PARSER_H +#define PARSER_H + +/** + * @todo write docs + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "audiotag.h" +#include "audioid.h" + +class Parser: public QObject +{ + Q_OBJECT +public: + Parser(const W::Socket* p_socket, W::Dispatcher* p_dp, ResourceCache* p_audio, ResourceCache* p_images); + ~Parser(); + + void parse(const W::String& p_path); + +signals: + void serviceMessage(const QString& msg); + void done(const W::String& path); + +private: + enum State { + idle, + waitingForCollections, + parsingDirectory, + updatingMusicDataBase, + updatingImageDataBase + }; + + const W::Socket* socket; + W::Dispatcher* dp; + C::Collection songs; + C::Collection albums; + C::Collection artists; + ResourceCache* audio; + ResourceCache* images; + W::String path; + + bool songsReady; + bool albumsReady; + bool artistsReady; + State state; + std::map foundImages; + std::map foundAudios; + + void checkState(); + void parseDirectory(); + void updateMusicDataBase(); + void updateImageDataBase(); + +private slots: + void onSongsReady(); + void onAlbumsReady(); + void onArtistsReady(); +}; + +#endif // PARSER_H diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..76f5480 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.12) +project(lib) + +add_subdirectory(utils) +add_subdirectory(tools) +add_subdirectory(wSocket) +add_subdirectory(wType) +add_subdirectory(wDispatcher) +add_subdirectory(wContainer) +add_subdirectory(wSsh) +add_subdirectory(wModel) +add_subdirectory(wController) +add_subdirectory(wServerUtils) +add_subdirectory(fontParser) +add_subdirectory(wDatabase) diff --git a/lib/fontParser/CMakeLists.txt b/lib/fontParser/CMakeLists.txt new file mode 100644 index 0000000..69f8d71 --- /dev/null +++ b/lib/fontParser/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.6) +project(fontparser) + +set(SOURCES + font.cpp +) + +add_subdirectory(tables) + +add_library(font STATIC ${SOURCES}) +target_link_libraries(font tables) + +add_executable(fontparser main.cpp) +target_link_libraries(fontparser font) + + +install(TARGETS fontparser RUNTIME DESTINATION bin) diff --git a/lib/fontParser/font.cpp b/lib/fontParser/font.cpp new file mode 100644 index 0000000..814772e --- /dev/null +++ b/lib/fontParser/font.cpp @@ -0,0 +1,223 @@ +#include "font.h" +#include + +Font::Font(const std::string& p_path): + path(p_path), + tables(), + cmap(0), + hhea(0), + hmtx(0), + head(0), + name(0) +{ + std::ifstream file(path, std::ios::in | std::ios::binary); + + char * buffer; + + buffer = new char[4]; + file.read(buffer, 4); + uint32_t sfntVersion = ntohl(*((uint32_t*) buffer)); + if (sfntVersion == 0x00010000) { + version = TrueTypeOutlines; + } else if (sfntVersion == 0x4f54544f) { + version = WithCFFData; + } else { + std::cout << "unsupported sfntVersion" << std::endl; + throw 1; + } + delete[] buffer; + + buffer = new char[2]; + file.read(buffer, 2); + numberTables = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + searchRange = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + entrySelector = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + rangeShift = ntohs(*((uint16_t*) buffer)); + + for (int i = 0; i < numberTables; ++i) { + Table* t = Table::fromIfStream(file); + tables.insert(std::make_pair(t->tag, t)); + } + + file.close(); +} + +Font::~Font() +{ + std::map::const_iterator beg = tables.begin(); + std::map::const_iterator end = tables.end(); + + for (; beg != end; ++beg) { + delete beg->second; + } +} + + +bool Font::hasTable(const std::string& tag) const +{ + std::map::const_iterator itr = tables.find(tag); + return itr != tables.end(); +} + +std::list Font::availableTables() const +{ + std::list res; + std::map::const_iterator beg = tables.begin(); + std::map::const_iterator end = tables.end(); + + for (; beg != end; ++beg) { + res.push_back(beg->first); + } + + return res; +} + +std::map Font::getCharCodeToCIDTable(uint32_t start, uint32_t end) +{ + if (cmap == NULL) { + cmap = static_cast(tables.at("cmap")); + cmap->read(path); + } + std::map res; + for (uint32_t i = start; i <= end; ++i) { + res.insert(std::make_pair(i, cmap->getCID(i))); + } + return res; +} + +std::map Font::getCharCodeMetrics(uint32_t start, uint32_t end) +{ + std::map CCtoCID = getCharCodeToCIDTable(start, end); + std::map res; + + if (hmtx == NULL) { + hmtx = static_cast(tables.at("hmtx")); + if (hhea == NULL) { + hhea = static_cast(tables.at("hhea")); + hhea->read(path); + } + hmtx->numOfLongHorMetrics = hhea->numOfLongHorMetrics; + hmtx->read(path); + } + + std::map::const_iterator itr = CCtoCID.begin(); + std::map::const_iterator mend = CCtoCID.end(); + + for (; itr != mend; ++itr) { + res.insert(std::make_pair(itr->first, hmtx->getMetric(itr->second))); + } + + return res; +} + + +Table * Font::getTable(const std::string& tag) +{ + std::map::iterator itr = tables.find(tag); + return itr->second; +} + +uint16_t Font::getUnitsPerEm() +{ + if (head == NULL) { + head = static_cast(tables.at("head")); + head->read(path); + } + return head->unitsPerEm; +} + +int16_t Font::getAscent() +{ + if (hhea == NULL) { + hhea = static_cast(tables.at("hhea")); + hhea->read(path); + } + return hhea->ascent; +} + +int16_t Font::getDescent() +{ + if (hhea == NULL) { + hhea = static_cast(tables.at("hhea")); + hhea->read(path); + } + return hhea->descent; +} + +int16_t Font::getLineGap() +{ + if (hhea == NULL) { + hhea = static_cast(tables.at("hhea")); + hhea->read(path); + } + return hhea->lineGap; +} + +std::string Font::getNameField(std::string key) +{ + if (name == NULL) { + name = static_cast(tables.at("name")); + name->read(path); + } + return name->getRecord(key); +} + +int16_t Font::getCaretSlopeRise() +{ + if (hhea == NULL) { + hhea = static_cast(tables.at("hhea")); + hhea->read(path); + } + return hhea->caretSlopeRise; +} + +int16_t Font::getCaretSlopeRun() +{ + if (hhea == NULL) { + hhea = static_cast(tables.at("hhea")); + hhea->read(path); + } + return hhea->caretSlopeRun; +} + +int16_t Font::getXMax() +{ + if (head == NULL) { + head = static_cast(tables.at("head")); + head->read(path); + } + return head->xMax; +} + +int16_t Font::getXMin() +{ + if (head == NULL) { + head = static_cast(tables.at("head")); + head->read(path); + } + return head->xMin; +} + +int16_t Font::getYMax() +{ + if (head == NULL) { + head = static_cast(tables.at("head")); + head->read(path); + } + return head->yMax; +} + +int16_t Font::getYMin() +{ + if (head == NULL) { + head = static_cast(tables.at("head")); + head->read(path); + } + return head->yMin; +} diff --git a/lib/fontParser/font.h b/lib/fontParser/font.h new file mode 100644 index 0000000..c10c3cd --- /dev/null +++ b/lib/fontParser/font.h @@ -0,0 +1,62 @@ +#ifndef FILE_H +#define FILE_H + +#include +#include +#include +#include +#include +#include + +#include "tables/table.h" +#include "tables/cmap.h" +#include "tables/hhea.h" +#include "tables/hmtx.h" +#include "tables/head.h" +#include "tables/name.h" + +class Font +{ +public: + enum SfntVersion { + TrueTypeOutlines, + WithCFFData + }; + Font(const std::string& p_path); + ~Font(); + + + bool hasTable(const std::string& tag) const; + Table* getTable(const std::string& tag); + std::list availableTables() const; + std::map getCharCodeToCIDTable(uint32_t start = 0, uint32_t end = 0xffff); + std::map getCharCodeMetrics(uint32_t start = 0, uint32_t end = 0xffff); + uint16_t getUnitsPerEm(); + int16_t getAscent(); + int16_t getDescent(); + int16_t getLineGap(); + int16_t getCaretSlopeRise(); + int16_t getCaretSlopeRun(); + int16_t getXMin(); + int16_t getXMax(); + int16_t getYMin(); + int16_t getYMax(); + std::string getNameField(std::string key); + + SfntVersion version; + uint16_t numberTables; + uint16_t searchRange; + uint16_t entrySelector; + uint16_t rangeShift; + +private: + const std::string path; + std::map tables; + Cmap* cmap; + Hhea* hhea; + Hmtx* hmtx; + Head* head; + Name* name; +}; + +#endif // FILE_H diff --git a/lib/fontParser/main.cpp b/lib/fontParser/main.cpp new file mode 100644 index 0000000..71025fb --- /dev/null +++ b/lib/fontParser/main.cpp @@ -0,0 +1,50 @@ +#include "font.h" +#include +#include +#include +#include "tables/hmtx.h" + +int main(int argc, char **argv) { + Font file(argv[1]); + + std::map cidMap = file.getCharCodeMetrics(0, 0x4ff); + std::map::const_iterator itr = cidMap.begin(); + std::map::const_iterator end = cidMap.end(); + + std::cout << "{\n"; + std::cout << " \"ascent\": " << file.getAscent() << ",\n"; + std::cout << " \"descent\": " << file.getDescent() << ",\n"; + std::cout << " \"lineGap\": " << file.getLineGap() << ",\n"; + std::cout << " \"caretSlopeRise\": " << file.getCaretSlopeRise() << ",\n"; + std::cout << " \"caretSlopeRun\": " << file.getCaretSlopeRun() << ",\n"; + std::cout << " \"unitsPerEm\": " << file.getUnitsPerEm() << ",\n"; + std::cout << " \"fontFamily\": \"" << file.getNameField("fontFamily") << "\",\n"; + std::cout << " \"postScriptName\": \"" << file.getNameField("postScriptName") << "\",\n"; + + std::cout << " \"boundingBox\": {\n"; + std::cout << " \"xMin\": " << file.getXMin() << ",\n"; + std::cout << " \"xMax\": " << file.getXMax() << ",\n"; + std::cout << " \"yMin\": " << file.getYMin() << ",\n"; + std::cout << " \"yMax\": " << file.getYMax() << "\n"; + std::cout << " },\n"; + + std::cout << " \"advanceWidthArray\": [\n "; + int i = 0; + for (; itr != end; ++itr) { + if (i != 0) { + if (i == 16) { + std::cout << ",\n "; + i = 0; + } else { + std::cout << ", "; + } + } + std::cout << itr->second.advanceWidth; + ++i; + } + + std::cout << "\n ]\n"; + std::cout << "}" << std::endl; + + return 0; +} diff --git a/lib/fontParser/tables/CMakeLists.txt b/lib/fontParser/tables/CMakeLists.txt new file mode 100644 index 0000000..48a3d0e --- /dev/null +++ b/lib/fontParser/tables/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.6) +project(tables) + +set(SOURCES + table.cpp + cmap.cpp + hhea.cpp + hmtx.cpp + head.cpp + name.cpp +) + +add_library(tables STATIC ${SOURCES}) diff --git a/lib/fontParser/tables/cmap.cpp b/lib/fontParser/tables/cmap.cpp new file mode 100644 index 0000000..98b703a --- /dev/null +++ b/lib/fontParser/tables/cmap.cpp @@ -0,0 +1,218 @@ +#include "cmap.h" +#include + +Cmap::Cmap(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length): + Table(p_tag, p_checkSum, p_offset, p_length), + initialized(false), + mt(0) +{ +} + +Cmap::~Cmap() +{ + if (initialized) { + delete mt; + } +} + + +void Cmap::read(const std::string& path) +{ + std::ifstream file(path, std::ios::in | std::ios::binary); + file.seekg(offset); + + char * buffer; + buffer = new char[2]; + + file.read(buffer, 2); + version = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + numberOfTables = ntohs(*((uint16_t*) buffer)); + + delete[] buffer; + buffer = new char[8]; + + std::list encodings; + for (int i = 0; i < numberOfTables; ++i) { + file.read(buffer, 8); + + char pb[2] = {buffer[0], buffer[1]}; + char eb[2] = {buffer[2], buffer[3]}; + char ob[4] = {buffer[4], buffer[5], buffer[6], buffer[7]}; + + uint16_t pid = ntohs(*((uint16_t*) pb)); + uint16_t eid = ntohs(*((uint16_t*) eb)); + uint16_t offset = ntohl(*((uint32_t*) ob)); + + //std::cout << "Found encoding platformId " << pid << ", encodingId " << eid << std::endl; + + if (pid == 0 || (pid == 3 && eid == 1)) { + encodings.emplace_back(pid, eid, offset); + } + } + delete[] buffer; + std::list::const_iterator itr = encodings.begin(); + std::list::const_iterator end = encodings.end(); + for (; itr != end; ++itr) { + //std::cout << "Trying platformId " << itr->platformId << ", encodingId " << itr->encodingId << std::endl; + file.seekg(offset + itr->offset); + bool success = true; + MappingTable* table; + try { + table = MappingTable::fromIfStream(file); + } catch (int e) { + success = false; + } + if (success) { + initialized = true; + mt = table; + break; + } + } + file.close(); + if (!initialized) { + //std::cout << "Error reading cmap: no supported encoding format" << std::endl; + throw 3; + } +} + +uint32_t Cmap::getCID(uint32_t charCode) const +{ + return this->mt->getCID(charCode); +} + + +MappingTable * MappingTable::fromIfStream(std::ifstream& file) +{ + uint64_t position = file.tellg(); + char * buffer; + buffer = new char[2]; + file.read(buffer, 2); + uint16_t format = ntohs(*((uint16_t*) buffer)); + + MappingTable* table = NULL; + + if (format >= 8) { + if (format != 14) { + file.read(buffer, 2); //padded .0 in stupid formats + } + delete[] buffer; + buffer = new char[4]; + file.read(buffer, 4); + uint32_t length = ntohl(*((uint32_t*) buffer)); + file.seekg(position); + buffer = new char[length]; + file.read(buffer, length); + } else { + file.read(buffer, 2); + uint16_t length = ntohs(*((uint16_t*) buffer)); + file.seekg(position); + buffer = new char[length]; + file.read(buffer, length); + + if (format == 4) { + table = new Format4(buffer, length); + } + } + + delete[] buffer; + + if (table == NULL) { + std::cout << "Unrecognized format " << format << std::endl; + throw 3; + } + return table; +} + +MappingTable::MappingTable(uint16_t p_f): + format(p_f) +{ + +} + +MappingTable::~MappingTable() +{ +} + + +Format4::Format4(char * data, uint16_t length): + MappingTable(4), + charCodesEndCode(), + segments(0), + glyphIndexArray(0) +{ + char sc[2] = {data[6], data[7]}; + uint16_t segCount = ntohs(*((uint16_t*) sc)) / 2; + segments = new std::vector(segCount); + + int endCodeShift = 14; + int startCodeShift = endCodeShift + segCount * 2 + 2; + int deltaShift = startCodeShift + segCount * 2; + int rangeShift = deltaShift + segCount * 2; + int giaShift = rangeShift + segCount * 2; + int giaLength = (length - giaShift) / 2; + glyphIndexArray = new std::vector(giaLength); +// std::cout << "Segments: " << segCount << ", "; +// std::cout << "Glyphs: " << giaLength << "\n"; +// std::cout << "******************************************" << "\n"; + + for (int i = 0; i < segCount; ++i) { + char cc[2] = {data[2 * i + endCodeShift], data[2 * i + endCodeShift + 1]}; + char sc[2] = {data[2 * i + startCodeShift], data[2 * i + startCodeShift + 1]}; + char dc[2] = {data[2 * i + deltaShift], data[2 * i + deltaShift + 1]}; + char rc[2] = {data[2 * i + rangeShift], data[2 * i + rangeShift + 1]}; + + uint16_t endCharCode = ntohs(*((uint16_t*) cc)); + uint16_t startCharCode = ntohs(*((uint16_t*) sc)); + int16_t delta = ntohs(*((int16_t*) dc)); + uint16_t range = ntohs(*((uint16_t*) rc)); + + SegParams& sp = segments->at(i); + sp.endCode = endCharCode; + sp.startCode = startCharCode; + sp.idDelta = delta; + sp.idRangeOffset = range; + + charCodesEndCode.insert(std::make_pair(endCharCode, i)); +// std::cout << "Segment " << i << ",\t"; +// std::cout << "Start " << startCharCode << ",\t"; +// std::cout << "End " << endCharCode << ",\t"; +// std::cout << "Delta " << delta << ",\t"; +// std::cout << "Range " << range << "\n"; + } +// std::cout << "******************************************" << std::endl;; + + for (int i = 0; i < giaLength; ++i) { + char cc[2] = {data[2 * i + giaShift], data[2 * i + giaShift + 1]}; + uint16_t glyphIndex = ntohs(*((uint16_t*) cc)); + glyphIndexArray->at(i) = glyphIndex; + } +} + +Format4::~Format4() +{ + delete segments; + delete glyphIndexArray; +} + + +uint32_t Format4::getCID(uint32_t charCode) const +{ + uint16_t cid; + uint16_t c = charCode & 0xffff; + std::map::const_iterator itr = charCodesEndCode.lower_bound(c); + uint16_t i = itr->second; + SegParams& seg = segments->at(i); + if (seg.startCode > c) { + return 0; + } + + if (seg.idRangeOffset == 0) { + cid = c + seg.idDelta; + } else { + cid = i + seg.idRangeOffset - segments->size() + c - seg.startCode; + } + + return cid; +} diff --git a/lib/fontParser/tables/cmap.h b/lib/fontParser/tables/cmap.h new file mode 100644 index 0000000..1980b36 --- /dev/null +++ b/lib/fontParser/tables/cmap.h @@ -0,0 +1,66 @@ +#ifndef CMAP_H +#define CMAP_H + +#include "table.h" +#include +#include +#include + +struct Enc { + Enc(uint16_t pid, uint16_t eid, uint32_t off): platformId(pid), encodingId(eid), offset(off) {} + + uint16_t platformId; + uint16_t encodingId; + uint32_t offset; +}; + +class MappingTable { +protected: + MappingTable(uint16_t p_f); + + uint16_t format; + +public: + static MappingTable* fromIfStream(std::ifstream& file); + virtual ~MappingTable(); + virtual uint32_t getCID(uint32_t charCode) const = 0; +}; + +class Format4 : public MappingTable { +public: + Format4(char* data, uint16_t length); + ~Format4(); + + uint32_t getCID(uint32_t charCode) const override; + +private: + struct SegParams { + uint16_t endCode; + uint16_t startCode; + int16_t idDelta; + uint16_t idRangeOffset; + }; + + std::map charCodesEndCode; + std::vector* segments; + std::vector* glyphIndexArray; +}; + +class Cmap : public Table +{ +public: + Cmap(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length); + ~Cmap(); + + void read(const std::string & path) override; + uint32_t getCID(uint32_t charCode) const; + + uint16_t version; + uint16_t numberOfTables; + +private: + bool initialized; + MappingTable* mt; +}; + +#endif // CMAP_H diff --git a/lib/fontParser/tables/head.cpp b/lib/fontParser/tables/head.cpp new file mode 100644 index 0000000..728fffc --- /dev/null +++ b/lib/fontParser/tables/head.cpp @@ -0,0 +1,93 @@ +#include "head.h" +#include + +Head::Head(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length): + Table(p_tag, p_checkSum, p_offset, p_length), + fontRevisionMajor(0), + fontRevisionMinor(0), + flags(0), + unitsPerEm(0), + xMin(0), + yMin(0), + xMax(0), + yMax(0), + macStyle(0), + lowestRecPPEM(0), + fontDirectionHint(0), + indexToLocFormat(0) +{ +} + +Head::~Head() +{ +} + +void Head::read(const std::string& path) +{ + char * buffer; + buffer = new char[2]; + + std::ifstream file(path, std::ios::in | std::ios::binary); + file.seekg(offset); + + file.read(buffer, 2); //version is not interesting, it is always 16.16 fixed point number equals to "1.0"; + file.read(buffer, 2); //version is not interesting, it is always 16.16 fixed point number equals to "1.0"; + + file.read(buffer, 2); + fontRevisionMajor = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + fontRevisionMinor = ntohs(*((uint16_t*) buffer)); + + delete[] buffer; + buffer = new char[4]; + file.read(buffer, 4); //checkSumAdjustment - it's something fishy, no idea what to use it for; + file.read(buffer, 4); //magicNumber, always set to 0x5f0f3cf5; + delete[] buffer; + buffer = new char[2]; + + file.read(buffer, 2); + flags = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + unitsPerEm = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); //creation date is a signed int64 + file.read(buffer, 2); + file.read(buffer, 2); + file.read(buffer, 2); + + file.read(buffer, 2); //last modification date is a signed int64 + file.read(buffer, 2); + file.read(buffer, 2); + file.read(buffer, 2); + + file.read(buffer, 2); + xMin = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + yMin = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + xMax = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + yMax = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + macStyle = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + lowestRecPPEM = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + fontDirectionHint = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + indexToLocFormat = ntohs(*((int16_t*) buffer)); + + //and there is stil uint16 glyph data format, but its always 0; + + file.close(); + delete[] buffer; +} diff --git a/lib/fontParser/tables/head.h b/lib/fontParser/tables/head.h new file mode 100644 index 0000000..4cc09a8 --- /dev/null +++ b/lib/fontParser/tables/head.h @@ -0,0 +1,28 @@ +#ifndef HEAD_H +#define HEAD_H + +#include "table.h" + +class Head : public Table +{ +public: + Head(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length); + ~Head(); + + void read(const std::string & path) override; + + uint16_t fontRevisionMajor; + uint16_t fontRevisionMinor; + uint16_t flags; + uint16_t unitsPerEm; + int16_t xMin; + int16_t yMin; + int16_t xMax; + int16_t yMax; + uint16_t macStyle; + uint16_t lowestRecPPEM; + int16_t fontDirectionHint; + int16_t indexToLocFormat; +}; + +#endif // HEAD_H diff --git a/lib/fontParser/tables/hhea.cpp b/lib/fontParser/tables/hhea.cpp new file mode 100644 index 0000000..4da3309 --- /dev/null +++ b/lib/fontParser/tables/hhea.cpp @@ -0,0 +1,76 @@ +#include "hhea.h" +#include + +Hhea::Hhea(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length): + Table(p_tag, p_checkSum, p_offset, p_length), + ascent(0), + descent(0), + lineGap(0), + advanceWidthMax(0), + minLeftSideBearing(0), + minRightSideBearing(0), + xMaxExtent(0), + caretSlopeRise(0), + caretSlopeRun(0), + caretOffset(0), + numOfLongHorMetrics(0) +{ +} + +Hhea::~Hhea() +{ +} + +void Hhea::read(const std::string& path) +{ + char * buffer; + buffer = new char[2]; + + std::ifstream file(path, std::ios::in | std::ios::binary); + file.seekg(offset); + + file.read(buffer, 2); //version is not interesting, it is always 16.16 fixed point number equals to "1.0"; + file.read(buffer, 2); //version is not interesting, it is always 16.16 fixed point number equals to "1.0"; + + file.read(buffer, 2); + ascent = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + descent = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + lineGap = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + advanceWidthMax = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + minLeftSideBearing = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + minRightSideBearing = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + xMaxExtent = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + caretSlopeRise = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + caretSlopeRun = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); + caretOffset = ntohs(*((int16_t*) buffer)); + + file.read(buffer, 2); //reserved empty field, supposed to be 0; + file.read(buffer, 2); //reserved empty field, supposed to be 0; + file.read(buffer, 2); //reserved empty field, supposed to be 0; + file.read(buffer, 2); //reserved empty field, supposed to be 0; + file.read(buffer, 2); //metricDataFormat, it's supposed to be 0; + + file.read(buffer, 2); + numOfLongHorMetrics = ntohs(*((uint16_t*) buffer)); + + delete[] buffer; + file.close(); +} diff --git a/lib/fontParser/tables/hhea.h b/lib/fontParser/tables/hhea.h new file mode 100644 index 0000000..d094068 --- /dev/null +++ b/lib/fontParser/tables/hhea.h @@ -0,0 +1,27 @@ +#ifndef HHEA_H +#define HHEA_H + +#include "table.h" + +class Hhea : public Table +{ +public: + Hhea(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length); + ~Hhea(); + + void read(const std::string & path) override; + + int16_t ascent; + int16_t descent; + int16_t lineGap; + uint16_t advanceWidthMax; + int16_t minLeftSideBearing; + int16_t minRightSideBearing; + int16_t xMaxExtent; + int16_t caretSlopeRise; + int16_t caretSlopeRun; + int16_t caretOffset; + uint16_t numOfLongHorMetrics; +}; + +#endif // HHEA_H diff --git a/lib/fontParser/tables/hmtx.cpp b/lib/fontParser/tables/hmtx.cpp new file mode 100644 index 0000000..5680cce --- /dev/null +++ b/lib/fontParser/tables/hmtx.cpp @@ -0,0 +1,59 @@ +#include "hmtx.h" +#include + +Hmtx::Hmtx(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length): + Table(p_tag, p_checkSum, p_offset, p_length), + numOfLongHorMetrics(0), + longHorMetric(0) +{ +} + +Hmtx::~Hmtx() +{ + delete longHorMetric; +} + +void Hmtx::read(const std::string& path) +{ + if (numOfLongHorMetrics == 0) { + throw 1; + } + + std::ifstream file(path, std::ios::in | std::ios::binary); + file.seekg(offset); + + char * buffer; + buffer = new char[2]; + + longHorMetric = new std::vector(numOfLongHorMetrics); + + for (int i = 0; i < numOfLongHorMetrics; ++i) { + HMetric& met = longHorMetric->at(i); + + file.read(buffer, 2); + uint16_t aw = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + int16_t lsb = ntohs(*((int16_t*) buffer)); + + met.advanceWidth = aw; + met.leftSideBearing = lsb; + } + file.close(); + delete[] buffer; +} + +Hmtx::HMetric::HMetric(): + advanceWidth(0), + leftSideBearing(0) +{ +} + +Hmtx::HMetric Hmtx::getMetric(uint16_t cid) const +{ + if (cid >= longHorMetric->size()) { + cid = longHorMetric->size() - 1; + } + + return longHorMetric->at(cid); +} diff --git a/lib/fontParser/tables/hmtx.h b/lib/fontParser/tables/hmtx.h new file mode 100644 index 0000000..9a21e41 --- /dev/null +++ b/lib/fontParser/tables/hmtx.h @@ -0,0 +1,29 @@ +#ifndef HMTX_H +#define HMTX_H + +#include "table.h" +#include + +class Hmtx : public Table +{ +public: + Hmtx(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length); + ~Hmtx(); + + uint16_t numOfLongHorMetrics; + + struct HMetric { + HMetric(); + + uint16_t advanceWidth; + int16_t leftSideBearing; + }; + + void read(const std::string & path) override; + HMetric getMetric(uint16_t cid) const; + +private: + std::vector* longHorMetric; +}; + +#endif // HMTX_H diff --git a/lib/fontParser/tables/name.cpp b/lib/fontParser/tables/name.cpp new file mode 100644 index 0000000..b01a97d --- /dev/null +++ b/lib/fontParser/tables/name.cpp @@ -0,0 +1,136 @@ +#include "name.h" +#include +#include +#include +#include +#include + +const std::map Name::nameIds({ + { "copyright", 0 }, + { "fontFamily", 1 }, + { "fontSubfamily", 2 }, + { "uniqueSubfamilyId", 3 }, + { "fullFontName", 4 }, + { "nameTableVersion", 5 }, + { "postScriptName", 6 }, + { "trademarkNotice", 7 }, + { "manufacturerName", 8 }, + { "designerName", 9 }, + { "description", 10 }, + { "vendorURL", 11 }, + { "designerURL", 12 }, + { "licenseDescription", 13 }, + { "licenseURL", 14 }, + + { "preferredFamily", 16 }, + { "preferredSubfamily", 17 }, + { "compatibleFull", 18 }, + { "sampleText", 19 }, + { "postScriptCID", 20 } +}); + +Name::Name(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length): + Table(p_tag, p_checkSum, p_offset, p_length), + names() +{ +} + +Name::~Name() +{ +} + +void Name::read(const std::string& path) +{ + std::ifstream file(path, std::ios::in | std::ios::binary); + file.seekg(offset); + + char * buffer; + buffer = new char[2]; + + file.read(buffer, 2); //format. it is always 0 or 1 for stupid microsoft langTags, but I don't cate, gonna use offset; + file.read(buffer, 2); + uint16_t count = ntohs(*((uint16_t*) buffer)); + + file.read(buffer, 2); + uint32_t storageOffset = offset + ntohs(*((uint16_t*) buffer)); + + std::list list; + std::set ids; + + for (int i = 0; i < count; ++i) { + file.read(buffer, 2); + uint16_t pid = ntohs(*((uint16_t*) buffer)); + file.read(buffer, 2); + uint16_t eid = ntohs(*((uint16_t*) buffer)); + file.read(buffer, 2); + uint16_t lid = ntohs(*((uint16_t*) buffer)); + file.read(buffer, 2); + uint16_t nid = ntohs(*((uint16_t*) buffer)); + file.read(buffer, 2); + uint16_t length = ntohs(*((uint16_t*) buffer)); + file.read(buffer, 2); + uint16_t nameOffset = ntohs(*((uint16_t*) buffer)); + + //std::cout << "Found pid " << pid << ", eid " << eid << ", nid " << nid << std::endl; + + if (ids.find(nid) == ids.end()) { + if ((pid == 0 && (eid == 3 || eid == 4)) || (pid == 3 && eid == 1)) { //screw microsoft, screw apple; + list.emplace_back(pid, eid, lid, nid, length, nameOffset); + ids.insert(nid); + } + } + + } + std::list::const_iterator itr; + for (itr = list.begin(); itr != list.end(); ++itr) { + const NameRecord& nr = *itr; + file.seekg(storageOffset + nr.offset); + + if ((nr.platformId == 0 && (nr.encodingId == 3 || nr.encodingId == 4)) || (nr.platformId == 3 && nr.encodingId == 1)) { + char16_t buf[nr.length / 2]; + for (int i = 0; i < nr.length / 2; ++i) { + file.read(buffer, 2); + buf[i] = ntohs(*((char16_t*) buffer)); + } + std::u16string string(buf, nr.length / 2); + std::wstring_convert, char16_t> convert; + names.insert(std::make_pair(nr.nameId, convert.to_bytes(string))); + } + + } + + delete[] buffer; + file.close(); +} + +std::string Name::getRecord(uint16_t id) const +{ + std::string res(""); + std::map::const_iterator itr = names.find(id); + if (itr != names.end()) { + res = itr->second; + } + + return res; +} + +std::string Name::getRecord(const std::string& name) const +{ + std::map::const_iterator itr = nameIds.find(name); + if (itr == nameIds.end()) { + return ""; + } else { + return getRecord(itr->second); + } +} + + +NameRecord::NameRecord(uint16_t pid, uint16_t eid, uint16_t lid, uint16_t nid, uint16_t p_l, uint16_t p_o): + platformId(pid), + encodingId(eid), + languageId(lid), + nameId(nid), + length(p_l), + offset(p_o) +{ +} diff --git a/lib/fontParser/tables/name.h b/lib/fontParser/tables/name.h new file mode 100644 index 0000000..fa23338 --- /dev/null +++ b/lib/fontParser/tables/name.h @@ -0,0 +1,35 @@ +#ifndef NAME_H +#define NAME_H + +#include +#include + +#include "table.h" + +class Name : public Table +{ +public: + Name(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length); + ~Name(); + + void read(const std::string & path) override; + std::string getRecord(uint16_t id) const; + std::string getRecord(const std::string& name) const; + +private: + std::map names; + + static const std::map nameIds; +}; + +struct NameRecord { + NameRecord(uint16_t pid, uint16_t eid, uint16_t lid, uint16_t nid, uint16_t p_l, uint16_t p_o); + uint16_t platformId; + uint16_t encodingId; + uint16_t languageId; + uint16_t nameId; + uint16_t length; + uint16_t offset; +}; + +#endif // NAME_H diff --git a/lib/fontParser/tables/table.cpp b/lib/fontParser/tables/table.cpp new file mode 100644 index 0000000..cc9bed3 --- /dev/null +++ b/lib/fontParser/tables/table.cpp @@ -0,0 +1,56 @@ +#include "table.h" +#include + +#include "cmap.h" +#include "hhea.h" +#include "hmtx.h" +#include "head.h" +#include "name.h" + +Table::Table(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length): + tag(p_tag), + checkSum(p_checkSum), + offset(p_offset), + length(p_length) +{ +} + +Table::~Table() +{ +} + +Table* Table::fromIfStream(std::ifstream& stream) +{ + char * buffer; + buffer = new char[4]; + stream.read(buffer, 4); + std::string tag(buffer, 4); + + stream.read(buffer, 4); + uint32_t cs = ntohl(*((uint32_t*) buffer)); + + stream.read(buffer, 4); + uint32_t offset = ntohl(*((uint32_t*) buffer)); + + stream.read(buffer, 4); + uint32_t l = ntohl(*((uint32_t*) buffer)); + + if (tag == "cmap") { + return new Cmap(tag, cs, offset, l); + } else if (tag == "hhea") { + return new Hhea(tag, cs, offset, l); + } else if (tag == "hmtx") { + return new Hmtx(tag, cs, offset, l); + } else if (tag == "head") { + return new Head(tag, cs, offset, l); + } else if (tag == "name") { + return new Name(tag, cs, offset, l); + } else { + return new Table(tag, cs, offset, l); + } +} + +void Table::read(const std::string& path) +{ + std::cout << "table with type " << tag << " is not supported yet" << std::endl; +} diff --git a/lib/fontParser/tables/table.h b/lib/fontParser/tables/table.h new file mode 100644 index 0000000..b673a76 --- /dev/null +++ b/lib/fontParser/tables/table.h @@ -0,0 +1,26 @@ +#ifndef TABLE_H +#define TABLE_H + +#include +#include +#include +#include + +class Table +{ +public: + Table(const std::string& p_tag, uint32_t p_checkSum, uint32_t p_offset, uint32_t p_length); + virtual ~Table(); + + const std::string tag; + const uint32_t checkSum; + const uint32_t offset; + const uint32_t length; + + static Table* fromIfStream(std::ifstream& stream); + + virtual void read(const std::string& path); +}; + + +#endif // TABLE_H diff --git a/lib/tools/CMakeLists.txt b/lib/tools/CMakeLists.txt new file mode 100644 index 0000000..8462e7b --- /dev/null +++ b/lib/tools/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.12) +project(tools) + +set(HEADERS + file.h +) + +set(SOURCES + file.cpp +) + +add_library(tools ${HEADERS} ${SOURCES}) + +target_link_libraries(tools wType) + diff --git a/lib/tools/file.cpp b/lib/tools/file.cpp new file mode 100644 index 0000000..f58f8f4 --- /dev/null +++ b/lib/tools/file.cpp @@ -0,0 +1,115 @@ +#include "file.h" +#include + +T::File::File(const W::String& p_path): + path(p_path) +{ +} + +T::File::~File() +{ +} + +const W::String & T::File::getPath() const +{ + return path; +} + +W::String T::File::suffix() const +{ + uint64_t dotPos = path.findLastOf(W::String(u".")); + if (dotPos > path.findLastOf(W::String(u"/"))) { + return path.substr(dotPos + 1); + } else { + return W::String(u""); + } +} + +bool T::File::readDirectoryRecursive(const W::String& path, std::list* result) +{ + DIR *d; + struct dirent *dir; + d = opendir(path.toString().c_str()); + bool success = false; + if (d) { + while ((dir = readdir(d)) != NULL) { + if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) { + continue; + } + W::String d_path = path + W::String(u"/") + W::String(std::string(dir->d_name)); + + struct stat st; + int err = lstat(d_path.toString().c_str(), &st); + if (err == 0) { + success = true; + switch (st.st_mode & S_IFMT) { + case S_IFDIR: + success = File::readDirectoryRecursive(d_path, result); + break; + case S_IFREG: + result->emplace_back(d_path); + break; + } + } else { + std::cout << "unable read description of file " << d_path.toString() << ". "; + switch (errno) { + case EACCES: + std::cout << "Search permission is denied for one of the directories in the path prefix of path"; + break; + case EFAULT: + std::cout << "Bad address"; + break; + case ELOOP: + std::cout << "Too many symbolic links encountered while traversing the path"; + break; + case ENAMETOOLONG: + std::cout << "path is too long"; + break; + case ENOENT: + std::cout << "A component of path does not exist, or path is an empty string"; + break; + case ENOMEM: + std::cout << "Out of memory"; + break; + case ENOTDIR: + std::cout << "A component of the path prefix of path is not a directory"; + break; + case EOVERFLOW: + std::cout << "EOVERFLOW error"; + break; + default: + std::cout << "undefined error"; + } + std::cout << std::endl; + } + } + + closedir(d); + } else { + std::cout << "unable to open a directory " << path.toString() << std::endl; + } + + return success; +} + +W::String T::File::parentDirectory() const +{ + uint64_t lastSlashPos = path.findLastOf(W::String(u"/")); + W::String fPath = path.substr(0, lastSlashPos); + uint64_t pSpashPos = fPath.findLastOf(W::String(u"/")); + return fPath.substr(pSpashPos + 1); +} + +W::String T::File::name() const +{ + uint64_t slashPos = path.findLastOf(W::String(u"/")); + return path.substr(slashPos + 1); + +} + +W::String T::File::nameWithoutSuffix() const +{ + W::String nws = name(); + uint64_t dotPos = path.findLastOf(W::String(u".")); + return nws.substr(0, dotPos); +} diff --git a/lib/tools/file.h b/lib/tools/file.h new file mode 100644 index 0000000..9efbf91 --- /dev/null +++ b/lib/tools/file.h @@ -0,0 +1,33 @@ +#ifndef TOOLS_FILE_H +#define TOOLS_FILE_H + +#include + +#include +#include +#include +#include + +namespace T { + +class File +{ +public: + File(const W::String& p_path); + ~File(); + + const W::String& getPath() const; + W::String suffix() const; + W::String nameWithoutSuffix() const; + W::String name() const; + W::String parentDirectory() const; + + static bool readDirectoryRecursive(const W::String& path, std::list* result); + +private: + W::String path; +}; + +} + +#endif // TOOLS_FILE_H diff --git a/lib/utils/CMakeLists.txt b/lib/utils/CMakeLists.txt new file mode 100644 index 0000000..df5f5b1 --- /dev/null +++ b/lib/utils/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 2.8.12) +project(utils) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + defines.h + exception.h + signalcatcher.h +) + +set(SOURCES + exception.cpp + signalcatcher.cpp +) + +add_library(utils ${HEADERS} ${SOURCES}) + +target_link_libraries(utils Qt5::Core) diff --git a/lib/utils/defines.h b/lib/utils/defines.h new file mode 100644 index 0000000..82acdce --- /dev/null +++ b/lib/utils/defines.h @@ -0,0 +1,9 @@ +#ifndef DEFINES_UTILS_H +#define DEFINES_UTILS_H + + +#define handler(HANDLER) \ + void _h_##HANDLER(const W::Event& ev) {h_##HANDLER(ev);}\ + virtual void h_##HANDLER(const W::Event& ev);\ + +#endif diff --git a/lib/utils/exception.cpp b/lib/utils/exception.cpp new file mode 100644 index 0000000..92b9f6e --- /dev/null +++ b/lib/utils/exception.cpp @@ -0,0 +1,14 @@ +#include "exception.h" + +Utils::Exception::Exception() +{ +} + +Utils::Exception::~Exception() +{ +} + +const char* Utils::Exception::what() const noexcept( true ) +{ + return getMessage().c_str(); +} \ No newline at end of file diff --git a/lib/utils/exception.h b/lib/utils/exception.h new file mode 100644 index 0000000..205c165 --- /dev/null +++ b/lib/utils/exception.h @@ -0,0 +1,22 @@ +#ifndef EXCEPTION_H +#define EXCEPTION_H + +#include +#include + +namespace Utils +{ + class Exception: + public std::exception + { + public: + Exception(); + virtual ~Exception(); + + virtual std::string getMessage() const = 0; + + const char* what() const noexcept( true ); + }; +} + +#endif // EXCEPTION_H diff --git a/lib/utils/signalcatcher.cpp b/lib/utils/signalcatcher.cpp new file mode 100644 index 0000000..cd47d70 --- /dev/null +++ b/lib/utils/signalcatcher.cpp @@ -0,0 +1,59 @@ +#include "signalcatcher.h" +#include +#include +#include + +int W::SignalCatcher::sigintFd[2] = {0,0}; + +W::SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent): + QObject(parent), + app(p_app) +{ + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd)) + { + qFatal("Couldn't create INT socketpair"); + } + + if (setup_unix_signal_handlers() != 0) + { + qFatal("Couldn't install unix handlers"); + } + + snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this); + connect(snInt, SIGNAL(activated(int)), this, SLOT(handleSigInt())); +} + +W::SignalCatcher::~SignalCatcher() +{} + +void W::SignalCatcher::handleSigInt() +{ + snInt->setEnabled(false); + char tmp; + ::read(sigintFd[1], &tmp, sizeof(tmp)); + + app->quit(); + + snInt->setEnabled(true); +} + +void W::SignalCatcher::intSignalHandler(int unused) +{ + char a = 1; + ::write(sigintFd[0], &a, sizeof(a)); +} + +int W::SignalCatcher::setup_unix_signal_handlers() +{ + struct sigaction s_int; + + s_int.sa_handler = SignalCatcher::intSignalHandler; + sigemptyset(&s_int.sa_mask); + s_int.sa_flags = 0; + s_int.sa_flags |= SA_RESTART; + + if (sigaction(SIGINT, &s_int, 0) > 0) + return 1; + + return 0; +} diff --git a/lib/utils/signalcatcher.h b/lib/utils/signalcatcher.h new file mode 100644 index 0000000..55aead6 --- /dev/null +++ b/lib/utils/signalcatcher.h @@ -0,0 +1,33 @@ +#ifndef SIGNALCATCHER_H +#define SIGNALCATCHER_H + +#include +#include +#include + +namespace W +{ + class SignalCatcher: public QObject + { + Q_OBJECT + + public: + SignalCatcher(QCoreApplication *p_app, QObject *parent = 0); + ~SignalCatcher(); + + static void intSignalHandler(int unused); + + public slots: + void handleSigInt(); + + private: + QCoreApplication *app; + static int sigintFd[2]; + + QSocketNotifier *snInt; + + static int setup_unix_signal_handlers(); + }; +} + +#endif // SIGNALCATCHER_H diff --git a/lib/wContainer/CMakeLists.txt b/lib/wContainer/CMakeLists.txt new file mode 100644 index 0000000..1575668 --- /dev/null +++ b/lib/wContainer/CMakeLists.txt @@ -0,0 +1,2 @@ +cmake_minimum_required(VERSION 2.8.12) + diff --git a/lib/wContainer/order.h b/lib/wContainer/order.h new file mode 100644 index 0000000..a80e7b4 --- /dev/null +++ b/lib/wContainer/order.h @@ -0,0 +1,136 @@ +#ifndef ORDER_H +#define ORDER_H + +#include +#include + +#include + +namespace W +{ + template > + class Order + { + + class Duplicates: + public Utils::Exception + { + public: + Duplicates():Exception(){} + + std::string getMessage() const{return "Inserting element duplicates existing";} + }; + + class NotFound: + public Utils::Exception + { + public: + NotFound():Exception(){} + + std::string getMessage() const{return "Erasing element haven't been found";} + }; + + protected: + typedef std::list List; + + public: + typedef typename List::size_type size_type; + typedef typename List::const_iterator const_iterator; + typedef typename List::iterator iterator; + + protected: + typedef std::map Map; + typedef typename Map::const_iterator m_const_itr; + typedef typename Map::iterator m_itr; + + public: + Order(): + order(), + r_map() + {} + ~Order() {}; + + size_type size() const { + return order.size(); + } + + void push_back(data_type element) { + m_const_itr m_itr = r_map.find(element); + if (m_itr != r_map.end()) { + throw Duplicates(); + } + + const_iterator itr = order.insert(order.end(), element); + r_map.insert(std::make_pair(element, itr)); + } + + void erase(data_type element) { + m_const_itr itr = r_map.find(element); + if (itr == r_map.end()) { + throw NotFound(); + } + order.erase(itr->second); + r_map.erase(itr); + + } + + void clear() { + order.clear(); + r_map.clear(); + } + + void insert(const_iterator pos, data_type element) { + m_const_itr m_itr = r_map.find(element); + if (m_itr != r_map.end()) { + throw Duplicates(); + } + + const_iterator itr = order.insert(pos, element); + r_map.insert(std::make_pair(element, itr)); + } + + void insert(iterator pos, data_type element) { + m_const_itr m_itr = r_map.find(element); + if (m_itr != r_map.end()) { + throw Duplicates(); + } + + const_iterator itr = order.insert(pos, element); + r_map.insert(std::make_pair(element, itr)); + } + + const_iterator find(data_type element) const { + m_const_itr itr = r_map.find(element); + + if (itr == r_map.end()) { + return end(); + } else { + return itr->second; + } + } + + const_iterator begin() const { + return order.begin(); + } + + const_iterator end() const { + return order.end(); + } + + iterator begin() { + return order.begin(); + } + + iterator end() { + return order.end(); + } + + private: + List order; + Map r_map; + }; +} + + + +#endif // ORDER_H diff --git a/lib/wController/CMakeLists.txt b/lib/wController/CMakeLists.txt new file mode 100644 index 0000000..0e2b052 --- /dev/null +++ b/lib/wController/CMakeLists.txt @@ -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) + diff --git a/lib/wController/attributes.cpp b/lib/wController/attributes.cpp new file mode 100644 index 0000000..b0a5436 --- /dev/null +++ b/lib/wController/attributes.cpp @@ -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(element); + const W::Uint64& type = static_cast(evc.at(u"type")); + const W::Address& addr = static_cast(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(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(); +} diff --git a/lib/wController/attributes.h b/lib/wController/attributes.h new file mode 100644 index 0000000..8a64b8d --- /dev/null +++ b/lib/wController/attributes.h @@ -0,0 +1,43 @@ +#ifndef ATTRIBUTES_H +#define ATTRIBUTES_H + +#include "vocabulary.h" + +#include + +#include + +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 Map; + typedef std::map RMap; + + static uint64_t counter; + + Map* attributes; + RMap* reversed; + }; +} + +#endif // ATTRIBUTES_H diff --git a/lib/wController/catalogue.cpp b/lib/wController/catalogue.cpp new file mode 100644 index 0000000..28bc2d6 --- /dev/null +++ b/lib/wController/catalogue.cpp @@ -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::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(ev.getData()); + const W::Vector& ord = static_cast(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(ord.at(i)); + addElement(id); + } + hasData = true; + + emit data(); +} + +void C::Catalogue::h_addElement(const W::Event& ev) +{ + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::Uint64& id = static_cast(vc.at(u"id")); + if (vc.has(u"before")) { + const W::Uint64& before = static_cast(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(ev.getData()); + const W::Uint64& id = static_cast(vc.at(u"id")); + + removeElement(id); +} + +void C::Catalogue::removeElement(const W::Uint64& id) +{ + W::Order::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(ev.getData()); + const W::Uint64& id = static_cast(vc.at(u"id")); + + W::Order::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(vc.at(u"before")); + + W::Order::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(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"}); +} diff --git a/lib/wController/catalogue.h b/lib/wController/catalogue.h new file mode 100644 index 0000000..323b9f4 --- /dev/null +++ b/lib/wController/catalogue.h @@ -0,0 +1,63 @@ +#ifndef CATALOGUE_H +#define CATALOGUE_H + +/** + * @todo write docs + */ +#include "controller.h" + +#include +#include +#include +#include + +#include + +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 order; + + private: + bool hasSorting; + bool hasFilter; + bool hasData; + W::Vocabulary* sorting; + W::Vocabulary* filter; + + static uint64_t counter; + }; +} + +#endif // CATALOGUE_H diff --git a/lib/wController/collection.cpp b/lib/wController/collection.cpp new file mode 100644 index 0000000..bf6007c --- /dev/null +++ b/lib/wController/collection.cpp @@ -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::const_iterator itr = waitingElements.begin(); + std::set::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::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(sender()); + + std::set::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 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 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); +} diff --git a/lib/wController/collection.h b/lib/wController/collection.h new file mode 100644 index 0000000..f47450d --- /dev/null +++ b/lib/wController/collection.h @@ -0,0 +1,52 @@ +#ifndef COLLECTION_H +#define COLLECTION_H + +#include "catalogue.h" +#include "vocabulary.h" + +#include + +#include +#include + +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 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 Elements; + Elements elements; + std::set waitingElements; + bool hasData; + + void addChildVocabulary(const W::Uint64& id); + + private slots: + void onChildVCData(); + + }; + +} + +#endif // COLLECTION_H diff --git a/lib/wController/controller.cpp b/lib/wController/controller.cpp new file mode 100644 index 0000000..5fe4572 --- /dev/null +++ b/lib/wController/controller.cpp @@ -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(event.getData()); + properties = static_cast(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; +} diff --git a/lib/wController/controller.h b/lib/wController/controller.h new file mode 100644 index 0000000..1749093 --- /dev/null +++ b/lib/wController/controller.h @@ -0,0 +1,82 @@ +#ifndef CONTROLLER_H +#define CONTROLLER_H + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 HList; + typedef W::Order 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 diff --git a/lib/wController/controllerstring.cpp b/lib/wController/controllerstring.cpp new file mode 100644 index 0000000..ac27e53 --- /dev/null +++ b/lib/wController/controllerstring.cpp @@ -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(ev.getData()); + + data = static_cast(vc.at(u"data")); + + emit change(QString(data.toString().c_str())); + emit modification(data); +} diff --git a/lib/wController/controllerstring.h b/lib/wController/controllerstring.h new file mode 100644 index 0000000..86af3e7 --- /dev/null +++ b/lib/wController/controllerstring.h @@ -0,0 +1,33 @@ +#ifndef CONTROLLER_STRING_H +#define CONTROLLER_STRING_H + +#include "controller.h" + +#include +#include +#include + +#include + +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 diff --git a/lib/wController/list.cpp b/lib/wController/list.cpp new file mode 100644 index 0000000..3f0945c --- /dev/null +++ b/lib/wController/list.cpp @@ -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(ev.getData()); + const W::Vector& edata = static_cast(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(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); +} diff --git a/lib/wController/list.h b/lib/wController/list.h new file mode 100644 index 0000000..f924a47 --- /dev/null +++ b/lib/wController/list.h @@ -0,0 +1,36 @@ +#ifndef CONTROLLER_LIST_H +#define CONTROLLER_LIST_H + +#include "controller.h" + +#include +#include +#include +#include + +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 diff --git a/lib/wController/vocabulary.cpp b/lib/wController/vocabulary.cpp new file mode 100644 index 0000000..66866d2 --- /dev/null +++ b/lib/wController/vocabulary.cpp @@ -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(ev.getData()); + const W::Vocabulary& e_data = static_cast(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(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(ev.getData()); + + const W::Vector& erase = static_cast(vc.at(u"erase")); + const W::Vocabulary& insert = static_cast(vc.at(u"insert")); + + int eSize = erase.length(); + for (int i = 0; i < eSize; ++i) { + const W::String& key = static_cast(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(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); +} diff --git a/lib/wController/vocabulary.h b/lib/wController/vocabulary.h new file mode 100644 index 0000000..9765c2b --- /dev/null +++ b/lib/wController/vocabulary.h @@ -0,0 +1,49 @@ +#ifndef CONTROLLER_VOCABULARY_H +#define CONTROLLER_VOCABULARY_H + +#include "controller.h" + +#include +#include +#include +#include +#include + +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 diff --git a/lib/wDatabase/CMakeLists.txt b/lib/wDatabase/CMakeLists.txt new file mode 100644 index 0000000..06189bc --- /dev/null +++ b/lib/wDatabase/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 2.8.12) +project(database) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + database.h + resourcecache.h +) + +set(SOURCES + database.cpp + resourcecache.cpp +) + + +add_library(wDatabase STATIC ${HEADERS} ${SOURCES}) + +target_link_libraries(wDatabase Qt5::Core) +target_link_libraries(wDatabase lmdb) +target_link_libraries(wDatabase wType) +target_link_libraries(wDatabase wModel) diff --git a/lib/wDatabase/database.cpp b/lib/wDatabase/database.cpp new file mode 100644 index 0000000..0a230e8 --- /dev/null +++ b/lib/wDatabase/database.cpp @@ -0,0 +1,230 @@ +#include "database.h" + +#include +#include + +#include +#include + +Database::Database(const W::String& dbName, QObject* parent): + M::ICatalogue(W::Address({dbName}), parent), + name(dbName), + opened(false), + environment(lmdb::env::create()), + dbi(0), + elements() +{ +} + +Database::~Database() +{ +} + +void Database::open() +{ + if (!opened) { + checkDirAndOpenEnvironment(); + index(); + opened = true; + } +} + +void Database::addIndex(const W::String& fieldName, W::Object::objectType fieldType) +{ + ICatalogue::addIndex(fieldName, fieldType); + + if (opened) { + lmdb::txn rtxn = lmdb::txn::begin(environment, nullptr, MDB_RDONLY); + lmdb::cursor cursor = lmdb::cursor::open(rtxn, dbi); + lmdb::val key; + lmdb::val value; + while (cursor.get(key, value, MDB_NEXT)) { + uint64_t iKey = *((uint64_t*) key.data()); + W::ByteArray ba(value.size()); + ba.fill(value.data(), value.size()); + W::Vocabulary* wVal = static_cast(W::Object::fromByteArray(ba)); + + IndexMap::const_iterator itr = indexes.find(fieldName); + itr->second->add(wVal->at(itr->first), iKey); + + delete wVal; + } + cursor.close(); + rtxn.abort(); + } +} + +uint64_t Database::addElement(const W::Vocabulary& record) +{ + if (!opened) { + throw 6; //TODO + } + uint64_t id = ICatalogue::addElement(record); + + elements.insert(id); + + int size = record.size(); + W::ByteArray ba(size + 1); + ba.push8(record.getType()); + record.serialize(ba); + + lmdb::val key((uint8_t*) &id, 8); + lmdb::val value(ba.getData(), ba.size()); + lmdb::txn wTrans = lmdb::txn::begin(environment); + dbi.put(wTrans, key, value); + wTrans.commit(); + + return id; +} + + +void Database::checkDirAndOpenEnvironment() +{ + int state1 = mkdir("./db", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (state1 != 0 && errno != EEXIST) { + emit serviceMessage("Failed to create a root database folder"); + throw 1; + } + + W::String path("./db/"); + path += name; + + int state2 = mkdir(path.toString().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (state2 != 0 && errno != EEXIST) { + emit serviceMessage(QString("Failed to create ") + name.toString().c_str() + " database folder"); + throw 1; + } + + environment.set_mapsize(1UL * 1024UL * 1024UL * 1024UL); + environment.set_max_dbs(10); + environment.open(path.toString().c_str(), 0, 0664); + + lmdb::txn wTrans = lmdb::txn::begin(environment); + dbi = lmdb::dbi::open(wTrans, "main", MDB_CREATE | MDB_INTEGERKEY); + wTrans.commit(); +} + +void Database::index() +{ + lmdb::txn rtxn = lmdb::txn::begin(environment, nullptr, MDB_RDONLY); + lmdb::cursor cursor = lmdb::cursor::open(rtxn, dbi); + lmdb::val key; + lmdb::val value; + while (cursor.get(key, value, MDB_NEXT)) { + uint64_t iKey = *((uint64_t*) key.data()); + W::ByteArray ba(value.size()); + ba.fill(value.data(), value.size()); + W::Vocabulary* wVal = static_cast(W::Object::fromByteArray(ba)); + ICatalogue::addElement(*wVal); + + elements.insert(iKey); + delete wVal; + } + cursor.close(); + rtxn.abort(); + + emit countChange(elements.size()); +} + +W::Vocabulary* Database::getElement(uint64_t id) +{ + lmdb::txn rtxn = lmdb::txn::begin(environment, nullptr, MDB_RDONLY); + lmdb::val key((uint8_t*) &id, 8); + lmdb::val value; + + if (dbi.get(rtxn, key, value)) { + W::ByteArray ba(value.size()); + ba.fill(value.data(), value.size()); + + W::Vocabulary* wVal = static_cast(W::Object::fromByteArray(ba)); + rtxn.abort(); + + return wVal; + } else { + rtxn.abort(); + throw 3; + } +} + +std::set Database::getAll() const +{ + return elements; +} + +void Database::removeElement(uint64_t id) +{ + if (!opened) { + throw 6; //TODO + } + ICatalogue::removeElement(id); + + lmdb::txn transaction = lmdb::txn::begin(environment); + lmdb::val key((uint8_t*) &id, 8); + dbi.del(transaction, key); + transaction.commit(); + elements.erase(id); +} + +void Database::clear() +{ + if (!opened) { + throw 6; //TODO + } + M::ICatalogue::clear(); + + lmdb::txn transaction = lmdb::txn::begin(environment); + dbi.drop(transaction); + transaction.commit(); +} + +void Database::addModel(M::Model* model) +{ + connect(model, SIGNAL(subscribersCountChange(uint64_t)), this, SLOT(onChildSubscribersCountChange(uint64_t))); + + M::ICatalogue::addModel(model); +} + +void Database::removeModel(M::Model* model) +{ + disconnect(model, SIGNAL(subscribersCountChange(uint64_t)), this, SLOT(onChildSubscribersCountChange(uint64_t))); + + M::ICatalogue::removeModel(model); +} + + +void Database::onChildSubscribersCountChange(uint64_t count) +{ + if (count == 0) { + M::Model* model = static_cast(sender()); + + removeModel(model); + emit serviceMessage(QString("Unregistered model ") + model->getAddress().toString().c_str() + " because there no subscribers left"); + + model->deleteLater(); + } +} + +void Database::modifyElement(uint64_t id, const W::Vocabulary& newValue) +{ + if (!opened) { + throw 6; //TODO + } + int size = newValue.size(); + W::ByteArray ba(size + 1); + + ba.push8(newValue.getType()); + newValue.serialize(ba); + lmdb::val key((uint8_t*) &id, 8); + lmdb::val value(ba.getData(), ba.size()); + lmdb::txn wTrans = lmdb::txn::begin(environment); + dbi.put(wTrans, key, value); + wTrans.commit(); + +} + +uint64_t Database::size() const +{ + return elements.size(); +} + + diff --git a/lib/wDatabase/database.h b/lib/wDatabase/database.h new file mode 100644 index 0000000..50e3beb --- /dev/null +++ b/lib/wDatabase/database.h @@ -0,0 +1,56 @@ +#ifndef DATABASE_H +#define DATABASE_H + +#include "lmdb++.h" + +#include +#include + +#include + +#include +#include + +class Database: public M::ICatalogue +{ + Q_OBJECT + class AbstractIndex; +public: + Database(const W::String& dbName, QObject* parent = 0); + ~Database(); + + void open(); + + uint64_t addElement(const W::Vocabulary& record) override; + W::Vocabulary* getElement(uint64_t id) override; + void removeElement(uint64_t id) override; + void clear() override; + void modifyElement(uint64_t id, const W::Vocabulary & newValue) override; + uint64_t size() const override; + + void addIndex(const W::String& fieldName, W::Object::objectType fieldType) override; + + void addModel(M::Model* model); + void removeModel(M::Model* model); + +protected: + std::set getAll() const override; + +private: + void checkDirAndOpenEnvironment(); + void index(); + +private slots: + void onChildSubscribersCountChange(uint64_t count); + +public: + const W::String name; + +private: + bool opened; + lmdb::env environment; + lmdb::dbi dbi; + std::set elements; +}; + +#endif // DATABASE_H diff --git a/lib/wDatabase/lmdb++.h b/lib/wDatabase/lmdb++.h new file mode 100644 index 0000000..ab75f8c --- /dev/null +++ b/lib/wDatabase/lmdb++.h @@ -0,0 +1,1913 @@ +/* This is free and unencumbered software released into the public domain. */ + +#ifndef LMDBXX_H +#define LMDBXX_H + +/** + * - C++11 wrapper for LMDB. + * + * @author Arto Bendiken + * @see https://sourceforge.net/projects/lmdbxx/ + */ + +#ifndef __cplusplus +#error " requires a C++ compiler" +#endif + +#if __cplusplus < 201103L +#if !defined(_MSC_VER) || _MSC_VER < 1900 +#error " requires a C++11 compiler (CXXFLAGS='-std=c++11')" +#endif // _MSC_VER check +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#include /* for MDB_*, mdb_*() */ + +#ifdef LMDBXX_DEBUG +#include /* for assert() */ +#endif +#include /* for std::size_t */ +#include /* for std::snprintf() */ +#include /* for std::strlen() */ +#include /* for std::runtime_error */ +#include /* for std::string */ +#include /* for std::is_pod<> */ + +namespace lmdb { + using mode = mdb_mode_t; +} + +//////////////////////////////////////////////////////////////////////////////// +/* Error Handling */ + +namespace lmdb { + class error; + class logic_error; + class fatal_error; + class runtime_error; + class key_exist_error; + class not_found_error; + class corrupted_error; + class panic_error; + class version_mismatch_error; + class map_full_error; + class bad_dbi_error; +} + +/** + * Base class for LMDB exception conditions. + * + * @see http://symas.com/mdb/doc/group__errors.html + */ +class lmdb::error : public std::runtime_error { +protected: + const int _code; + +public: + /** + * Throws an error based on the given LMDB return code. + */ + [[noreturn]] static inline void raise(const char* origin, int rc); + + /** + * Constructor. + */ + error(const char* const origin, + const int rc) noexcept + : runtime_error{origin}, + _code{rc} {} + + /** + * Returns the underlying LMDB error code. + */ + int code() const noexcept { + return _code; + } + + /** + * Returns the origin of the LMDB error. + */ + const char* origin() const noexcept { + return runtime_error::what(); + } + + /** + * Returns the underlying LMDB error code. + */ + virtual const char* what() const noexcept { + static thread_local char buffer[1024]; + std::snprintf(buffer, sizeof(buffer), + "%s: %s", origin(), ::mdb_strerror(code())); + return buffer; + } +}; + +/** + * Base class for logic error conditions. + */ +class lmdb::logic_error : public lmdb::error { +public: + using error::error; +}; + +/** + * Base class for fatal error conditions. + */ +class lmdb::fatal_error : public lmdb::error { +public: + using error::error; +}; + +/** + * Base class for runtime error conditions. + */ +class lmdb::runtime_error : public lmdb::error { +public: + using error::error; +}; + +/** + * Exception class for `MDB_KEYEXIST` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#ga05dc5bbcc7da81a7345bd8676e8e0e3b + */ +class lmdb::key_exist_error final : public lmdb::runtime_error { +public: + using runtime_error::runtime_error; +}; + +/** + * Exception class for `MDB_NOTFOUND` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#gabeb52e4c4be21b329e31c4add1b71926 + */ +class lmdb::not_found_error final : public lmdb::runtime_error { +public: + using runtime_error::runtime_error; +}; + +/** + * Exception class for `MDB_CORRUPTED` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#gaf8148bf1b85f58e264e57194bafb03ef + */ +class lmdb::corrupted_error final : public lmdb::fatal_error { +public: + using fatal_error::fatal_error; +}; + +/** + * Exception class for `MDB_PANIC` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#gae37b9aedcb3767faba3de8c1cf6d3473 + */ +class lmdb::panic_error final : public lmdb::fatal_error { +public: + using fatal_error::fatal_error; +}; + +/** + * Exception class for `MDB_VERSION_MISMATCH` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#ga909b2db047fa90fb0d37a78f86a6f99b + */ +class lmdb::version_mismatch_error final : public lmdb::fatal_error { +public: + using fatal_error::fatal_error; +}; + +/** + * Exception class for `MDB_MAP_FULL` errors. + * + * @see http://symas.com/mdb/doc/group__errors.html#ga0a83370402a060c9175100d4bbfb9f25 + */ +class lmdb::map_full_error final : public lmdb::runtime_error { +public: + using runtime_error::runtime_error; +}; + +/** + * Exception class for `MDB_BAD_DBI` errors. + * + * @since 0.9.14 (2014/09/20) + * @see http://symas.com/mdb/doc/group__errors.html#gab4c82e050391b60a18a5df08d22a7083 + */ +class lmdb::bad_dbi_error final : public lmdb::runtime_error { +public: + using runtime_error::runtime_error; +}; + +inline void +lmdb::error::raise(const char* const origin, + const int rc) { + switch (rc) { + case MDB_KEYEXIST: throw key_exist_error{origin, rc}; + case MDB_NOTFOUND: throw not_found_error{origin, rc}; + case MDB_CORRUPTED: throw corrupted_error{origin, rc}; + case MDB_PANIC: throw panic_error{origin, rc}; + case MDB_VERSION_MISMATCH: throw version_mismatch_error{origin, rc}; + case MDB_MAP_FULL: throw map_full_error{origin, rc}; +#ifdef MDB_BAD_DBI + case MDB_BAD_DBI: throw bad_dbi_error{origin, rc}; +#endif + default: throw lmdb::runtime_error{origin, rc}; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Metadata */ + +namespace lmdb { + // TODO: mdb_version() + // TODO: mdb_strerror() +} + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Environment */ + +namespace lmdb { + static inline void env_create(MDB_env** env); + static inline void env_open(MDB_env* env, + const char* path, unsigned int flags, mode mode); +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14) + static inline void env_copy(MDB_env* env, const char* path, unsigned int flags); + static inline void env_copy_fd(MDB_env* env, mdb_filehandle_t fd, unsigned int flags); +#else + static inline void env_copy(MDB_env* env, const char* path); + static inline void env_copy_fd(MDB_env* env, mdb_filehandle_t fd); +#endif + static inline void env_stat(MDB_env* env, MDB_stat* stat); + static inline void env_info(MDB_env* env, MDB_envinfo* stat); + static inline void env_sync(MDB_env* env, bool force); + static inline void env_close(MDB_env* env) noexcept; + static inline void env_set_flags(MDB_env* env, unsigned int flags, bool onoff); + static inline void env_get_flags(MDB_env* env, unsigned int* flags); + static inline void env_get_path(MDB_env* env, const char** path); + static inline void env_get_fd(MDB_env* env, mdb_filehandle_t* fd); + static inline void env_set_mapsize(MDB_env* env, std::size_t size); + static inline void env_set_max_readers(MDB_env* env, unsigned int count); + static inline void env_get_max_readers(MDB_env* env, unsigned int* count); + static inline void env_set_max_dbs(MDB_env* env, MDB_dbi count); + static inline unsigned int env_get_max_keysize(MDB_env* env); +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11) + static inline void env_set_userctx(MDB_env* env, void* ctx); + static inline void* env_get_userctx(MDB_env* env); +#endif + // TODO: mdb_env_set_assert() + // TODO: mdb_reader_list() + // TODO: mdb_reader_check() +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaad6be3d8dcd4ea01f8df436f41d158d4 + */ +static inline void +lmdb::env_create(MDB_env** env) { + const int rc = ::mdb_env_create(env); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_create", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga32a193c6bf4d7d5c5d579e71f22e9340 + */ +static inline void +lmdb::env_open(MDB_env* const env, + const char* const path, + const unsigned int flags, + const mode mode) { + const int rc = ::mdb_env_open(env, path, flags, mode); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_open", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga3bf50d7793b36aaddf6b481a44e24244 + * @see http://symas.com/mdb/doc/group__mdb.html#ga5d51d6130325f7353db0955dbedbc378 + */ +static inline void +lmdb::env_copy(MDB_env* const env, +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14) + const char* const path, + const unsigned int flags = 0) { + const int rc = ::mdb_env_copy2(env, path, flags); +#else + const char* const path) { + const int rc = ::mdb_env_copy(env, path); +#endif + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_copy2", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga5040d0de1f14000fa01fc0b522ff1f86 + * @see http://symas.com/mdb/doc/group__mdb.html#ga470b0bcc64ac417de5de5930f20b1a28 + */ +static inline void +lmdb::env_copy_fd(MDB_env* const env, +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 14) + const mdb_filehandle_t fd, + const unsigned int flags = 0) { + const int rc = ::mdb_env_copyfd2(env, fd, flags); +#else + const mdb_filehandle_t fd) { + const int rc = ::mdb_env_copyfd(env, fd); +#endif + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_copyfd2", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaf881dca452050efbd434cd16e4bae255 + */ +static inline void +lmdb::env_stat(MDB_env* const env, + MDB_stat* const stat) { + const int rc = ::mdb_env_stat(env, stat); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_stat", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga18769362c7e7d6cf91889a028a5c5947 + */ +static inline void +lmdb::env_info(MDB_env* const env, + MDB_envinfo* const stat) { + const int rc = ::mdb_env_info(env, stat); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_info", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga85e61f05aa68b520cc6c3b981dba5037 + */ +static inline void +lmdb::env_sync(MDB_env* const env, + const bool force = true) { + const int rc = ::mdb_env_sync(env, force); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_sync", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga4366c43ada8874588b6a62fbda2d1e95 + */ +static inline void +lmdb::env_close(MDB_env* const env) noexcept { + ::mdb_env_close(env); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga83f66cf02bfd42119451e9468dc58445 + */ +static inline void +lmdb::env_set_flags(MDB_env* const env, + const unsigned int flags, + const bool onoff = true) { + const int rc = ::mdb_env_set_flags(env, flags, onoff ? 1 : 0); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_flags", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga2733aefc6f50beb49dd0c6eb19b067d9 + */ +static inline void +lmdb::env_get_flags(MDB_env* const env, + unsigned int* const flags) { + const int rc = ::mdb_env_get_flags(env, flags); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_get_flags", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gac699fdd8c4f8013577cb933fb6a757fe + */ +static inline void +lmdb::env_get_path(MDB_env* const env, + const char** path) { + const int rc = ::mdb_env_get_path(env, path); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_get_path", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaf1570e7c0e5a5d860fef1032cec7d5f2 + */ +static inline void +lmdb::env_get_fd(MDB_env* const env, + mdb_filehandle_t* const fd) { + const int rc = ::mdb_env_get_fd(env, fd); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_get_fd", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5 + */ +static inline void +lmdb::env_set_mapsize(MDB_env* const env, + const std::size_t size) { + const int rc = ::mdb_env_set_mapsize(env, size); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_mapsize", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gae687966c24b790630be2a41573fe40e2 + */ +static inline void +lmdb::env_set_max_readers(MDB_env* const env, + const unsigned int count) { + const int rc = ::mdb_env_set_maxreaders(env, count); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_maxreaders", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga70e143cf11760d869f754c9c9956e6cc + */ +static inline void +lmdb::env_get_max_readers(MDB_env* const env, + unsigned int* const count) { + const int rc = ::mdb_env_get_maxreaders(env, count); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_get_maxreaders", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gaa2fc2f1f37cb1115e733b62cab2fcdbc + */ +static inline void +lmdb::env_set_max_dbs(MDB_env* const env, + const MDB_dbi count) { + const int rc = ::mdb_env_set_maxdbs(env, count); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_maxdbs", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#gaaf0be004f33828bf2fb09d77eb3cef94 + */ +static inline unsigned int +lmdb::env_get_max_keysize(MDB_env* const env) { + const int rc = ::mdb_env_get_maxkeysize(env); +#ifdef LMDBXX_DEBUG + assert(rc >= 0); +#endif + return static_cast(rc); +} + +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11) +/** + * @throws lmdb::error on failure + * @since 0.9.11 (2014/01/15) + * @see http://symas.com/mdb/doc/group__mdb.html#gaf2fe09eb9c96eeb915a76bf713eecc46 + */ +static inline void +lmdb::env_set_userctx(MDB_env* const env, + void* const ctx) { + const int rc = ::mdb_env_set_userctx(env, ctx); + if (rc != MDB_SUCCESS) { + error::raise("mdb_env_set_userctx", rc); + } +} +#endif + +#if MDB_VERSION_FULL >= MDB_VERINT(0, 9, 11) +/** + * @since 0.9.11 (2014/01/15) + * @see http://symas.com/mdb/doc/group__mdb.html#ga45df6a4fb150cda2316b5ae224ba52f1 + */ +static inline void* +lmdb::env_get_userctx(MDB_env* const env) { + return ::mdb_env_get_userctx(env); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Transactions */ + +namespace lmdb { + static inline void txn_begin( + MDB_env* env, MDB_txn* parent, unsigned int flags, MDB_txn** txn); + static inline MDB_env* txn_env(MDB_txn* txn) noexcept; +#ifdef LMDBXX_TXN_ID + static inline std::size_t txn_id(MDB_txn* txn) noexcept; +#endif + static inline void txn_commit(MDB_txn* txn); + static inline void txn_abort(MDB_txn* txn) noexcept; + static inline void txn_reset(MDB_txn* txn) noexcept; + static inline void txn_renew(MDB_txn* txn); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gad7ea55da06b77513609efebd44b26920 + */ +static inline void +lmdb::txn_begin(MDB_env* const env, + MDB_txn* const parent, + const unsigned int flags, + MDB_txn** txn) { + const int rc = ::mdb_txn_begin(env, parent, flags, txn); + if (rc != MDB_SUCCESS) { + error::raise("mdb_txn_begin", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#gaeb17735b8aaa2938a78a45cab85c06a0 + */ +static inline MDB_env* +lmdb::txn_env(MDB_txn* const txn) noexcept { + return ::mdb_txn_env(txn); +} + +#ifdef LMDBXX_TXN_ID +/** + * @note Only available in HEAD, not yet in any 0.9.x release (as of 0.9.16). + */ +static inline std::size_t +lmdb::txn_id(MDB_txn* const txn) noexcept { + return ::mdb_txn_id(txn); +} +#endif + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga846fbd6f46105617ac9f4d76476f6597 + */ +static inline void +lmdb::txn_commit(MDB_txn* const txn) { + const int rc = ::mdb_txn_commit(txn); + if (rc != MDB_SUCCESS) { + error::raise("mdb_txn_commit", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga73a5938ae4c3239ee11efa07eb22b882 + */ +static inline void +lmdb::txn_abort(MDB_txn* const txn) noexcept { + ::mdb_txn_abort(txn); +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga02b06706f8a66249769503c4e88c56cd + */ +static inline void +lmdb::txn_reset(MDB_txn* const txn) noexcept { + ::mdb_txn_reset(txn); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga6c6f917959517ede1c504cf7c720ce6d + */ +static inline void +lmdb::txn_renew(MDB_txn* const txn) { + const int rc = ::mdb_txn_renew(txn); + if (rc != MDB_SUCCESS) { + error::raise("mdb_txn_renew", rc); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Databases */ + +namespace lmdb { + static inline void dbi_open( + MDB_txn* txn, const char* name, unsigned int flags, MDB_dbi* dbi); + static inline void dbi_stat(MDB_txn* txn, MDB_dbi dbi, MDB_stat* stat); + static inline void dbi_flags(MDB_txn* txn, MDB_dbi dbi, unsigned int* flags); + static inline void dbi_close(MDB_env* env, MDB_dbi dbi) noexcept; + static inline void dbi_drop(MDB_txn* txn, MDB_dbi dbi, bool del); + static inline void dbi_set_compare(MDB_txn* txn, MDB_dbi dbi, MDB_cmp_func* cmp); + static inline void dbi_set_dupsort(MDB_txn* txn, MDB_dbi dbi, MDB_cmp_func* cmp); + static inline void dbi_set_relfunc(MDB_txn* txn, MDB_dbi dbi, MDB_rel_func* rel); + static inline void dbi_set_relctx(MDB_txn* txn, MDB_dbi dbi, void* ctx); + static inline bool dbi_get(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, MDB_val* data); + static inline bool dbi_put(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, MDB_val* data, unsigned int flags); + static inline bool dbi_del(MDB_txn* txn, MDB_dbi dbi, const MDB_val* key, const MDB_val* data); + // TODO: mdb_cmp() + // TODO: mdb_dcmp() +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a + */ +static inline void +lmdb::dbi_open(MDB_txn* const txn, + const char* const name, + const unsigned int flags, + MDB_dbi* const dbi) { + const int rc = ::mdb_dbi_open(txn, name, flags, dbi); + if (rc != MDB_SUCCESS) { + error::raise("mdb_dbi_open", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gae6c1069febe94299769dbdd032fadef6 + */ +static inline void +lmdb::dbi_stat(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_stat* const result) { + const int rc = ::mdb_stat(txn, dbi, result); + if (rc != MDB_SUCCESS) { + error::raise("mdb_stat", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga95ba4cb721035478a8705e57b91ae4d4 + */ +static inline void +lmdb::dbi_flags(MDB_txn* const txn, + const MDB_dbi dbi, + unsigned int* const flags) { + const int rc = ::mdb_dbi_flags(txn, dbi, flags); + if (rc != MDB_SUCCESS) { + error::raise("mdb_dbi_flags", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga52dd98d0c542378370cd6b712ff961b5 + */ +static inline void +lmdb::dbi_close(MDB_env* const env, + const MDB_dbi dbi) noexcept { + ::mdb_dbi_close(env, dbi); +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#gab966fab3840fc54a6571dfb32b00f2db + */ +static inline void +lmdb::dbi_drop(MDB_txn* const txn, + const MDB_dbi dbi, + const bool del = false) { + const int rc = ::mdb_drop(txn, dbi, del ? 1 : 0); + if (rc != MDB_SUCCESS) { + error::raise("mdb_drop", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga68e47ffcf72eceec553c72b1784ee0fe + */ +static inline void +lmdb::dbi_set_compare(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_cmp_func* const cmp = nullptr) { + const int rc = ::mdb_set_compare(txn, dbi, cmp); + if (rc != MDB_SUCCESS) { + error::raise("mdb_set_compare", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gacef4ec3dab0bbd9bc978b73c19c879ae + */ +static inline void +lmdb::dbi_set_dupsort(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_cmp_func* const cmp = nullptr) { + const int rc = ::mdb_set_dupsort(txn, dbi, cmp); + if (rc != MDB_SUCCESS) { + error::raise("mdb_set_dupsort", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga697d82c7afe79f142207ad5adcdebfeb + */ +static inline void +lmdb::dbi_set_relfunc(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_rel_func* const rel) { + const int rc = ::mdb_set_relfunc(txn, dbi, rel); + if (rc != MDB_SUCCESS) { + error::raise("mdb_set_relfunc", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga7c34246308cee01724a1839a8f5cc594 + */ +static inline void +lmdb::dbi_set_relctx(MDB_txn* const txn, + const MDB_dbi dbi, + void* const ctx) { + const int rc = ::mdb_set_relctx(txn, dbi, ctx); + if (rc != MDB_SUCCESS) { + error::raise("mdb_set_relctx", rc); + } +} + +/** + * @retval true if the key/value pair was retrieved + * @retval false if the key wasn't found + * @see http://symas.com/mdb/doc/group__mdb.html#ga8bf10cd91d3f3a83a34d04ce6b07992d + */ +static inline bool +lmdb::dbi_get(MDB_txn* const txn, + const MDB_dbi dbi, + const MDB_val* const key, + MDB_val* const data) { + const int rc = ::mdb_get(txn, dbi, const_cast(key), data); + if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) { + error::raise("mdb_get", rc); + } + return (rc == MDB_SUCCESS); +} + +/** + * @retval true if the key/value pair was inserted + * @retval false if the key already existed + * @see http://symas.com/mdb/doc/group__mdb.html#ga4fa8573d9236d54687c61827ebf8cac0 + */ +static inline bool +lmdb::dbi_put(MDB_txn* const txn, + const MDB_dbi dbi, + const MDB_val* const key, + MDB_val* const data, + const unsigned int flags = 0) { + const int rc = ::mdb_put(txn, dbi, const_cast(key), data, flags); + if (rc != MDB_SUCCESS && rc != MDB_KEYEXIST) { + error::raise("mdb_put", rc); + } + return (rc == MDB_SUCCESS); +} + +/** + * @retval true if the key/value pair was removed + * @retval false if the key wasn't found + * @see http://symas.com/mdb/doc/group__mdb.html#gab8182f9360ea69ac0afd4a4eaab1ddb0 + */ +static inline bool +lmdb::dbi_del(MDB_txn* const txn, + const MDB_dbi dbi, + const MDB_val* const key, + const MDB_val* const data = nullptr) { + const int rc = ::mdb_del(txn, dbi, const_cast(key), const_cast(data)); + if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) { + error::raise("mdb_del", rc); + } + return (rc == MDB_SUCCESS); +} + +//////////////////////////////////////////////////////////////////////////////// +/* Procedural Interface: Cursors */ + +namespace lmdb { + static inline void cursor_open(MDB_txn* txn, MDB_dbi dbi, MDB_cursor** cursor); + static inline void cursor_close(MDB_cursor* cursor) noexcept; + static inline void cursor_renew(MDB_txn* txn, MDB_cursor* cursor); + static inline MDB_txn* cursor_txn(MDB_cursor* cursor) noexcept; + static inline MDB_dbi cursor_dbi(MDB_cursor* cursor) noexcept; + static inline bool cursor_get(MDB_cursor* cursor, MDB_val* key, MDB_val* data, MDB_cursor_op op); + static inline void cursor_put(MDB_cursor* cursor, MDB_val* key, MDB_val* data, unsigned int flags); + static inline void cursor_del(MDB_cursor* cursor, unsigned int flags); + static inline void cursor_count(MDB_cursor* cursor, std::size_t& count); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga9ff5d7bd42557fd5ee235dc1d62613aa + */ +static inline void +lmdb::cursor_open(MDB_txn* const txn, + const MDB_dbi dbi, + MDB_cursor** const cursor) { + const int rc = ::mdb_cursor_open(txn, dbi, cursor); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_open", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#gad685f5d73c052715c7bd859cc4c05188 + */ +static inline void +lmdb::cursor_close(MDB_cursor* const cursor) noexcept { + ::mdb_cursor_close(cursor); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#gac8b57befb68793070c85ea813df481af + */ +static inline void +lmdb::cursor_renew(MDB_txn* const txn, + MDB_cursor* const cursor) { + const int rc = ::mdb_cursor_renew(txn, cursor); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_renew", rc); + } +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga7bf0d458f7f36b5232fcb368ebda79e0 + */ +static inline MDB_txn* +lmdb::cursor_txn(MDB_cursor* const cursor) noexcept { + return ::mdb_cursor_txn(cursor); +} + +/** + * @see http://symas.com/mdb/doc/group__mdb.html#ga2f7092cf70ee816fb3d2c3267a732372 + */ +static inline MDB_dbi +lmdb::cursor_dbi(MDB_cursor* const cursor) noexcept { + return ::mdb_cursor_dbi(cursor); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga48df35fb102536b32dfbb801a47b4cb0 + */ +static inline bool +lmdb::cursor_get(MDB_cursor* const cursor, + MDB_val* const key, + MDB_val* const data, + const MDB_cursor_op op) { + const int rc = ::mdb_cursor_get(cursor, key, data, op); + if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) { + error::raise("mdb_cursor_get", rc); + } + return (rc == MDB_SUCCESS); +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga1f83ccb40011837ff37cc32be01ad91e + */ +static inline void +lmdb::cursor_put(MDB_cursor* const cursor, + MDB_val* const key, + MDB_val* const data, + const unsigned int flags = 0) { + const int rc = ::mdb_cursor_put(cursor, key, data, flags); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_put", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga26a52d3efcfd72e5bf6bd6960bf75f95 + */ +static inline void +lmdb::cursor_del(MDB_cursor* const cursor, + const unsigned int flags = 0) { + const int rc = ::mdb_cursor_del(cursor, flags); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_del", rc); + } +} + +/** + * @throws lmdb::error on failure + * @see http://symas.com/mdb/doc/group__mdb.html#ga4041fd1e1862c6b7d5f10590b86ffbe2 + */ +static inline void +lmdb::cursor_count(MDB_cursor* const cursor, + std::size_t& count) { + const int rc = ::mdb_cursor_count(cursor, &count); + if (rc != MDB_SUCCESS) { + error::raise("mdb_cursor_count", rc); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Values */ + +namespace lmdb { + class val; +} + +/** + * Wrapper class for `MDB_val` structures. + * + * @note Instances of this class are movable and copyable both. + * @see http://symas.com/mdb/doc/group__mdb.html#structMDB__val + */ +class lmdb::val { +protected: + MDB_val _val; + +public: + /** + * Default constructor. + */ + val() noexcept = default; + + /** + * Constructor. + */ + val(const std::string& data) noexcept + : val{data.data(), data.size()} {} + + /** + * Constructor. + */ + val(const char* const data) noexcept + : val{data, std::strlen(data)} {} + + /** + * Constructor. + */ + val(const void* const data, + const std::size_t size) noexcept + : _val{size, const_cast(data)} {} + + /** + * Move constructor. + */ + val(val&& other) noexcept = default; + + /** + * Move assignment operator. + */ + val& operator=(val&& other) noexcept = default; + + /** + * Destructor. + */ + ~val() noexcept = default; + + /** + * Returns an `MDB_val*` pointer. + */ + operator MDB_val*() noexcept { + return &_val; + } + + /** + * Returns an `MDB_val*` pointer. + */ + operator const MDB_val*() const noexcept { + return &_val; + } + + /** + * Determines whether this value is empty. + */ + bool empty() const noexcept { + return size() == 0; + } + + /** + * Returns the size of the data. + */ + std::size_t size() const noexcept { + return _val.mv_size; + } + + /** + * Returns a pointer to the data. + */ + template + T* data() noexcept { + return reinterpret_cast(_val.mv_data); + } + + /** + * Returns a pointer to the data. + */ + template + const T* data() const noexcept { + return reinterpret_cast(_val.mv_data); + } + + /** + * Returns a pointer to the data. + */ + char* data() noexcept { + return reinterpret_cast(_val.mv_data); + } + + /** + * Returns a pointer to the data. + */ + const char* data() const noexcept { + return reinterpret_cast(_val.mv_data); + } + + /** + * Assigns the value. + */ + template + val& assign(const T* const data, + const std::size_t size) noexcept { + _val.mv_size = size; + _val.mv_data = const_cast(reinterpret_cast(data)); + return *this; + } + + /** + * Assigns the value. + */ + val& assign(const char* const data) noexcept { + return assign(data, std::strlen(data)); + } + + /** + * Assigns the value. + */ + val& assign(const std::string& data) noexcept { + return assign(data.data(), data.size()); + } +}; + +#if !(defined(__COVERITY__) || defined(_MSC_VER)) +static_assert(std::is_pod::value, "lmdb::val must be a POD type"); +static_assert(sizeof(lmdb::val) == sizeof(MDB_val), "sizeof(lmdb::val) != sizeof(MDB_val)"); +#endif + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Environment */ + +namespace lmdb { + class env; +} + +/** + * Resource class for `MDB_env*` handles. + * + * @note Instances of this class are movable, but not copyable. + * @see http://symas.com/mdb/doc/group__internal.html#structMDB__env + */ +class lmdb::env { +protected: + MDB_env* _handle{nullptr}; + +public: + static constexpr unsigned int default_flags = 0; + static constexpr mode default_mode = 0644; /* -rw-r--r-- */ + + /** + * Creates a new LMDB environment. + * + * @param flags + * @throws lmdb::error on failure + */ + static env create(const unsigned int flags = default_flags) { + MDB_env* handle{nullptr}; + lmdb::env_create(&handle); +#ifdef LMDBXX_DEBUG + assert(handle != nullptr); +#endif + if (flags) { + try { + lmdb::env_set_flags(handle, flags); + } + catch (const lmdb::error&) { + lmdb::env_close(handle); + throw; + } + } + return env{handle}; + } + + /** + * Constructor. + * + * @param handle a valid `MDB_env*` handle + */ + env(MDB_env* const handle) noexcept + : _handle{handle} {} + + /** + * Move constructor. + */ + env(env&& other) noexcept { + std::swap(_handle, other._handle); + } + + /** + * Move assignment operator. + */ + env& operator=(env&& other) noexcept { + if (this != &other) { + std::swap(_handle, other._handle); + } + return *this; + } + + /** + * Destructor. + */ + ~env() noexcept { + try { close(); } catch (...) {} + } + + /** + * Returns the underlying `MDB_env*` handle. + */ + operator MDB_env*() const noexcept { + return _handle; + } + + /** + * Returns the underlying `MDB_env*` handle. + */ + MDB_env* handle() const noexcept { + return _handle; + } + + /** + * Flushes data buffers to disk. + * + * @param force + * @throws lmdb::error on failure + */ + void sync(const bool force = true) { + lmdb::env_sync(handle(), force); + } + + /** + * Closes this environment, releasing the memory map. + * + * @note this method is idempotent + * @post `handle() == nullptr` + */ + void close() noexcept { + if (handle()) { + lmdb::env_close(handle()); + _handle = nullptr; + } + } + + /** + * Opens this environment. + * + * @param path + * @param flags + * @param mode + * @throws lmdb::error on failure + */ + env& open(const char* const path, + const unsigned int flags = default_flags, + const mode mode = default_mode) { + lmdb::env_open(handle(), path, flags, mode); + return *this; + } + + /** + * @param flags + * @param onoff + * @throws lmdb::error on failure + */ + env& set_flags(const unsigned int flags, + const bool onoff = true) { + lmdb::env_set_flags(handle(), flags, onoff); + return *this; + } + + /** + * @param size + * @throws lmdb::error on failure + */ + env& set_mapsize(const std::size_t size) { + lmdb::env_set_mapsize(handle(), size); + return *this; + } + + /** + * @param count + * @throws lmdb::error on failure + */ + env& set_max_readers(const unsigned int count) { + lmdb::env_set_max_readers(handle(), count); + return *this; + } + + /** + * @param count + * @throws lmdb::error on failure + */ + env& set_max_dbs(const MDB_dbi count) { + lmdb::env_set_max_dbs(handle(), count); + return *this; + } +}; + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Transactions */ + +namespace lmdb { + class txn; +} + +/** + * Resource class for `MDB_txn*` handles. + * + * @note Instances of this class are movable, but not copyable. + * @see http://symas.com/mdb/doc/group__internal.html#structMDB__txn + */ +class lmdb::txn { +protected: + MDB_txn* _handle{nullptr}; + +public: + static constexpr unsigned int default_flags = 0; + + /** + * Creates a new LMDB transaction. + * + * @param env the environment handle + * @param parent + * @param flags + * @throws lmdb::error on failure + */ + static txn begin(MDB_env* const env, + MDB_txn* const parent = nullptr, + const unsigned int flags = default_flags) { + MDB_txn* handle{nullptr}; + lmdb::txn_begin(env, parent, flags, &handle); +#ifdef LMDBXX_DEBUG + assert(handle != nullptr); +#endif + return txn{handle}; + } + + /** + * Constructor. + * + * @param handle a valid `MDB_txn*` handle + */ + txn(MDB_txn* const handle) noexcept + : _handle{handle} {} + + /** + * Move constructor. + */ + txn(txn&& other) noexcept { + std::swap(_handle, other._handle); + } + + /** + * Move assignment operator. + */ + txn& operator=(txn&& other) noexcept { + if (this != &other) { + std::swap(_handle, other._handle); + } + return *this; + } + + /** + * Destructor. + */ + ~txn() noexcept { + if (_handle) { + try { abort(); } catch (...) {} + _handle = nullptr; + } + } + + /** + * Returns the underlying `MDB_txn*` handle. + */ + operator MDB_txn*() const noexcept { + return _handle; + } + + /** + * Returns the underlying `MDB_txn*` handle. + */ + MDB_txn* handle() const noexcept { + return _handle; + } + + /** + * Returns the transaction's `MDB_env*` handle. + */ + MDB_env* env() const noexcept { + return lmdb::txn_env(handle()); + } + + /** + * Commits this transaction. + * + * @throws lmdb::error on failure + * @post `handle() == nullptr` + */ + void commit() { + lmdb::txn_commit(_handle); + _handle = nullptr; + } + + /** + * Aborts this transaction. + * + * @post `handle() == nullptr` + */ + void abort() noexcept { + lmdb::txn_abort(_handle); + _handle = nullptr; + } + + /** + * Resets this read-only transaction. + */ + void reset() noexcept { + lmdb::txn_reset(_handle); + } + + /** + * Renews this read-only transaction. + * + * @throws lmdb::error on failure + */ + void renew() { + lmdb::txn_renew(_handle); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Databases */ + +namespace lmdb { + class dbi; +} + +/** + * Resource class for `MDB_dbi` handles. + * + * @note Instances of this class are movable, but not copyable. + * @see http://symas.com/mdb/doc/group__mdb.html#gadbe68a06c448dfb62da16443d251a78b + */ +class lmdb::dbi { +protected: + MDB_dbi _handle{0}; + +public: + static constexpr unsigned int default_flags = 0; + static constexpr unsigned int default_put_flags = 0; + + /** + * Opens a database handle. + * + * @param txn the transaction handle + * @param name + * @param flags + * @throws lmdb::error on failure + */ + static dbi + open(MDB_txn* const txn, + const char* const name = nullptr, + const unsigned int flags = default_flags) { + MDB_dbi handle{}; + lmdb::dbi_open(txn, name, flags, &handle); + return dbi{handle}; + } + + /** + * Constructor. + * + * @param handle a valid `MDB_dbi` handle + */ + dbi(const MDB_dbi handle) noexcept + : _handle{handle} {} + + /** + * Move constructor. + */ + dbi(dbi&& other) noexcept { + std::swap(_handle, other._handle); + } + + /** + * Move assignment operator. + */ + dbi& operator=(dbi&& other) noexcept { + if (this != &other) { + std::swap(_handle, other._handle); + } + return *this; + } + + /** + * Destructor. + */ + ~dbi() noexcept { + if (_handle) { + /* No need to call close() here. */ + } + } + + /** + * Returns the underlying `MDB_dbi` handle. + */ + operator MDB_dbi() const noexcept { + return _handle; + } + + /** + * Returns the underlying `MDB_dbi` handle. + */ + MDB_dbi handle() const noexcept { + return _handle; + } + + /** + * Returns statistics for this database. + * + * @param txn a transaction handle + * @throws lmdb::error on failure + */ + MDB_stat stat(MDB_txn* const txn) const { + MDB_stat result; + lmdb::dbi_stat(txn, handle(), &result); + return result; + } + + /** + * Retrieves the flags for this database handle. + * + * @param txn a transaction handle + * @throws lmdb::error on failure + */ + unsigned int flags(MDB_txn* const txn) const { + unsigned int result{}; + lmdb::dbi_flags(txn, handle(), &result); + return result; + } + + /** + * Returns the number of records in this database. + * + * @param txn a transaction handle + * @throws lmdb::error on failure + */ + std::size_t size(MDB_txn* const txn) const { + return stat(txn).ms_entries; + } + + /** + * @param txn a transaction handle + * @param del + * @throws lmdb::error on failure + */ + void drop(MDB_txn* const txn, + const bool del = false) { + lmdb::dbi_drop(txn, handle(), del); + } + + /** + * Sets a custom key comparison function for this database. + * + * @param txn a transaction handle + * @param cmp the comparison function + * @throws lmdb::error on failure + */ + dbi& set_compare(MDB_txn* const txn, + MDB_cmp_func* const cmp = nullptr) { + lmdb::dbi_set_compare(txn, handle(), cmp); + return *this; + } + + /** + * Retrieves a key/value pair from this database. + * + * @param txn a transaction handle + * @param key + * @param data + * @throws lmdb::error on failure + */ + bool get(MDB_txn* const txn, + const val& key, + val& data) { + return lmdb::dbi_get(txn, handle(), key, data); + } + + /** + * Retrieves a key from this database. + * + * @param txn a transaction handle + * @param key + * @throws lmdb::error on failure + */ + template + bool get(MDB_txn* const txn, + const K& key) const { + const lmdb::val k{&key, sizeof(K)}; + lmdb::val v{}; + return lmdb::dbi_get(txn, handle(), k, v); + } + + /** + * Retrieves a key/value pair from this database. + * + * @param txn a transaction handle + * @param key + * @param val + * @throws lmdb::error on failure + */ + template + bool get(MDB_txn* const txn, + const K& key, + V& val) const { + const lmdb::val k{&key, sizeof(K)}; + lmdb::val v{}; + const bool result = lmdb::dbi_get(txn, handle(), k, v); + if (result) { + val = *v.data(); + } + return result; + } + + /** + * Retrieves a key/value pair from this database. + * + * @param txn a transaction handle + * @param key a NUL-terminated string key + * @param val + * @throws lmdb::error on failure + */ + template + bool get(MDB_txn* const txn, + const char* const key, + V& val) const { + const lmdb::val k{key, std::strlen(key)}; + lmdb::val v{}; + const bool result = lmdb::dbi_get(txn, handle(), k, v); + if (result) { + val = *v.data(); + } + return result; + } + + /** + * Stores a key/value pair into this database. + * + * @param txn a transaction handle + * @param key + * @param data + * @param flags + * @throws lmdb::error on failure + */ + bool put(MDB_txn* const txn, + const val& key, + val& data, + const unsigned int flags = default_put_flags) { + return lmdb::dbi_put(txn, handle(), key, data, flags); + } + + /** + * Stores a key into this database. + * + * @param txn a transaction handle + * @param key + * @param flags + * @throws lmdb::error on failure + */ + template + bool put(MDB_txn* const txn, + const K& key, + const unsigned int flags = default_put_flags) { + const lmdb::val k{&key, sizeof(K)}; + lmdb::val v{}; + return lmdb::dbi_put(txn, handle(), k, v, flags); + } + + /** + * Stores a key/value pair into this database. + * + * @param txn a transaction handle + * @param key + * @param val + * @param flags + * @throws lmdb::error on failure + */ + template + bool put(MDB_txn* const txn, + const K& key, + const V& val, + const unsigned int flags = default_put_flags) { + const lmdb::val k{&key, sizeof(K)}; + lmdb::val v{&val, sizeof(V)}; + return lmdb::dbi_put(txn, handle(), k, v, flags); + } + + /** + * Stores a key/value pair into this database. + * + * @param txn a transaction handle + * @param key a NUL-terminated string key + * @param val + * @param flags + * @throws lmdb::error on failure + */ + template + bool put(MDB_txn* const txn, + const char* const key, + const V& val, + const unsigned int flags = default_put_flags) { + const lmdb::val k{key, std::strlen(key)}; + lmdb::val v{&val, sizeof(V)}; + return lmdb::dbi_put(txn, handle(), k, v, flags); + } + + /** + * Stores a key/value pair into this database. + * + * @param txn a transaction handle + * @param key a NUL-terminated string key + * @param val a NUL-terminated string key + * @param flags + * @throws lmdb::error on failure + */ + bool put(MDB_txn* const txn, + const char* const key, + const char* const val, + const unsigned int flags = default_put_flags) { + const lmdb::val k{key, std::strlen(key)}; + lmdb::val v{val, std::strlen(val)}; + return lmdb::dbi_put(txn, handle(), k, v, flags); + } + + /** + * Removes a key/value pair from this database. + * + * @param txn a transaction handle + * @param key + * @throws lmdb::error on failure + */ + bool del(MDB_txn* const txn, + const val& key) { + return lmdb::dbi_del(txn, handle(), key); + } + + /** + * Removes a key/value pair from this database. + * + * @param txn a transaction handle + * @param key + * @throws lmdb::error on failure + */ + template + bool del(MDB_txn* const txn, + const K& key) { + const lmdb::val k{&key, sizeof(K)}; + return lmdb::dbi_del(txn, handle(), k); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +/* Resource Interface: Cursors */ + +namespace lmdb { + class cursor; +} + +/** + * Resource class for `MDB_cursor*` handles. + * + * @note Instances of this class are movable, but not copyable. + * @see http://symas.com/mdb/doc/group__internal.html#structMDB__cursor + */ +class lmdb::cursor { +protected: + MDB_cursor* _handle{nullptr}; + +public: + static constexpr unsigned int default_flags = 0; + + /** + * Creates an LMDB cursor. + * + * @param txn the transaction handle + * @param dbi the database handle + * @throws lmdb::error on failure + */ + static cursor + open(MDB_txn* const txn, + const MDB_dbi dbi) { + MDB_cursor* handle{}; + lmdb::cursor_open(txn, dbi, &handle); +#ifdef LMDBXX_DEBUG + assert(handle != nullptr); +#endif + return cursor{handle}; + } + + /** + * Constructor. + * + * @param handle a valid `MDB_cursor*` handle + */ + cursor(MDB_cursor* const handle) noexcept + : _handle{handle} {} + + /** + * Move constructor. + */ + cursor(cursor&& other) noexcept { + std::swap(_handle, other._handle); + } + + /** + * Move assignment operator. + */ + cursor& operator=(cursor&& other) noexcept { + if (this != &other) { + std::swap(_handle, other._handle); + } + return *this; + } + + /** + * Destructor. + */ + ~cursor() noexcept { + try { close(); } catch (...) {} + } + + /** + * Returns the underlying `MDB_cursor*` handle. + */ + operator MDB_cursor*() const noexcept { + return _handle; + } + + /** + * Returns the underlying `MDB_cursor*` handle. + */ + MDB_cursor* handle() const noexcept { + return _handle; + } + + /** + * Closes this cursor. + * + * @note this method is idempotent + * @post `handle() == nullptr` + */ + void close() noexcept { + if (_handle) { + lmdb::cursor_close(_handle); + _handle = nullptr; + } + } + + /** + * Renews this cursor. + * + * @param txn the transaction scope + * @throws lmdb::error on failure + */ + void renew(MDB_txn* const txn) { + lmdb::cursor_renew(txn, handle()); + } + + /** + * Returns the cursor's transaction handle. + */ + MDB_txn* txn() const noexcept { + return lmdb::cursor_txn(handle()); + } + + /** + * Returns the cursor's database handle. + */ + MDB_dbi dbi() const noexcept { + return lmdb::cursor_dbi(handle()); + } + + /** + * Retrieves a key from the database. + * + * @param key + * @param op + * @throws lmdb::error on failure + */ + bool get(MDB_val* const key, + const MDB_cursor_op op) { + return get(key, nullptr, op); + } + + /** + * Retrieves a key from the database. + * + * @param key + * @param op + * @throws lmdb::error on failure + */ + bool get(lmdb::val& key, + const MDB_cursor_op op) { + return get(key, nullptr, op); + } + + /** + * Retrieves a key/value pair from the database. + * + * @param key + * @param val (may be `nullptr`) + * @param op + * @throws lmdb::error on failure + */ + bool get(MDB_val* const key, + MDB_val* const val, + const MDB_cursor_op op) { + return lmdb::cursor_get(handle(), key, val, op); + } + + /** + * Retrieves a key/value pair from the database. + * + * @param key + * @param val + * @param op + * @throws lmdb::error on failure + */ + bool get(lmdb::val& key, + lmdb::val& val, + const MDB_cursor_op op) { + return lmdb::cursor_get(handle(), key, val, op); + } + + /** + * Retrieves a key/value pair from the database. + * + * @param key + * @param val + * @param op + * @throws lmdb::error on failure + */ + bool get(std::string& key, + std::string& val, + const MDB_cursor_op op) { + lmdb::val k{}, v{}; + const bool found = get(k, v, op); + if (found) { + key.assign(k.data(), k.size()); + val.assign(v.data(), v.size()); + } + return found; + } + + /** + * Positions this cursor at the given key. + * + * @param key + * @param op + * @throws lmdb::error on failure + */ + template + bool find(const K& key, + const MDB_cursor_op op = MDB_SET) { + lmdb::val k{&key, sizeof(K)}; + return get(k, nullptr, op); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +#endif /* LMDBXX_H */ diff --git a/lib/wDatabase/resourcecache.cpp b/lib/wDatabase/resourcecache.cpp new file mode 100644 index 0000000..64d383b --- /dev/null +++ b/lib/wDatabase/resourcecache.cpp @@ -0,0 +1,277 @@ +#include "resourcecache.h" + +#include +#include + +#include + +ResourceCache::ResourceCache(const W::String& dbName, QObject* parent): + M::Model(W::Address{dbName}, parent), + subscribeMember(W::Handler::create(W::Address({}), this, &ResourceCache::_h_subscribeMember)), + name(dbName), + opened(false), + environment(lmdb::env::create()), + dbi(0), + elements(), + lastIndex(0) +{ + W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &ResourceCache::_h_get); + addHandler(get); +} + +ResourceCache::~ResourceCache() +{ + delete subscribeMember; +} + +void ResourceCache::open() +{ + if (!opened) { + checkDirAndOpenEnvironment(); + + lmdb::txn rtxn = lmdb::txn::begin(environment, nullptr, MDB_RDONLY); + lmdb::cursor cursor = lmdb::cursor::open(rtxn, dbi); + lmdb::val key; + lmdb::val value; + while (cursor.get(key, value, MDB_NEXT)) { + uint64_t iKey = *((uint64_t*) key.data()); + + elements.insert(iKey); + if (iKey > lastIndex) { + lastIndex = iKey; + } + } + cursor.close(); + rtxn.abort(); + + opened = true; + emit countChange(elements.size()); + } +} + +void ResourceCache::checkDirAndOpenEnvironment() +{ + int state1 = mkdir("./dbMedia", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (state1 != 0 && errno != EEXIST) { + emit serviceMessage("Failed to create a root database folder"); + throw 1; + } + + W::String path("./dbMedia/"); + path += name; + + int state2 = mkdir(path.toString().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (state2 != 0 && errno != EEXIST) { + emit serviceMessage(QString("Failed to create ") + name.toString().c_str() + " database folder"); + throw 1; + } + + environment.set_mapsize(1UL * 1024UL * 1024UL * 1024UL); + environment.set_max_dbs(10); + environment.open(path.toString().c_str(), 0, 0664); + + lmdb::txn wTrans = lmdb::txn::begin(environment); + dbi = lmdb::dbi::open(wTrans, "main", MDB_CREATE | MDB_INTEGERKEY); + wTrans.commit(); +} + +uint64_t ResourceCache::addResource(const W::String& path) +{ + uint64_t id = ++lastIndex; + + elements.insert(id); + + W::ByteArray ba(path.size() + 1); + ba.push8(path.getType()); + path.serialize(ba); + + lmdb::val key((uint8_t*) &id, 8); + lmdb::val value(ba.getData(), ba.size()); + lmdb::txn wTrans = lmdb::txn::begin(environment); + dbi.put(wTrans, key, value); + wTrans.commit(); + + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(id)); + + broadcast(vc, W::Address({u"addElement"})); + emit countChange(elements.size()); + + return id; +} + +void ResourceCache::removeResource(uint64_t id) +{ + if (!opened) { + throw ClosedDB("removeResource"); + } + + lmdb::txn transaction = lmdb::txn::begin(environment); + lmdb::val key((uint8_t*) &id, 8); + dbi.del(transaction, key); + transaction.commit(); + elements.erase(id); + + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(id)); + + broadcast(vc, W::Address({u"removeElement"})); + emit countChange(elements.size()); + + //TODO not sure, may be it's better to also destroy resource model? +} + +void ResourceCache::clear() +{ + if (!opened) { + throw new ClosedDB("clear"); + } + + lmdb::txn transaction = lmdb::txn::begin(environment); + dbi.drop(transaction); + transaction.commit(); + + elements.clear(); + lastIndex = 0; + + W::Vocabulary* vc = new W::Vocabulary(); + + broadcast(vc, W::Address({u"clear"})); + emit countChange(elements.size()); +} + +void ResourceCache::h_get(const W::Event& ev) +{ + W::Vector* order = new W::Vector(); + std::set::const_iterator itr = elements.begin(); + std::set::const_iterator end = elements.end(); + + for (; itr != end; ++itr) { + order->push(W::Uint64(*itr)); + } + + W::Vocabulary* rvc = new W::Vocabulary; + rvc->insert(u"data", order); + + response(rvc, W::Address({u"get"}), ev); +} + + +M::Model::ModelType ResourceCache::getType() const +{ + return type; +} + +void ResourceCache::set(W::Object* value) +{ + set(*value); +} + +void ResourceCache::set(const W::Object& value) +{ + throw 14; //what do you expect here? not implemented, and not sure it ever would be +} + +void ResourceCache::h_subscribe(const W::Event& ev) +{ + M::Model::h_subscribe(ev); + + h_get(ev); +} + +W::String * ResourceCache::getElement(uint64_t id) const +{ + lmdb::txn rtxn = lmdb::txn::begin(environment, nullptr, MDB_RDONLY); + lmdb::val key((uint8_t*) &id, 8); + lmdb::val value; + + if (lmdb::dbi_get(rtxn, dbi.handle(), key, value)) { + W::ByteArray ba(value.size()); + ba.fill(value.data(), value.size()); + + W::String* wVal = static_cast(W::Object::fromByteArray(ba)); + rtxn.abort(); + + return wVal; + } else { + rtxn.abort(); + throw 3; + } +} + + +void ResourceCache::h_subscribeMember(const W::Event& ev) +{ + const W::Address& addr = ev.getDestination(); + W::Address lastHops = addr << address.length(); + + if (lastHops.length() == 2 && (lastHops.ends(W::Address{u"subscribe"}) + || lastHops.ends(W::Address{u"get"}) + || lastHops.ends(W::Address{u"getAdditional"})) + ) { + W::String* record; + try { + record = getElement(lastHops.front().toUint64()); + M::File* modelRecord = M::File::create(readFile(*record), address + lastHops >> 1); + delete record; + addModel(modelRecord); + passToHandler(ev); + } catch (int err) { + if (err == 3) { + emit serviceMessage(QString("An attempt to create and subscribe record model in resourcecache, but it is not found. Event: ") + ev.toString().c_str()); + } else if (err == 10) { + serviceMessage(QString("Can't open file ") + record->toString().c_str()); + delete record; + } else { + throw err; + } + } catch (const std::invalid_argument& err) { + emit serviceMessage(QString("Strange event in custom handler of resourcecache ") + ev.toString().c_str()); + } + } else { + emit serviceMessage(QString("Strange event in custom handler of resourcecache ") + ev.toString().c_str()); + } +} + + +W::Blob * ResourceCache::readFile(const W::String& path) const +{ + std::ifstream file (path.toString(), std::ios::in|std::ios::binary|std::ios::ate); + + if (file.is_open()) { + char * memblock; + uint32_t size; + size = file.tellg(); + file.seekg(0, std::ios::beg); + memblock = new char[size]; + file.read(memblock, size); + file.close(); + + return new W::Blob(size, memblock); + } else { + throw 10; //TODO + } +} + +std::set ResourceCache::getAllIdentificators() const +{ + if (!opened) { + throw new ClosedDB("getAllIdentificators"); + } + + return elements; +} + +W::String ResourceCache::getPath(uint64_t id) const +{ + return *(getElement(id)); +} + +bool ResourceCache::has(uint64_t id) const +{ + if (!opened) { + throw new ClosedDB("has"); + } + + return elements.find(id) != elements.end(); +} diff --git a/lib/wDatabase/resourcecache.h b/lib/wDatabase/resourcecache.h new file mode 100644 index 0000000..f2b8c38 --- /dev/null +++ b/lib/wDatabase/resourcecache.h @@ -0,0 +1,84 @@ +#ifndef RESOURCECACHE_H +#define RESOURCECACHE_H + +#include +#include + +#include "lmdb++.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +class ResourceCache : public M::Model +{ + Q_OBJECT +public: + ResourceCache(const W::String& dbName, QObject* parent = 0); + ~ResourceCache(); + + void open(); + uint64_t addResource(const W::String& path); + void removeResource(uint64_t id); + void clear(); + bool has(uint64_t id) const; + W::String getPath(uint64_t id) const; + std::set getAllIdentificators() const; + + W::Handler* subscribeMember; + handler(subscribeMember); + + M::Model::ModelType getType() const override; + static const M::Model::ModelType type = resourceCache; + void set(const W::Object & value) override; + void set(W::Object * value) override; + +signals: + void countChange(uint64_t count); + +protected: + void h_subscribe(const W::Event & ev) override; + + handler(get); + + W::String* getElement(uint64_t id) const; + +public: + const W::String name; + +private: + bool opened; + lmdb::env environment; + lmdb::dbi dbi; + std::set elements; + uint64_t lastIndex; + + void checkDirAndOpenEnvironment(); + W::Blob* readFile(const W::String& path) const; + + class ClosedDB: + public Utils::Exception + { + public: + ClosedDB(const std::string& p_op):Exception(), operation(p_op){} + + std::string getMessage() const { + std::string str = "An attempt to perform method ResourceCache::"; + str += operation; + str += " but the database is not open"; + return str; + + } + + private: + std::string operation; + }; +}; + +#endif // RESOURCECACHE_H diff --git a/lib/wDispatcher/CMakeLists.txt b/lib/wDispatcher/CMakeLists.txt new file mode 100644 index 0000000..0c5aa22 --- /dev/null +++ b/lib/wDispatcher/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.12) +project(wDispatcher) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + dispatcher.h + handler.h + defaulthandler.h + logger.h + parentreporter.h +) + +set(SOURCES + dispatcher.cpp + handler.cpp + defaulthandler.cpp + logger.cpp + parentreporter.cpp +) + +add_library(wDispatcher ${HEADERS} ${SOURCES}) + +target_link_libraries(wDispatcher Qt5::Core) diff --git a/lib/wDispatcher/defaulthandler.cpp b/lib/wDispatcher/defaulthandler.cpp new file mode 100644 index 0000000..de8a349 --- /dev/null +++ b/lib/wDispatcher/defaulthandler.cpp @@ -0,0 +1,11 @@ +#include "defaulthandler.h" + +W::DefaultHandler::DefaultHandler() +{ + +} + +W::DefaultHandler::~DefaultHandler() +{ + +} diff --git a/lib/wDispatcher/defaulthandler.h b/lib/wDispatcher/defaulthandler.h new file mode 100644 index 0000000..2b9243c --- /dev/null +++ b/lib/wDispatcher/defaulthandler.h @@ -0,0 +1,18 @@ +#ifndef DEFAULTHANDLER_H +#define DEFAULTHANDLER_H + +#include + +namespace W +{ + class DefaultHandler + { + public: + DefaultHandler(); + virtual ~DefaultHandler(); + + virtual bool call(const W::Event& ev) const = 0; + }; +} + +#endif // DEFAULTHANDLER_H diff --git a/lib/wDispatcher/dispatcher.cpp b/lib/wDispatcher/dispatcher.cpp new file mode 100644 index 0000000..2e4c3e2 --- /dev/null +++ b/lib/wDispatcher/dispatcher.cpp @@ -0,0 +1,84 @@ +#include "dispatcher.h" + +W::Dispatcher::Dispatcher() +{} + +W::Dispatcher::~Dispatcher() +{} + +void W::Dispatcher::pass(const W::Event& ev) const +{ + + n_map::const_iterator itr; + itr = nodes.find(ev.getDestination()); + + if (itr != nodes.end()) + { + W::Order::const_iterator beg = itr->second.begin(); + W::Order::const_iterator end = itr->second.end(); + + std::list list(beg, end); + std::list::const_iterator itr = list.begin(); + std::list::const_iterator tEnd = list.end(); + for (; itr != tEnd; ++itr) { + (*itr)->pass(ev); + } + } + else + { + d_order::const_iterator itr = defaultHandlers.begin(); + d_order::const_iterator end = defaultHandlers.end(); + + for (; itr != end; ++itr) + { + if ((*itr)->call(ev)){ + break; + } + } + } +} + +void W::Dispatcher::registerHandler(W::Handler* dp) +{ + n_map::iterator itr = nodes.find(dp->getAddress()); + if (itr == nodes.end()) { + W::Order ord; + itr = nodes.insert(std::make_pair(dp->getAddress(), ord)).first; + } + itr->second.push_back(dp); +} + +void W::Dispatcher::unregisterHandler(W::Handler* dp) +{ + n_map::iterator itr = nodes.find(dp->getAddress()); + if (itr != nodes.end()) + { + W::Order::const_iterator o_itr = itr->second.find(dp); + if (o_itr != itr->second.end()) { + itr->second.erase(dp); + + if (itr->second.size() == 0) { + nodes.erase(itr); + } + } + else + { + throw 5;//TODO exception; + } + + } + else + { + throw 5;//TODO exception; + } +} + +void W::Dispatcher::registerDefaultHandler(W::DefaultHandler* dh) +{ + defaultHandlers.push_back(dh); +} + +void W::Dispatcher::unregisterDefaultHandler(W::DefaultHandler* dh) +{ + defaultHandlers.erase(dh); +} diff --git a/lib/wDispatcher/dispatcher.h b/lib/wDispatcher/dispatcher.h new file mode 100644 index 0000000..8dfdafa --- /dev/null +++ b/lib/wDispatcher/dispatcher.h @@ -0,0 +1,48 @@ +#ifndef DISPATCHER_H +#define DISPATCHER_H + +#include +#include + +#include +#include +#include + +#include + +#include "handler.h" +#include "defaulthandler.h" + +#include + +namespace W +{ + class Dispatcher: + public QObject + { + Q_OBJECT + + public: + Dispatcher(); + ~Dispatcher(); + + void registerHandler(W::Handler* dp); + void unregisterHandler(W::Handler* dp); + + void registerDefaultHandler(W::DefaultHandler* dh); + void unregisterDefaultHandler(W::DefaultHandler* dh); + + public slots: + void pass(const W::Event& ev) const; + + protected: + typedef std::map> n_map; + typedef W::Order d_order; + + n_map nodes; + d_order defaultHandlers; + + }; +} + +#endif // DISPATCHER_H diff --git a/lib/wDispatcher/handler.cpp b/lib/wDispatcher/handler.cpp new file mode 100644 index 0000000..f55af78 --- /dev/null +++ b/lib/wDispatcher/handler.cpp @@ -0,0 +1,17 @@ +#include "handler.h" + +W::Handler::Handler(const W::Address& p_rel_addr): + address(p_rel_addr) +{ + +} + +W::Handler::~Handler() +{ + +} + +const W::Address& W::Handler::getAddress() const +{ + return address; +} \ No newline at end of file diff --git a/lib/wDispatcher/handler.h b/lib/wDispatcher/handler.h new file mode 100644 index 0000000..e44e0f2 --- /dev/null +++ b/lib/wDispatcher/handler.h @@ -0,0 +1,56 @@ +#ifndef HANDLER_H +#define HANDLER_H + +#include "wType/address.h" +#include "wType/event.h" + +namespace W +{ + template + class ImplHandle; + + class Handler + { + public: + Handler(const Address& p_rel_addr); + virtual ~Handler(); + + template + static Handler* create(const Address& addr, InstanceType* inst, MethodType mth) + { + return new ImplHandle(addr, inst, mth); + } + + const W::Address& getAddress() const; + + virtual void pass(const W::Event& ev) const = 0; + + private: + W::Address address; + }; + + template + class ImplHandle: public Handler + { + public: + ImplHandle(const Address& p_rel_addr, InstanceType *p_inst, MethodType p_mth): + Handler(p_rel_addr), + inst(p_inst), + mth(p_mth) + {} + + ~ImplHandle() {} + + void pass(const W::Event& ev) const + { + ( ( *inst ).*mth )(ev); + } + + private: + InstanceType* inst; + MethodType mth; + }; + +} + +#endif // HANDLER_H diff --git a/lib/wDispatcher/logger.cpp b/lib/wDispatcher/logger.cpp new file mode 100644 index 0000000..0cb9302 --- /dev/null +++ b/lib/wDispatcher/logger.cpp @@ -0,0 +1,24 @@ +#include "logger.h" + +#include "iostream" + +W::Logger::Logger(): + DefaultHandler() +{ + +} + +W::Logger::~Logger() +{ + +} + +bool W::Logger::call(const W::Event& ev) const +{ + std::cout << "Event went to default handler.\n"; + std::cout << "Destination: " << ev.getDestination().toString() << "\n"; + std::cout << "Data: " << ev.getData().toString(); + std::cout << std::endl; + + return false; +} diff --git a/lib/wDispatcher/logger.h b/lib/wDispatcher/logger.h new file mode 100644 index 0000000..74479cd --- /dev/null +++ b/lib/wDispatcher/logger.h @@ -0,0 +1,21 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include "defaulthandler.h" + +#include + +namespace W +{ + class Logger: + public DefaultHandler + { + public: + Logger(); + ~Logger(); + + bool call(const W::Event& ev) const; + }; +} + +#endif // LOGGER_H diff --git a/lib/wDispatcher/parentreporter.cpp b/lib/wDispatcher/parentreporter.cpp new file mode 100644 index 0000000..5109edf --- /dev/null +++ b/lib/wDispatcher/parentreporter.cpp @@ -0,0 +1,44 @@ +#include "parentreporter.h" + +W::ParentReporter::ParentReporter(): + W::DefaultHandler(), + handlers() +{ +} + +W::ParentReporter::~ParentReporter() +{ +} + +bool W::ParentReporter::call(const W::Event& ev) const +{ + const W::Address& addr = ev.getDestination(); + std::map result; + + Hmap::const_iterator itr = handlers.begin(); + Hmap::const_iterator end = handlers.end(); + + for (; itr != end; ++itr) { //need to find the closest parent to the event destination + if (addr.begins(itr->first)) { //the closest parent has the longest address of those whose destinatiion begins with + result.insert(std::make_pair(itr->first.size(), itr->second)); + } + } + if (result.size() > 0) { + std::map::const_iterator itr = result.end(); + --itr; + itr->second->pass(ev); //need to report only to the closest parent + return true; + } else { + return false; + } +} + +void W::ParentReporter::registerParent(const W::Address& address, W::Handler* handler) +{ + Hmap::const_iterator itr = handlers.find(address); + if (itr != handlers.end()) { + throw 1; + } else { + handlers.insert(std::make_pair(address, handler)); + } +} diff --git a/lib/wDispatcher/parentreporter.h b/lib/wDispatcher/parentreporter.h new file mode 100644 index 0000000..ecfd9b2 --- /dev/null +++ b/lib/wDispatcher/parentreporter.h @@ -0,0 +1,25 @@ +#ifndef PARENTREPORTER_H +#define PARENTREPORTER_H + +#include "defaulthandler.h" +#include "handler.h" +#include + +namespace W { + + class ParentReporter : public DefaultHandler + { + public: + ParentReporter(); + ~ParentReporter(); + + bool call(const W::Event& ev) const; + void registerParent(const W::Address& address, W::Handler* handler); + + private: + typedef std::map Hmap; + Hmap handlers; + }; +} + +#endif // PARENTREPORTER_H diff --git a/lib/wModel/CMakeLists.txt b/lib/wModel/CMakeLists.txt new file mode 100644 index 0000000..cd37fb7 --- /dev/null +++ b/lib/wModel/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12) +project(model) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + model.h + modelstring.h + list.h + vocabulary.h + attributes.h + icatalogue.h + catalogue.h + file/file.h +) + +set(SOURCES + model.cpp + modelstring.cpp + list.cpp + vocabulary.cpp + attributes.cpp + icatalogue.cpp + catalogue.cpp + file/file.cpp +) + +add_library(wModel STATIC ${HEADERS} ${SOURCES}) + +target_link_libraries(wModel Qt5::Core) +target_link_libraries(wModel wSocket) +target_link_libraries(wModel wDispatcher) +target_link_libraries(wModel wType) diff --git a/lib/wModel/attributes.cpp b/lib/wModel/attributes.cpp new file mode 100644 index 0000000..b685818 --- /dev/null +++ b/lib/wModel/attributes.cpp @@ -0,0 +1,60 @@ +#include "attributes.h" + +M::Attributes::Attributes(const W::Address p_address, QObject* parent): + M::Vocabulary(p_address, parent), + attributes(new Map()) +{ +} + +M::Attributes::~Attributes() +{ + delete attributes; +} + +M::Model::ModelType M::Attributes::getType() const +{ + return type; +} + +void M::Attributes::addAttribute(const W::String& key, M::Model* model) +{ + Map::const_iterator itr = attributes->find(key); + if (itr != attributes->end()) { + throw 1; + } + + attributes->insert(std::make_pair(key, model)); + addModel(model); + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"name", key); + vc->insert(u"address", model->getAddress()); + vc->insert(u"type", W::Uint64(model->getType())); + + insert(key, vc); +} + +void M::Attributes::removeAttribute(const W::String& key) +{ + Map::const_iterator itr = attributes->find(key); + if (itr == attributes->end()) { + throw 1; + } + M::Model* model = itr->second; + attributes->erase(itr); + erase(key); + removeModel(model); + + delete model; +} + +void M::Attributes::setAttribute(const W::String& key, const W::Object& value) +{ + Map::const_iterator itr = attributes->find(key); + itr->second->set(value); +} + +void M::Attributes::setAttribute(const W::String& key, W::Object* value) +{ + Map::const_iterator itr = attributes->find(key); + itr->second->set(value); +} diff --git a/lib/wModel/attributes.h b/lib/wModel/attributes.h new file mode 100644 index 0000000..fc7375f --- /dev/null +++ b/lib/wModel/attributes.h @@ -0,0 +1,32 @@ +#ifndef M_ATTRIBUTES_H +#define M_ATTRIBUTES_H + +#include "vocabulary.h" + +#include + +#include + +namespace M { + class Attributes : public M::Vocabulary + { + public: + Attributes(const W::Address p_address, QObject* parent = 0); + ~Attributes(); + + void addAttribute(const W::String& key, M::Model* model); + void removeAttribute(const W::String& key); + void setAttribute(const W::String& key, const W::Object& value); + void setAttribute(const W::String& key, W::Object* value); + + M::Model::ModelType getType() const override; + static const M::Model::ModelType type = attributes; + + private: + typedef std::map Map; + + Map* attributes; + }; +} + +#endif // ATTRIBUTES_H diff --git a/lib/wModel/catalogue.cpp b/lib/wModel/catalogue.cpp new file mode 100644 index 0000000..29a5948 --- /dev/null +++ b/lib/wModel/catalogue.cpp @@ -0,0 +1,66 @@ +#include "catalogue.h" + +M::Catalogue::Catalogue(const W::Address p_address, QObject* parent): + ICatalogue(p_address, parent), + data() +{ +} + +M::Catalogue::~Catalogue() +{ +} + +uint64_t M::Catalogue::addElement(const W::Vocabulary& record) +{ + uint64_t id = M::ICatalogue::addElement(record); + + data.insert(std::make_pair(id, static_cast(record.copy()))); + return id; +} + +void M::Catalogue::removeElement(uint64_t id) +{ + M::ICatalogue::removeElement(id); + + Data::const_iterator itr = data.find(id); + delete itr->second; + data.erase(itr); +} + +void M::Catalogue::clear() +{ + M::ICatalogue::clear(); + + data.clear(); +} + + +W::Vocabulary * M::Catalogue::getElement(uint64_t id) +{ + return static_cast(data.at(id)->copy()); +} +std::set M::Catalogue::getAll() const +{ + std::set res; + + Data::const_iterator itr = data.begin(); + Data::const_iterator end = data.end(); + + for (; itr != end; ++itr) { + res.insert(itr->first); + } + + return res; +} + +void M::Catalogue::modifyElement(uint64_t id, const W::Vocabulary& newValue) +{ + Data::iterator itr = data.find(id); + delete itr->second; + itr->second = static_cast(newValue.copy()); +} + +uint64_t M::Catalogue::size() const +{ + return data.size(); +} diff --git a/lib/wModel/catalogue.h b/lib/wModel/catalogue.h new file mode 100644 index 0000000..4555460 --- /dev/null +++ b/lib/wModel/catalogue.h @@ -0,0 +1,29 @@ +#ifndef CATALOGUE_H +#define CATALOGUE_H + +#include "icatalogue.h" + +namespace M { + + class Catalogue : public ICatalogue { + public: + Catalogue(const W::Address p_address, QObject* parent = 0); + ~Catalogue(); + + uint64_t addElement(const W::Vocabulary & record) override; + void removeElement(uint64_t id) override; + void clear() override; + W::Vocabulary* getElement(uint64_t id) override; + void modifyElement(uint64_t id, const W::Vocabulary & newValue) override; + uint64_t size() const override; + + protected: + std::set getAll() const override; + + private: + typedef std::map Data; + Data data; + }; +} + +#endif // CATALOGUE_H diff --git a/lib/wModel/file/file.cpp b/lib/wModel/file/file.cpp new file mode 100644 index 0000000..083ec62 --- /dev/null +++ b/lib/wModel/file/file.cpp @@ -0,0 +1,86 @@ +#include "file.h" +#include + +QMimeDatabase M::File::mimeDB; + +M::File::File(W::Blob* p_file, const W::Address& addr, QObject* parent): + M::Model(addr, parent), + additional(), + file(p_file) +{ + W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &M::File::_h_get); + W::Handler* getAdditional = W::Handler::create(address + W::Address({u"getAdditional"}), this, &M::File::_h_getAdditional); + + addHandler(get); + addHandler(getAdditional); +} + +M::File::~File() +{ + delete file; +} + +M::Model::ModelType M::File::getType() const +{ + return type; +} + +void M::File::initAdditional(const W::String& p_mime) +{ + additional.clear(); + + additional.insert(u"size", new W::Uint64(file->size())); + additional.insert(u"mimeType", p_mime); +} + +void M::File::set(const W::Object& value) +{ + set(value.copy()); +} + +void M::File::set(W::Object* value) +{ + delete file; + file = static_cast(value); + + QMimeType mt = mimeDB.mimeTypeForData(file->byteArray()); + initAdditional(W::String(mt.name().toStdString())); + + W::Vocabulary* vc = static_cast(additional.copy()); + + broadcast(vc, W::Address({u"getAdditional"})); +} + +void M::File::h_getAdditional(const W::Event& ev) +{ + W::Vocabulary* vc = static_cast(additional.copy()); + + response(vc, W::Address({u"getAdditional"}), ev); +} + +void M::File::h_subscribe(const W::Event& ev) +{ + M::Model::h_subscribe(ev); + + h_getAdditional(ev); +} + +void M::File::h_get(const W::Event& ev) +{ + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"additional", additional.copy()); + vc->insert(u"data", file->copy()); + + response(vc, W::Address({u"get"}), ev); +} + +M::File * M::File::create(W::Blob* blob, const W::Address& addr, QObject* parent) +{ + M::File* out; + + QMimeType mt = mimeDB.mimeTypeForData(blob->byteArray()); + out = new File(blob, addr, parent); + + out->initAdditional(W::String(mt.name().toStdString())); + return out; +} diff --git a/lib/wModel/file/file.h b/lib/wModel/file/file.h new file mode 100644 index 0000000..7a3d79d --- /dev/null +++ b/lib/wModel/file/file.h @@ -0,0 +1,44 @@ +#ifndef FILE_H +#define FILE_H + +/** + * @todo write docs + */ + +#include + +#include +#include + +namespace M { + + class File: public Model { + protected: + File(W::Blob* p_file, const W::Address& addr, QObject* parent = 0); + public: + ~File(); + + void set(const W::Object & value) override; + void set(W::Object * value) override; + + M::Model::ModelType getType() const override; + static const M::Model::ModelType type = file; + + static File* create(W::Blob* blob, const W::Address& addr, QObject* parent = 0); + + protected: + virtual void initAdditional(const W::String& p_mime); + void h_subscribe(const W::Event & ev) override; + + handler(get); + handler(getAdditional); + + protected: + W::Vocabulary additional; + W::Blob* file; + + static QMimeDatabase mimeDB; + }; +} + +#endif // FILE_H diff --git a/lib/wModel/icatalogue.cpp b/lib/wModel/icatalogue.cpp new file mode 100644 index 0000000..3eb4401 --- /dev/null +++ b/lib/wModel/icatalogue.cpp @@ -0,0 +1,469 @@ +#include "icatalogue.h" + +const std::set M::ICatalogue::empty = std::set(); + +M::ICatalogue::ICatalogue(const W::Address p_address, QObject* parent): + M::Model(p_address, parent), + subscribeMember(W::Handler::create(W::Address({}), this, &M::ICatalogue::_h_subscribeMember)), + indexes(), + lastIndex(0), + activeChildren() +{ + W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &M::ICatalogue::_h_get); + W::Handler* add = W::Handler::create(address + W::Address({u"add"}), this, &M::ICatalogue::_h_add); + W::Handler* update = W::Handler::create(address + W::Address({u"update"}), this, &M::ICatalogue::_h_update); + + addHandler(get); + addHandler(add); + addHandler(update); +} + +M::ICatalogue::~ICatalogue() +{ + delete subscribeMember; + + IndexMap::const_iterator itr = indexes.begin(); + IndexMap::const_iterator end = indexes.end(); + + for (; itr != end; ++itr) { + delete itr->second; + } +} + +void M::ICatalogue::clear() +{ + lastIndex = 0; + IndexMap::const_iterator itr = indexes.begin(); + IndexMap::const_iterator end = indexes.end(); + + for (; itr != end; ++itr) { + itr->second->clear(); + } + + if (registered) { + broadcast(new W::Vocabulary(), W::Address{u"clear"}); + } + + emit countChange(0); +} + + +void M::ICatalogue::addIndex(const W::String& fieldName, W::Object::objectType fieldType) +{ + IndexMap::const_iterator itr = indexes.find(fieldName); + if (itr != indexes.end()) { + throw 2; + } + + switch (fieldType) { + case W::Object::uint64: + indexes.insert(std::make_pair(fieldName, new Index())); + break; + case W::Object::string: + indexes.insert(std::make_pair(fieldName, new Index())); + break; + + default: + throw 3; + } +} + +const std::set & M::ICatalogue::find(const W::String& indexName, const W::Object& value) const +{ + IndexMap::const_iterator itr = indexes.find(indexName); + if (itr == indexes.end()) { + throw 4; + } + + return itr->second->find(value); +} + + +std::set M::ICatalogue::find(const W::Vocabulary& value) const +{ + W::Vector keys = value.keys(); + int size = keys.length(); + + std::set result; + bool first = true; + + for (int i = 0; i < size; ++i) { + const W::String& key = static_cast(keys.at(i)); + IndexMap::const_iterator itr = indexes.find(key); + if (itr == indexes.end()) { + throw 4; + } + if (first) { + result = itr->second->find(value.at(key)); + first = false; + } else { + std::set copy = result; + result.clear(); + const std::set& current = itr->second->find(value.at(key)); + std::set_intersection(copy.begin(), copy.end(), current.begin(), current.end(), std::inserter(result, result.end())); + } + + if (result.empty()) { + break; + } + } + + return result; +} + +M::Model::ModelType M::ICatalogue::getType() const +{ + return type; +} + +uint64_t M::ICatalogue::addElement(const W::Vocabulary& record) +{ + IndexMap::const_iterator itr = indexes.begin(); + IndexMap::const_iterator end = indexes.end(); + + ++lastIndex; + + for (; itr != end; ++itr) { + itr->second->add(record.at(itr->first), lastIndex); + } + + Map::const_iterator sItr = subscribers->begin(); + Map::const_iterator sEnd = subscribers->end(); + + for (; sItr != sEnd; ++sItr) { + SMap::const_iterator oItr = sItr->second.begin(); + SMap::const_iterator oEnd = sItr->second.end(); + + for (; oItr != oEnd; ++oItr) { + const W::Vocabulary& params = oItr->second; + if (params.has(u"filter")) { + processAddElement(lastIndex, record, oItr, sItr->first); + } else { + uint64_t bid = getInsertingNeighbour(oItr->second, record, lastIndex, getAll()); + + W::Address dest = oItr->first + W::Address({u"addElement"}); + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(lastIndex)); + if (bid != 0) { + vc->insert(u"before", new W::Uint64(bid)); + } + send(vc, dest, sItr->first); + } + } + } + + emit countChange(size() + 1); + return lastIndex; +} + +void M::ICatalogue::removeElement(uint64_t id) +{ + IndexMap::const_iterator itr = indexes.begin(); + IndexMap::const_iterator end = indexes.end(); + + W::Vocabulary* value = getElement(id); + + Map::const_iterator sItr = subscribers->begin(); + Map::const_iterator sEnd = subscribers->end(); + + for (; sItr != sEnd; ++sItr) { + SMap::const_iterator oItr = sItr->second.begin(); + SMap::const_iterator oEnd = sItr->second.end(); + + for (; oItr != oEnd; ++oItr) { + const W::Vocabulary& params = oItr->second; + if (params.has(u"filter")) { + const W::Vocabulary& filter = static_cast(params.at(u"filter")); + std::set set = find(filter); + std::set::const_iterator idItr = set.find(id); + if (idItr != set.end()) { + W::Address dest = oItr->first + W::Address({u"removeElement"}); + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(id)); + send(vc, dest, sItr->first); + } + } else { + W::Address dest = oItr->first + W::Address({u"removeElement"}); + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(id)); + send(vc, dest, sItr->first); + } + } + } + + for (; itr != end; ++itr) { + itr->second->remove(value->at(itr->first), id); + } + + std::map::const_iterator aItr = activeChildren.find(id); + if (aItr != activeChildren.end()) { + removeModel(aItr->second); + aItr->second->deleteLater(); + activeChildren.erase(aItr); + } + + emit countChange(size() - 1); + delete value; +} + +void M::ICatalogue::h_get(const W::Event& ev) +{ + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::Vocabulary& params = static_cast(vc.at(u"params")); + + std::set set; + if (params.has(u"filter")) { + const W::Vocabulary& filter = static_cast(params.at(u"filter")); + set = find(filter); + } else { + set = getAll(); + } + + W::Vocabulary* rvc = new W::Vocabulary; + if (params.has(u"sorting")) { + const W::Vocabulary& sorting = static_cast(params.at(u"sorting")); + const W::String& field = static_cast(sorting.at(u"field")); + bool ascending = static_cast(sorting.at(u"ascending")); + rvc->insert(u"data", indexes.at(field)->sort(set, ascending)); + } else { + W::Vector* order = new W::Vector(); + std::set::const_iterator itr = set.begin(); + std::set::const_iterator end = set.end(); + + for (; itr != end; ++itr) { + order->push(W::Uint64(*itr)); + } + rvc->insert(u"data", order); + } + + response(rvc, W::Address({u"get"}), ev); +} + +void M::ICatalogue::h_subscribe(const W::Event& ev) +{ + M::Model::h_subscribe(ev); + + h_get(ev); +} + +void M::ICatalogue::set(const W::Object& value) +{ + throw 14; //what do you expect here? not implemented, and not sure it ever would be +} + +void M::ICatalogue::set(W::Object* value) +{ + set(*value); +} + +void M::ICatalogue::h_add(const W::Event& ev) +{ + addElement(static_cast(ev.getData())); +} + +void M::ICatalogue::h_update(const W::Event& ev) +{ + const W::Vocabulary& data = static_cast(ev.getData()); + + const W::Uint64& id = static_cast(data.at(u"id")); + const W::Vocabulary& newValue = static_cast(data.at(u"value")); + W::Vector affectedKeys = newValue.keys(); + + W::Vocabulary* oldValue = getElement(id); + W::Vocabulary* modifiedValue = W::Vocabulary::extend(*oldValue, newValue); + + modifyElement(id, *modifiedValue); + + std::map::const_iterator itr = activeChildren.find(id); + if (itr != activeChildren.end()) { + itr->second->set(modifiedValue); + } + + Map::const_iterator sItr = subscribers->begin(); + Map::const_iterator sEnd = subscribers->end(); + + for (; sItr != sEnd; ++sItr) { + SMap::const_iterator oItr = sItr->second.begin(); + SMap::const_iterator oEnd = sItr->second.end(); + + for (; oItr != oEnd; ++oItr) { + const W::Vocabulary& params = oItr->second; + if (params.has(u"filter")) { + const W::Vocabulary& filter = static_cast(params.at(u"filter")); + bool matched = match(*oldValue, filter); + bool matching = match(*modifiedValue, filter); + + if (matched && !matching) { + W::Address dest = oItr->first + W::Address({u"removeElement"}); + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(id)); + send(vc, dest, sItr->first); + } else if (!matched && matching) { + processAddElement(id, *modifiedValue, oItr, sItr->first); + } else if (matched && matching) { + std::set set = find(filter); + uint64_t cbid = getInsertingNeighbour(params, *oldValue, id, set); + uint64_t bid = getInsertingNeighbour(params, *modifiedValue, id, set); + + if (cbid != bid) { + W::Address dest = oItr->first + W::Address({u"moveElement"}); + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(id)); + if (id != 0) { + vc->insert(u"before", new W::Uint64(bid)); + } + send(vc, dest, sItr->first); + } + } + } else { + if (params.has(u"sorting")) { + std::set set = getAll(); + uint64_t cbid = getInsertingNeighbour(params, *oldValue, id, set); + uint64_t bid = getInsertingNeighbour(params, *modifiedValue, id, set); + + if (cbid != bid) { + W::Address dest = oItr->first + W::Address({u"moveElement"}); + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(id)); + if (id != 0) { + vc->insert(u"before", new W::Uint64(bid)); + } + send(vc, dest, sItr->first); + } + } + } + } + } +} + +void M::ICatalogue::h_subscribeMember(const W::Event& ev) +{ + const W::Address& addr = ev.getDestination(); + W::Address lastHops = addr << address.length(); + + if (lastHops.length() == 2 && (lastHops.ends(W::Address{u"subscribe"}) || lastHops.ends(W::Address{u"get"}))) { + W::Vocabulary* record; + try { + uint64_t id = lastHops.front().toUint64(); + record = getElement(id); + if (lastHops.ends(W::Address{u"subscribe"})) { + M::Vocabulary* modelRecord = new M::Vocabulary(record, address + lastHops >> 1); + addModel(modelRecord); + activeChildren.insert(std::make_pair(id, modelRecord)); + modelRecord->_h_subscribe(ev); + } else { + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"data", record); + + fakeResponse(vc, W::Address({u"get"}), addr >> 1, ev); + } + } catch(int err) { + if (err == 3) { + emit serviceMessage(QString("An attempt to create and subscribe record model in catalogue, but it is not found. Event: ") + ev.toString().c_str()); + } else { + throw err; + } + } catch (const std::invalid_argument& err) { + emit serviceMessage(QString("Strange event in custom handler of catalogue ") + ev.toString().c_str()); + } + } else { + emit serviceMessage(QString("Strange event in custom handler of catalogue ") + ev.toString().c_str()); + } + +} + +bool M::ICatalogue::match(const W::Vocabulary& value, const W::Vocabulary& filter) +{ + bool m = true; + W::Vector keys = filter.keys(); + for (int i = 0; i < keys.length(); ++i) { + const W::String& key = static_cast(keys.at(i)); + if (filter.at(key) != value.at(key)) { + m = false; + break; + }; + } + + return m; +} + +void M::ICatalogue::processAddElement(uint64_t id, const W::Vocabulary& value, SMap::const_iterator subscriberIterator, uint64_t socketId) +{ + const W::Address& addr = subscriberIterator->first; + const W::Vocabulary& params = subscriberIterator->second; + const W::Vocabulary& filter = static_cast(params.at(u"filter")); + + std::set set = find(filter); + std::set::const_iterator idItr = set.find(id); + if (idItr != set.end()) { //to make sure if subscriber cares + uint64_t bid = getInsertingNeighbour(params, value, id, set); + + W::Address dest = addr + W::Address({u"addElement"}); + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"id", new W::Uint64(id)); + if (id != 0) { + vc->insert(u"before", new W::Uint64(bid)); + } + send(vc, dest, socketId); + } +} + +uint64_t M::ICatalogue::getInsertingNeighbour(const W::Vocabulary& params, const W::Vocabulary& record, uint64_t id, const std::set& allowed) const +{ + uint64_t bid; + if (params.has(u"sorting")) { + if (allowed.empty()) { + bid = 0; + } else { + const W::Vocabulary& sorting = static_cast(params.at(u"sorting")); + const W::String& field = static_cast(sorting.at(u"field")); + bool ascending = static_cast(sorting.at(u"ascending")); + + uint64_t foundId = id; + do { + if (ascending) { + foundId = indexes.at(field)->getNext(foundId, record.at(field)); + } else { + foundId = indexes.at(field)->getPrev(foundId, record.at(field)); + } + } while (allowed.find(foundId) == allowed.end() || foundId != 0); //to make sure, that id folowing the inserting also present in the + bid = foundId; //subscribers filter result + } + } else { + std::set::const_iterator idItr = allowed.find(id); + if (idItr == allowed.end()) { + bid = 0; + } else { + ++idItr; + if (idItr == allowed.end()) { + bid = 0; + } else { + bid = *idItr; + } + } + } + + return bid; +} + +M::ICatalogue::AbstractIndex::TypeError::TypeError(const std::string& name, const std::string& method, W::Object::objectType myType, W::Object::objectType valueType): + Utils::Exception(), + name(name), + method(method), + myType(myType), + valueType(valueType) +{} + +std::string M::ICatalogue::AbstractIndex::TypeError::getMessage() const +{ + std::string msg = "An attempt to call Catalogue Index of "; + msg += name; + msg += " method \""; + msg += method; + msg += "\" with value type of "; + msg += W::Object::getTypeName(valueType); + msg += " but this index values supposed to have type "; + msg += W::Object::getTypeName(myType); + + return msg; +} diff --git a/lib/wModel/icatalogue.h b/lib/wModel/icatalogue.h new file mode 100644 index 0000000..9a9ac64 --- /dev/null +++ b/lib/wModel/icatalogue.h @@ -0,0 +1,316 @@ +#ifndef ICATALOGUE_H +#define ICATALOGUE_H + +#include "model.h" + +#include +#include + +#include +#include + +namespace M { + class ICatalogue : public M::Model + { + Q_OBJECT + protected: + class AbstractIndex; + public: + ICatalogue(const W::Address p_address, QObject* parent = 0); + ~ICatalogue(); + + virtual uint64_t addElement(const W::Vocabulary& record); + virtual void removeElement(uint64_t id); + virtual W::Vocabulary* getElement(uint64_t id) = 0; + virtual void modifyElement(uint64_t id, const W::Vocabulary& newValue) = 0; + virtual uint64_t size() const = 0; + virtual void clear(); + + virtual void addIndex(const W::String& fieldName, W::Object::objectType fieldType); + const std::set& find(const W::String& indexName, const W::Object& value) const; + std::set find(const W::Vocabulary& value) const; + + M::Model::ModelType getType() const override; + static const M::Model::ModelType type = catalogue; + void set(const W::Object & value) override; + void set(W::Object * value) override; + + W::Handler* subscribeMember; + handler(subscribeMember); + + static bool match(const W::Vocabulary& value, const W::Vocabulary& filter); + static const std::set empty; + + signals: + void countChange(uint64_t count); + + protected: + virtual std::set getAll() const = 0; + void h_subscribe(const W::Event & ev) override; + + handler(get); + handler(add); + handler(update); + + typedef std::map IndexMap; + IndexMap indexes; + + private: + uint64_t lastIndex; + std::map activeChildren; + + void processAddElement(uint64_t id, const W::Vocabulary& value, SMap::const_iterator subscriberIterator, uint64_t socketId); + uint64_t getInsertingNeighbour(const W::Vocabulary& params, const W::Vocabulary& record, uint64_t id, const std::set& allowed = empty) const; + + protected: + class AbstractIndex { + public: + AbstractIndex(W::Object::objectType vt): valueType(vt) {} + virtual ~AbstractIndex() {} + + virtual const std::set& find(const W::Object& value) const = 0; + virtual void add(const W::Object& value, uint64_t id) = 0; + virtual void remove(const W::Object & value, uint64_t id) = 0; + virtual void clear() = 0; + virtual W::Vector sort(const std::set& set, bool ascending) = 0; + virtual uint64_t getNext(uint64_t id, const W::Object& value) = 0; + virtual uint64_t getPrev(uint64_t id, const W::Object& value) = 0; + + W::Object::objectType valueType; + + protected: + class TypeError : public Utils::Exception { + public: + TypeError(const std::string& name, const std::string& method, W::Object::objectType myType, W::Object::objectType valueType); + + std::string getMessage() const; + + private: + std::string name; + std::string method; + W::Object::objectType myType; + W::Object::objectType valueType; + }; + }; + + template + class Index : public AbstractIndex { + public: + Index(); + ~Index(); + + const std::set& find(const W::Object& value) const override; + void add(const W::Object & value, uint64_t id) override; + void remove(const W::Object & value, uint64_t id) override; + void clear() override; + W::Vector sort(const std::set & set, bool ascending) override; + uint64_t getNext(uint64_t id, const W::Object& value) override; + uint64_t getPrev(uint64_t id, const W::Object& value) override; + + private: + typedef std::map> Map; + + Map values; + + }; + }; + + + + template + ICatalogue::Index::Index(): + ICatalogue::AbstractIndex(T::type), + values() + { + } + + template + ICatalogue::Index::~Index() + { + } + + template + const std::set & ICatalogue::Index::find(const W::Object& value) const + { + if (value.getType() != valueType) { + throw new TypeError("Unknown", "find", valueType, value.getType()); //todo replace that unknown stuff, find a way to provide index name + } + + const T& val = static_cast(value); + typename std::map>::const_iterator itr = values.find(val); + + if (itr == values.end()) { + return ICatalogue::empty; + } else { + return itr->second; + } + } + + template + void ICatalogue::Index::add(const W::Object& value, uint64_t id) + { + if (value.getType() != valueType) { + throw new TypeError("Unknown", "add", valueType, value.getType()); + } + + const T& val = static_cast(value); + typename std::map>::iterator itr = values.find(val); + if (itr == values.end()) { + itr = values.insert(std::make_pair(val, std::set())).first; + } + itr->second.insert(id); + } + + template + void ICatalogue::Index::remove(const W::Object& value, uint64_t id) + { + if (value.getType() != valueType) { + throw new TypeError("Unknown", "remove", valueType, value.getType()); + } + const T& val = static_cast(value); + typename std::map>::iterator itr = values.find(val); + if (itr != values.end()) { + std::set& set = itr->second; + if (set.size() == 1) { + values.erase(itr); + } else { + std::set::const_iterator hint = set.find(id); + set.erase(hint); + } + } + } + + template + void ICatalogue::Index::clear() + { + values.clear(); + } + + template + W::Vector ICatalogue::Index::sort(const std::set & set, bool ascending) //TODO this needs an optimization + { + W::Vector res; + std::set::const_iterator sEnd = set.end(); + uint64_t size = set.size(); + + if (size == 0) { + return res; + } else if (size == 1) { + res.push(W::Uint64(*(set.begin()))); + return res; + } + if (ascending) { + typename std::map>::const_iterator itr = values.begin(); + typename std::map>::const_iterator end = values.end(); + + for (; itr != end; ++itr) { + if (size == res.size()) { + break; + } + const std::set& chunk = itr->second; + + std::set::const_iterator cItr = chunk.begin(); + std::set::const_iterator cEnd = chunk.end(); + for (; cItr != cEnd; ++cItr) { + uint64_t id = *cItr; + if (set.find(id) != sEnd) { + res.push(W::Uint64(id)); + } + } + } + } else { + typename std::map>::reverse_iterator itr = values.rbegin(); + typename std::map>::reverse_iterator end = values.rend(); + + for (; itr != end; ++itr) { + if (size == res.size()) { + break; + } + const std::set& chunk = itr->second; + + std::set::const_iterator cItr = chunk.begin(); + std::set::const_iterator cEnd = chunk.end(); + for (; cItr != cEnd; ++cItr) { + uint64_t id = *cItr; + if (set.find(id) != sEnd) { + res.push(W::Uint64(id)); + } + } + } + } + + return res; + } + + template + uint64_t ICatalogue::Index::getNext(uint64_t id, const W::Object& value) + { + if (value.getType() != valueType) { + throw new TypeError("Unknown", "getNext", valueType, value.getType()); + } + const T& val = static_cast(value); + typename std::map>::iterator itr = values.find(val); + if (itr == values.end()) { + throw 2; //this is not suppose to happen! + } + const std::set& set = itr->second; + std::set::const_iterator sItr = set.find(id); + if (sItr == set.end()) { + throw 2; //not suppose to happen! + } + ++sItr; + if (sItr == set.end()) { + ++itr; + bool found = false; + while (itr != values.end()) { + if (itr->second.size() != 0) { + sItr = set.begin(); + found = true; + break; + } + ++itr; + } + if (!found) { + return 0; + } + } + return *sItr; + } + + template + uint64_t ICatalogue::Index::getPrev(uint64_t id, const W::Object& value) + { + if (value.getType() != valueType) { + throw new TypeError("Unknown", "getPrev", valueType, value.getType()); + } + const T& val = static_cast(value); + typename std::map>::iterator itr = values.find(val); + if (itr == values.end()) { + throw 2; //this is not suppose to happen! + } + const std::set& set = itr->second; + std::set::const_iterator sItr = set.find(id); + if (sItr == set.end()) { + throw 2; //not suppose to happen! + } + if (sItr == set.begin()) { + bool found = false; + while (itr != values.begin()) { + --itr; + if (itr->second.size() != 0) { + sItr = set.end(); + --sItr; + break; + } + } + if (!found) { + return 0; + } + } else { + --sItr; + } + return *sItr; + } +} + +#endif // ICATALOGUE_H diff --git a/lib/wModel/list.cpp b/lib/wModel/list.cpp new file mode 100644 index 0000000..d5fd923 --- /dev/null +++ b/lib/wModel/list.cpp @@ -0,0 +1,100 @@ +#include "list.h" + +M::List::List(const W::Address p_address, QObject* parent): + M::Model(p_address, parent), + data(new W::Vector()) +{ + W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &M::List::_h_get); + addHandler(get); +} + +M::List::~List() +{ + delete data; +} + +void M::List::h_subscribe(const W::Event& ev) +{ + M::Model::h_subscribe(ev); + + h_get(ev); +} + +void M::List::h_get(const W::Event& ev) +{ + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"data", data->copy()); + + response(vc, W::Address({u"get"}), ev); +} + +void M::List::push(const W::Object& obj) +{ + data->push(obj); + + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"data", obj); + + broadcast(vc, W::Address{u"push"}); + } +} + +void M::List::push(W::Object* obj) +{ + data->push(obj); + + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"data", obj->copy()); + + broadcast(vc, W::Address{u"push"}); + } +} + +void M::List::clear() +{ + data->clear(); + + if (registered) { + broadcast(new W::Vocabulary(), W::Address{u"clear"}); + } +} + +M::Model::ModelType M::List::getType() const +{ + return type; +} + +void M::List::set(const W::Object& value) +{ + delete data; + data = static_cast(value.copy()); + + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"data", data->copy()); + + broadcast(vc, W::Address({u"get"})); +} + +void M::List::set(W::Object* value) +{ + delete data; + data = static_cast(value); + + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"data", data->copy()); + + broadcast(vc, W::Address({u"get"})); +} + +uint64_t M::List::size() const +{ + return data->size(); +} + +const W::Object & M::List::at(uint64_t index) const +{ + return data->at(index); +} + diff --git a/lib/wModel/list.h b/lib/wModel/list.h new file mode 100644 index 0000000..df0d437 --- /dev/null +++ b/lib/wModel/list.h @@ -0,0 +1,41 @@ +#ifndef M_LIST_H +#define M_LIST_H + +#include "model.h" + +#include +#include +#include + +namespace M { + class List : public M::Model + { + public: + List(const W::Address p_address, QObject* parent = 0); + ~List(); + + void push(const W::Object& obj); + void push(W::Object* obj); + void clear(); + + uint64_t size() const; + const W::Object& at(uint64_t index) const; + + void set(const W::Object & value) override; + void set(W::Object * value) override; + + M::Model::ModelType getType() const override; + static const M::Model::ModelType type = list; + + protected: + void h_subscribe(const W::Event & ev) override; + + handler(get); + + private: + W::Vector* data; + + }; +} + +#endif // M_LIST_H diff --git a/lib/wModel/model.cpp b/lib/wModel/model.cpp new file mode 100644 index 0000000..d1168cd --- /dev/null +++ b/lib/wModel/model.cpp @@ -0,0 +1,339 @@ +#include "model.h" + +M::Model::Model(const W::Address p_address, QObject* parent): + QObject(parent), + address(p_address), + registered(false), + subscribers(new Map()), + dispatcher(0), + server(0), + subscribersCount(0), + handlers(new HList()), + properties(new W::Vector()), + models(new MList()) +{ + W::Handler* subscribe = W::Handler::create(address + W::Address({u"subscribe"}), this, &M::Model::_h_subscribe); + W::Handler* unsubscribe = W::Handler::create(address + W::Address({u"unsubscribe"}), this, &M::Model::_h_unsubscribe); + addHandler(subscribe); + addHandler(unsubscribe); +} + +M::Model::~Model() +{ + if (registered) { + unregisterModel(); + } + + MList::iterator itr = models->begin(); + MList::iterator end = models->end(); + + for (; itr != end; ++itr) { + delete *itr; + } + + HList::iterator hItr = handlers->begin(); + HList::iterator hEnd = handlers->end(); + + for (; hItr != hEnd; ++hItr) { + delete *hItr; + } + + delete subscribers; + delete properties; + delete handlers; + delete models; +} + +void M::Model::addModel(M::Model* model) +{ + models->push_back(model); + connect(model, SIGNAL(serviceMessage(const QString&)), SIGNAL(serviceMessage(const QString&))); + if (registered) { + model->registerModel(dispatcher, server); + } +} + +void M::Model::addHandler(W::Handler* handler) +{ + handlers->push_back(handler); + if (registered) { + dispatcher->registerHandler(handler); + } +} + +void M::Model::addProperty(const W::String& value, const W::String& name) +{ + W::Vocabulary vc; + vc.insert(u"key", name); + vc.insert(u"property", value); + + properties->push(vc); + + if (registered) { + W::Vocabulary* nvc = new W::Vocabulary; + nvc->insert(u"properties", *properties); + broadcast(nvc, W::Address({u"properties"})); + } +} + + +W::Address M::Model::getAddress() const +{ + return address; +} + + +void M::Model::registerModel(W::Dispatcher* dp, W::Server* srv) +{ + if (registered) { + emit serviceMessage(QString("Model ") + address.toString().c_str() + " is already registered"); + throw 1; + } else { + dispatcher = dp; + server = srv; + + MList::iterator itr = models->begin(); + MList::iterator end = models->end(); + + for (; itr != end; ++itr) { + M::Model* model = *itr; + model->registerModel(dispatcher, server); + } + + HList::iterator hItr = handlers->begin(); + HList::iterator hEnd = handlers->end(); + + for (; hItr != hEnd; ++hItr) { + W::Handler* handler = *hItr; + dispatcher->registerHandler(handler); + } + + registered = true; + } +} + +void M::Model::unregisterModel() +{ + if (!registered) { + emit serviceMessage(QString("Model ") + address.toString().c_str() + " is not registered"); + throw 2; + } else { + MList::iterator itr = models->begin(); + MList::iterator end = models->end(); + + for (; itr != end; ++itr) { + Model* model = *itr; + model->unregisterModel(); + } + + HList::iterator hItr = handlers->begin(); + HList::iterator hEnd = handlers->end(); + + for (; hItr != hEnd; ++hItr) { + W::Handler* handler = *hItr; + dispatcher->unregisterHandler(handler); + } + + Map::iterator sItr = subscribers->begin(); + Map::iterator sEnd = subscribers->end(); + + for (; sItr != sEnd; ++sItr) { + const W::Socket& socket = server->getConnection(sItr->first); + disconnect(&socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + } + subscribers->clear(); + subscribersCount = 0; + + dispatcher = 0; + server = 0; + + registered = false; + } + +} + +void M::Model::h_subscribe(const W::Event& ev) +{ + uint64_t id = ev.getSenderId(); + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::Address& source = static_cast(vc.at(u"source")); + W::Vocabulary params; + if (vc.has(u"params")) { + params = static_cast(vc.at(u"params")); + } + + + Map::iterator sItr = subscribers->find(id); + + if (sItr == subscribers->end()) { + std::pair pair = subscribers->emplace(std::make_pair(id, SMap())); + if (!pair.second) { + emit serviceMessage(QString("Model ") + address.toString().c_str() + ": something completely wrong happened"); + throw 3; + } + const W::Socket& socket = server->getConnection(id); + connect(&socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + sItr = pair.first; + } + SMap::const_iterator oItr = sItr->second.find(source); + if (oItr != sItr->second.end()) { + emit serviceMessage(QString("Socket ") + id + + " subscriber " + source.toString().c_str() + + " is already subscribed to model " + source.toString().c_str()); + throw 4; + } + + sItr->second.insert(std::make_pair(source, params)); + ++subscribersCount; + + W::Vocabulary* nvc = new W::Vocabulary(); + nvc->insert(u"properties", *properties); + + response(nvc, W::Address({u"properties"}), ev); + emit serviceMessage(QString("Model ") + address.toString().c_str() + ": now has " + std::to_string(subscribersCount).c_str() + " subscribers"); + emit subscribersCountChange(subscribersCount); +} + +void M::Model::onSocketDisconnected() +{ + W::Socket* socket = static_cast(sender()); + disconnect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + uint64_t id = socket->getId(); + Map::iterator itr = subscribers->find(id); + + if (itr == subscribers->end()) { + emit serviceMessage(QString("Model ") + address.toString().c_str() + + ": socket disconnected have been handled for not subscribed id"); + throw 5; + } + + subscribersCount -= itr->second.size(); + subscribers->erase(itr); + + emit serviceMessage(QString("Model ") + address.toString().c_str() + ": now has " + std::to_string(subscribersCount).c_str() + " subscribers"); + emit subscribersCountChange(subscribersCount); +} + +void M::Model::h_unsubscribe(const W::Event& ev) +{ + uint64_t id = ev.getSenderId(); + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::Address& source = static_cast(vc.at(u"source")); + + Map::iterator itr = subscribers->find(id); + if (itr == subscribers->end()) { + emit serviceMessage(QString("Socket ") + id + + " has no subscribed addresses to model " + source.toString().c_str()); + throw 6; + } + + SMap& smap = itr->second; + SMap::const_iterator sItr = smap.find(source); + if (sItr == smap.end()) { + emit serviceMessage(QString("Socket ") + id + + " subscriber " + source.toString().c_str() + + " is not subscribed to model " + source.toString().c_str()); + throw 7; + } + + smap.erase(sItr); + if (smap.size() == 0) { + const W::Socket& socket = server->getConnection(itr->first); + disconnect(&socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + subscribers->erase(itr); + } + --subscribersCount; + + emit serviceMessage(QString("Model ") + address.toString().c_str() + ": now has " + std::to_string(subscribersCount).c_str() + " subscribers"); + emit subscribersCountChange(subscribersCount); +} + +void M::Model::send(W::Vocabulary* vc, const W::Address& destination, uint64_t connectionId) +{ + if (!registered) { + emit serviceMessage(QString("An attempt to send event from model ") + address.toString().c_str() + " which was not registered"); + throw 8; + } + W::Event ev(destination, vc); + ev.setSenderId(connectionId); + server->getConnection(connectionId).send(ev); +} + +void M::Model::response(W::Vocabulary* vc, const W::Address& handlerAddress, const W::Event& src) +{ + if (!registered) { + emit serviceMessage(QString("An attempt to send event from model ") + address.toString().c_str() + " which was not registered"); + throw 8; + } + const W::Vocabulary& svc = static_cast(src.getData()); + const W::Address& source = static_cast(svc.at(u"source")); + uint64_t id = src.getSenderId(); + vc->insert(u"source", address); + + W::Event ev(source + handlerAddress, vc); + ev.setSenderId(id); + server->getConnection(id).send(ev); +} + +void M::Model::fakeResponse(W::Vocabulary* vc, const W::Address& handlerAddress, const W::Address& sourceAddress, const W::Event& src) +{ + if (!registered) { + emit serviceMessage(QString("An attempt to send event from model ") + address.toString().c_str() + " which was not registered"); + throw 8; + } + const W::Vocabulary& svc = static_cast(src.getData()); + const W::Address& source = static_cast(svc.at(u"source")); + uint64_t id = src.getSenderId(); + vc->insert(u"source", sourceAddress); + + W::Event ev(source + handlerAddress, vc); + ev.setSenderId(id); + server->getConnection(id).send(ev); +} + +void M::Model::broadcast(W::Vocabulary* vc, const W::Address& handlerAddress) +{ + if (!registered) { + emit serviceMessage(QString("An attempt to send event from model ") + address.toString().c_str() + " which was not registered"); + throw 8; + } + Map::const_iterator itr = subscribers->begin(); + Map::const_iterator end = subscribers->end(); + vc->insert(u"source", address); + + for (;itr != end; ++itr) { + SMap::const_iterator oItr = itr->second.begin(); + SMap::const_iterator oEnd = itr->second.end(); + for (;oItr != oEnd; ++oItr) { + W::Event ev(oItr->first + handlerAddress, vc->copy()); + ev.setSenderId(itr->first); + server->getConnection(itr->first).send(ev); + } + } + delete vc; +} + +void M::Model::removeHandler(W::Handler* handler) +{ + handlers->erase(handler); + if (registered) { + dispatcher->unregisterHandler(handler); + } +} + +void M::Model::removeModel(M::Model* model) +{ + models->erase(model); + if (registered) { + model->unregisterModel(); + } +} + +void M::Model::passToHandler(const W::Event& event) const +{ + if (registered) { + dispatcher->pass(event); + } else { + emit serviceMessage(QString("An attempt to pass event to dispatcher from unregistered model\nModel address ") + address.toString().c_str()); + } +} diff --git a/lib/wModel/model.h b/lib/wModel/model.h new file mode 100644 index 0000000..8b44fc9 --- /dev/null +++ b/lib/wModel/model.h @@ -0,0 +1,92 @@ +#ifndef W_MODEL_H +#define W_MODEL_H + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace M { + + class Model : public QObject + { + Q_OBJECT + public: + enum ModelType { + string, + list, + vocabulary, + catalogue, + + attributes = 50, + file, + resourceCache + }; + + Model(const W::Address p_address, QObject* parent = 0); + //i'm not sure about copy constructor, it just doesn't make sense, because the address is the parameter which is supposed to be unique + virtual ~Model(); + + virtual ModelType getType() const = 0; + virtual void set(W::Object* value) = 0; + virtual void set(const W::Object& value) = 0; + + void addModel(M::Model* model); + void addHandler(W::Handler* handler); + void addProperty(const W::String& value, const W::String& name); + W::Address getAddress() const; + void registerModel(W::Dispatcher* dp, W::Server* srv); + void unregisterModel(); + + void removeHandler(W::Handler* handler); + void removeModel(M::Model* model); + void passToHandler(const W::Event& event) const; + + signals: + void serviceMessage(const QString& msg) const; + void subscribersCountChange(uint64_t count) const; + + protected: + typedef std::map SMap; + typedef std::map Map; + W::Address address; + bool registered; + Map* subscribers; + + void send(W::Vocabulary* vc, const W::Address& destination, uint64_t connectionId); + void response(W::Vocabulary* vc, const W::Address& handlerAddress, const W::Event& src); + void fakeResponse(W::Vocabulary* vc, const W::Address& handlerAddress, const W::Address& sourceAddress, const W::Event& src); + void broadcast(W::Vocabulary* vc, const W::Address& handlerAddress); + + handler(subscribe) + handler(unsubscribe) + + private: + typedef W::Order HList; + typedef W::Order MList; + + W::Dispatcher* dispatcher; + W::Server* server; + uint64_t subscribersCount; + HList* handlers; + W::Vector* properties; + MList* models; + + private slots: + void onSocketDisconnected(); + }; +} +#endif // W_MODEL_H + diff --git a/lib/wModel/modelstring.cpp b/lib/wModel/modelstring.cpp new file mode 100644 index 0000000..a7066e9 --- /dev/null +++ b/lib/wModel/modelstring.cpp @@ -0,0 +1,77 @@ +#include "modelstring.h" + +M::String::String(const W::String& str, const W::Address& addr, QObject* parent): + M::Model(addr, parent), + data(new W::String(str)) +{ + addHandler(W::Handler::create(address + W::Address({u"get"}), this, &M::String::_h_get)); +} + +M::String::String(W::String* str, const W::Address& addr, QObject* parent): + M::Model(addr, parent), + data(str) +{ +} + +M::String::~String() +{ + delete data; +} + +void M::String::h_subscribe(const W::Event& ev) +{ + M::Model::h_subscribe(ev); + + h_get(ev); +} + +void M::String::h_get(const W::Event& ev) +{ + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"data", *data); + + response(vc, W::Address({u"get"}), ev); +} + +void M::String::set(const W::String& str) +{ + delete data; + data = static_cast(str.copy()); + + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"data", str); + + broadcast(vc, W::Address{u"get"}); + } +} + +void M::String::set(W::String* str) +{ + delete data; + data = str; + + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + vc->insert(u"data", *str); + + broadcast(vc, W::Address{u"get"}); + } +} + +void M::String::set(const W::Object& value) +{ + set(static_cast(value)); +} + +void M::String::set(W::Object* value) +{ + set(static_cast(value)); +} + + +M::Model::ModelType M::String::getType() const +{ + return type; +} + diff --git a/lib/wModel/modelstring.h b/lib/wModel/modelstring.h new file mode 100644 index 0000000..d098d5c --- /dev/null +++ b/lib/wModel/modelstring.h @@ -0,0 +1,40 @@ +#ifndef M_STRING_H +#define M_STRING_H + +#include "model.h" + +#include + +#include +#include + +namespace M { + + class String: public Model + { + public: + String(const W::String& str, const W::Address& addr, QObject* parent = 0); + String(W::String* str, const W::Address& addr, QObject* parent = 0); + + ~String(); + + void set(const W::Object & value) override; + void set(W::Object * value) override; + void set(const W::String& str); + void set(W::String* str); + + M::Model::ModelType getType() const override; + static const M::Model::ModelType type = M::Model::string; + + protected: + void h_subscribe(const W::Event& ev); + handler(get) + + private: + W::String* data; + + }; +} + + +#endif // M_STRING_H diff --git a/lib/wModel/vocabulary.cpp b/lib/wModel/vocabulary.cpp new file mode 100644 index 0000000..6539b9f --- /dev/null +++ b/lib/wModel/vocabulary.cpp @@ -0,0 +1,136 @@ +#include "vocabulary.h" + +M::Vocabulary::Vocabulary(const W::Address p_address, QObject* parent): + M::Model(p_address, parent), + data(new W::Vocabulary()) +{ + W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &M::Vocabulary::_h_get); + addHandler(get); +} + +M::Vocabulary::Vocabulary(W::Vocabulary* p_data, const W::Address p_address, QObject* parent): + M::Model(p_address, parent), + data(p_data) +{ + W::Handler* get = W::Handler::create(address + W::Address({u"get"}), this, &M::Vocabulary::_h_get); + addHandler(get); +} + + +M::Vocabulary::~Vocabulary() +{ + delete data; +} + +void M::Vocabulary::h_subscribe(const W::Event& ev) +{ + M::Model::h_subscribe(ev); + + h_get(ev); +} + +void M::Vocabulary::h_get(const W::Event& ev) +{ + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"data", data->copy()); + + response(vc, W::Address({u"get"}), ev); +} + +void M::Vocabulary::insert(const W::String& key, const W::Object& value) +{ + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + W::Vocabulary* insert = new W::Vocabulary(); + W::Vector* erase = new W::Vector(); + + if (data->has(key)) { + erase->push(key); + } + data->insert(key, value); + insert->insert(key, value); + + vc->insert(u"insert", insert); + vc->insert(u"erase", erase); + + broadcast(vc, W::Address{u"change"}); + } else { + data->insert(key, value); + } +} + +void M::Vocabulary::insert(const W::String& key, W::Object* value) +{ + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + W::Vocabulary* insert = new W::Vocabulary(); + W::Vector* erase = new W::Vector(); + + if (data->has(key)) { + erase->push(key); + } + data->insert(key, value); + insert->insert(key, value->copy()); + + vc->insert(u"insert", insert); + vc->insert(u"erase", erase); + + broadcast(vc, W::Address{u"change"}); + } else { + data->insert(key, value); + } +} + +void M::Vocabulary::erase(const W::String& key) +{ + data->erase(key); + if (registered) { + W::Vocabulary* vc = new W::Vocabulary(); + W::Vocabulary* insert = new W::Vocabulary(); + W::Vector* erase = new W::Vector(); + + erase->push(key); + + vc->insert(u"insert", insert); + vc->insert(u"erase", erase); + + broadcast(vc, W::Address{u"change"}); + } +} + +void M::Vocabulary::clear() +{ + data->clear(); + + if (registered) { + broadcast(new W::Vocabulary(), W::Address{u"clear"}); + } +} + +M::Model::ModelType M::Vocabulary::getType() const +{ + return type; +} + +void M::Vocabulary::set(const W::Object& value) +{ + delete data; + data = static_cast(value.copy()); + + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"data", data->copy()); + + broadcast(vc, W::Address({u"get"})); +} + +void M::Vocabulary::set(W::Object* value) +{ + delete data; + data = static_cast(value); + + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"data", data->copy()); + + broadcast(vc, W::Address({u"get"})); +} + diff --git a/lib/wModel/vocabulary.h b/lib/wModel/vocabulary.h new file mode 100644 index 0000000..5ddb250 --- /dev/null +++ b/lib/wModel/vocabulary.h @@ -0,0 +1,45 @@ +#ifndef M_VOCABULARY_H +#define M_VOCABULARY_H + +#include "model.h" + +#include +#include +#include +#include + + +namespace M { + class ICatalogue; + + class Vocabulary : public M::Model + { + friend class ICatalogue; + public: + Vocabulary(const W::Address p_address, QObject* parent = 0); + Vocabulary(W::Vocabulary* p_data, const W::Address p_address, QObject* parent = 0); + ~Vocabulary(); + + void insert(const W::String& key, const W::Object& value); + void insert(const W::String& key, W::Object* value); + void erase(const W::String& key); + void clear(); + + void set(const W::Object & value) override; + void set(W::Object* value) override; + + M::Model::ModelType getType() const override; + static const M::Model::ModelType type = vocabulary; + + protected: + void h_subscribe(const W::Event & ev) override; + + handler(get); + + private: + W::Vocabulary* data; + + }; +} + +#endif // M_VOCABULARY_H diff --git a/lib/wServerUtils/CMakeLists.txt b/lib/wServerUtils/CMakeLists.txt new file mode 100644 index 0000000..39fa60a --- /dev/null +++ b/lib/wServerUtils/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 2.8.12) +project(wServerUtils) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + commands.h + connector.h +) + +set(SOURCES + commands.cpp + connector.cpp +) + +add_library(wServerUtils ${HEADERS} ${SOURCES}) + +target_link_libraries(wServerUtils Qt5::Core) +target_link_libraries(wServerUtils wType) +target_link_libraries(wServerUtils wModel) +target_link_libraries(wServerUtils wSocket) +target_link_libraries(wServerUtils wDispatcher) + diff --git a/lib/wServerUtils/commands.cpp b/lib/wServerUtils/commands.cpp new file mode 100644 index 0000000..8ed8b7c --- /dev/null +++ b/lib/wServerUtils/commands.cpp @@ -0,0 +1,85 @@ +#include "commands.h" + +U::Commands::Commands(const W::Address& address, QObject* parent): + M::Vocabulary(address, parent), + commands(new Map()) +{ +} + +U::Commands::~Commands() +{ + Map::iterator beg = commands->begin(); + Map::iterator end = commands->end(); + + for (; beg != end; ++beg) { + Command* cmd = beg->second; + if (cmd->enabled) { + removeHandler(cmd->handler); + } + delete cmd->handler; + delete cmd; + } + + delete commands; +} + +void U::Commands::addCommand(const W::String& key, W::Handler* handler, const W::Vocabulary& args) +{ + Map::const_iterator itr = commands->find(key); + if (itr != commands->end()) { + throw 1; + } + Command* cmd = new Command{key, handler, args, false}; + commands->insert(std::make_pair(cmd->name, cmd)); +} + +void U::Commands::enableCommand(const W::String& key, bool value) +{ + Map::const_iterator itr = commands->find(key); + if (itr == commands->end()) { + throw 2; + } + + Command* cmd = itr->second; + if (cmd->enabled != value) { + if (value) { + enableCommand(cmd); + } else { + disableCommand(cmd); + } + } +} + +void U::Commands::enableCommand(U::Commands::Command* cmd) +{ + addHandler(cmd->handler); + cmd->enabled = true; + + W::Vocabulary* vc = new W::Vocabulary; + vc->insert(u"address", cmd->handler->getAddress()); + vc->insert(u"arguments", cmd->arguments); + insert(cmd->name, vc); +} + +void U::Commands::disableCommand(U::Commands::Command* cmd) +{ + removeHandler(cmd->handler); + cmd->enabled = false; + erase(cmd->name); +} + +void U::Commands::removeCommand(const W::String& key) +{ + Map::const_iterator itr = commands->find(key); + if (itr == commands->end()) { + throw 2; + } + Command* cmd = itr->second; + if (cmd->enabled) { + disableCommand(cmd); + } + + commands->erase(itr); + delete cmd->handler; + delete cmd; +} diff --git a/lib/wServerUtils/commands.h b/lib/wServerUtils/commands.h new file mode 100644 index 0000000..d5e6fcc --- /dev/null +++ b/lib/wServerUtils/commands.h @@ -0,0 +1,43 @@ +#ifndef SERVERUTILS_COMMANDS_H +#define SERVERUTILS_COMMANDS_H + +#include + +#include +#include +#include +#include + +#include + +namespace U { + + class Commands : public M::Vocabulary + { + struct Command; + typedef std::map Map; + public: + Commands(const W::Address& address, QObject* parent = 0); + ~Commands(); + + void addCommand(const W::String& key, W::Handler* handler, const W::Vocabulary& args); + void removeCommand(const W::String& key); + void enableCommand(const W::String& key, bool value); + + private: + void enableCommand(Command* cmd); + void disableCommand(Command* cmd); + + Map* commands; + + struct Command { + W::String name; + W::Handler* handler; + W::Vocabulary arguments; + bool enabled; + }; + }; + +} + +#endif // SERVERUTILS_COMMANDS_H diff --git a/lib/wServerUtils/connector.cpp b/lib/wServerUtils/connector.cpp new file mode 100644 index 0000000..77b1211 --- /dev/null +++ b/lib/wServerUtils/connector.cpp @@ -0,0 +1,124 @@ +#include "connector.h" + +U::Connector::Connector(W::Dispatcher* dp, W::Server* srv, U::Commands* cmds, QObject* parent): + QObject(parent), + dispatcher(dp), + server(srv), + commands(cmds), + nodes(), + ignoredNodes() +{ + connect(server, SIGNAL(newConnection(const W::Socket&)), SLOT(onNewConnection(const W::Socket&))); + connect(server, SIGNAL(closedConnection(const W::Socket&)), SLOT(onClosedConnection(const W::Socket&))); + + W::String cn = W::String(u"connect"); + W::Handler* ch = W::Handler::create(commands->getAddress() + W::Address({cn}), this, &U::Connector::_h_connect); + W::Vocabulary vc; + vc.insert(u"address", W::Uint64(W::Object::string)); + vc.insert(u"port", W::Uint64(W::Object::uint64)); + commands->addCommand(cn, ch, vc); + commands->enableCommand(cn, true); +} + +U::Connector::~Connector() +{ + commands->removeCommand(W::String(u"connect")); + Map::const_iterator itr = nodes.begin(); + Map::const_iterator end = nodes.begin(); + + W::String dc = W::String(u"disconnect"); + for (; itr != end; ++itr) { + commands->removeCommand(dc + itr->first); + } +} + +void U::Connector::addIgnoredNode(const W::String& name) +{ + ignoredNodes.insert(name); +} + +void U::Connector::sendTo(const W::String& name, const W::Event& event) +{ + Map::const_iterator itr = nodes.find(name); + if (itr != nodes.end()) { + throw new NodeAccessError(name); + } else { + server->getConnection(itr->second).send(event); + } +} + +void U::Connector::onNewConnection(const W::Socket& socket) +{ + W::String name = socket.getRemoteName(); + std::set::const_iterator ign = ignoredNodes.find(name); + if (ign == ignoredNodes.end()) { + Map::const_iterator itr = nodes.find(name); + if (itr == nodes.end()) { + if (server->getName() == name) { + emit serviceMessage("An attempt to connect node to itself, closing connection"); + server->closeConnection(socket.getId()); + } else { + W::String dc = W::String(u"disconnect"); + W::String dn = dc + name; + W::Handler* dh = W::Handler::create(commands->getAddress() + W::Address({dc, name}), this, &U::Connector::_h_disconnect); + commands->addCommand(dn, dh, W::Vocabulary()); + commands->enableCommand(dn, true); + + nodes.insert(std::make_pair(name, socket.getId())); + + emit serviceMessage(QString("New connection, id: ") + socket.getId().toString().c_str()); + connect(&socket, SIGNAL(message(const W::Event&)), dispatcher, SLOT(pass(const W::Event&))); + + emit nodeConnected(name); + } + } else { + emit serviceMessage(QString("Node ") + QString(name.toString().c_str()) + " tried to connect, but connection with that node is already open, closing new connection"); + server->closeConnection(socket.getId()); + } + } else { + emit serviceMessage(QString("New connection, id: ") + socket.getId().toString().c_str()); + connect(&socket, SIGNAL(message(const W::Event&)), dispatcher, SLOT(pass(const W::Event&))); + } +} + +void U::Connector::onClosedConnection(const W::Socket& socket) +{ + emit serviceMessage(QString("Connection closed, id: ") + socket.getId().toString().c_str()); + + W::String name = socket.getRemoteName(); + std::set::const_iterator ign = ignoredNodes.find(name); + if (ign == ignoredNodes.end()) { + Map::const_iterator itr = nodes.find(name); + if (itr != nodes.end()) { + emit nodeDisconnected(name); + commands->removeCommand(W::String(u"disconnect") + name); + nodes.erase(itr); + } + } +} + +void U::Connector::h_connect(const W::Event& ev) +{ + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::String& addr = static_cast(vc.at(u"address")); + const W::Uint64& port = static_cast(vc.at(u"port")); + server->openConnection(addr, port); +} + +void U::Connector::h_disconnect(const W::Event& ev) +{ + const W::Address& addr = static_cast(ev.getDestination()); + const W::String& name = addr.back(); + + Map::const_iterator itr = nodes.find(name); + server->closeConnection(itr->second); +} + +const W::Socket& U::Connector::getNodeSocket(const W::String& name) +{ + Map::const_iterator itr = nodes.find(name); + if (itr == nodes.end()) { + throw new NodeAccessError(name); + } + return server->getConnection(itr->second); +} diff --git a/lib/wServerUtils/connector.h b/lib/wServerUtils/connector.h new file mode 100644 index 0000000..4ba0a48 --- /dev/null +++ b/lib/wServerUtils/connector.h @@ -0,0 +1,68 @@ +#ifndef CONNECTOR_H +#define CONNECTOR_H + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include + +#include "commands.h" + +namespace U { + class Connector : public QObject + { + Q_OBJECT + typedef std::map Map; + public: + Connector(W::Dispatcher* dp, W::Server* srv, Commands* cmds, QObject* parent = 0); + ~Connector(); + + void addIgnoredNode(const W::String& name); + void sendTo(const W::String& name, const W::Event& event); + const W::Socket& getNodeSocket(const W::String& name); + + signals: + void serviceMessage(const QString& msg); + void nodeConnected(const W::String& name); + void nodeDisconnected(const W::String& name); + + private: + W::Dispatcher* dispatcher; + W::Server* server; + U::Commands* commands; + Map nodes; + std::set ignoredNodes; + + protected: + handler(connect); + handler(disconnect); + + private slots: + void onNewConnection(const W::Socket& socket); + void onClosedConnection(const W::Socket& socket); + + public: + class NodeAccessError: + public Utils::Exception + { + W::String name; + public: + NodeAccessError(const W::String& p_name):Exception(), name(p_name){} + + std::string getMessage() const{return std::string("An attempt to access non existing node ") + name.toString();} + }; + }; +} + + +#endif // CONNECTOR_H diff --git a/lib/wSocket/CMakeLists.txt b/lib/wSocket/CMakeLists.txt new file mode 100644 index 0000000..d04d5d7 --- /dev/null +++ b/lib/wSocket/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.12) +project(wSocket) + +find_package(Qt5Core REQUIRED) +find_package(Qt5Network REQUIRED) +find_package(Qt5WebSockets REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + socket.h + server.h +) + +set(SOURCES + socket.cpp + server.cpp +) + +add_library(wSocket ${HEADERS} ${SOURCES}) + +target_link_libraries(wSocket Qt5::Core) +target_link_libraries(wSocket Qt5::Network) +target_link_libraries(wSocket Qt5::WebSockets) + +target_link_libraries(wSocket wType) \ No newline at end of file diff --git a/lib/wSocket/server.cpp b/lib/wSocket/server.cpp new file mode 100644 index 0000000..ec44bc3 --- /dev/null +++ b/lib/wSocket/server.cpp @@ -0,0 +1,158 @@ +#include "server.h" + +#include + +using std::cout; +using std::endl; + +W::Server::Server(const W::String& name, QObject* parent): + QObject(parent), + lastId(0), + pool(), + connections(), + server(0), + name(name) +{ + server = new QWebSocketServer(name.toString().c_str(), QWebSocketServer::NonSecureMode, this); + connect(server, SIGNAL(newConnection()), SLOT(onNewConnection())); + connect(server, SIGNAL(serverError(QWebSocketProtocol::CloseCode)), SLOT(onServerError(QWebSocketProtocol::CloseCode))); +} + +W::Server::~Server() +{ + +} + +void W::Server::listen(uint16_t port) +{ + if (server->listen(QHostAddress::Any, port)){ + + } + +} + +void W::Server::stop() +{ + server->close(); + lastId = 0; + pool.clear(); + std::map::const_iterator it; + std::map::const_iterator end = connections.end(); + + for (it = connections.begin(); it != end; ++it) { + it->second->close(); + } +} + +const W::Socket& W::Server::getConnection(uint64_t p_id) const +{ + std::map::const_iterator itr = connections.find(p_id); + if (itr == connections.end()) { + throw new SocketAccessError(); + } + + return *(itr->second); +} + +uint64_t W::Server::getConnectionsCount() const +{ + return connections.size(); +} + +void W::Server::onNewConnection() +{ + QWebSocket *webSocket = server->nextPendingConnection(); + Socket* wSocket = createSocket(webSocket); + wSocket->setRemoteId(); +} + +void W::Server::onSocketConnected() { + Socket* socket = static_cast(sender()); + emit newConnection(*socket); + emit connectionCountChange(getConnectionsCount()); +} + +void W::Server::onSocketDisconnected() { + Socket* socket = static_cast(sender()); + uint64_t socketId = socket->getId(); + std::map::const_iterator it = connections.find(socketId); + connections.erase(it); + pool.insert(socketId); + emit closedConnection(*socket); + emit connectionCountChange(getConnectionsCount()); + socket->deleteLater(); +} + +void W::Server::onServerError(QWebSocketProtocol::CloseCode code) +{ + cout << "Server error: " << code << endl; +} + +void W::Server::closeConnection(uint64_t p_id) +{ + std::map::const_iterator itr = connections.find(p_id); + if (itr == connections.end()) { + throw new SocketAccessError(); + } + + itr->second->close(); +} + +W::Socket * W::Server::createSocket(QWebSocket* socket) +{ + uint64_t connectionId; + if (pool.empty()) { + connectionId = ++lastId; + } else { + std::set::const_iterator itr = pool.begin(); + connectionId = *itr; + pool.erase(itr); + } + Socket *wSocket = new Socket(name, socket, connectionId, this); + + connections[connectionId] = wSocket; + connect(wSocket, SIGNAL(connected()), SLOT(onSocketConnected())); + connect(wSocket, SIGNAL(disconnected()), SLOT(onSocketDisconnected())); + connect(wSocket, SIGNAL(negotiationId(uint64_t)), SLOT(onSocketNegotiationId(uint64_t))); + + return wSocket; +} + + +void W::Server::openConnection(const W::String& addr, const W::Uint64& port) +{ + QWebSocket *webSocket = new QWebSocket(); + Socket* wSocket = createSocket(webSocket); + wSocket->open(addr, port); + +} + +void W::Server::onSocketNegotiationId(uint64_t p_id) +{ + Socket* socket = static_cast(sender()); + + if (p_id == socket->id) { + socket->setRemoteName(); + } else { + std::set::const_iterator pItr = pool.lower_bound(p_id); + uint64_t newId; + if (pItr == pool.end()) { + newId = ++lastId; + } else { + newId = *pItr; + pool.erase(pItr); + } + std::map::const_iterator itr = connections.find(socket->id); + connections.erase(itr); + pool.insert(socket->id); + socket->id = Uint64(newId); + connections[newId] = socket; + socket->setRemoteId(); + } +} + +W::String W::Server::getName() const +{ + return name; +} + diff --git a/lib/wSocket/server.h b/lib/wSocket/server.h new file mode 100644 index 0000000..7981a71 --- /dev/null +++ b/lib/wSocket/server.h @@ -0,0 +1,79 @@ +#ifndef SERVER_H +#define SERVER_H +#include +#include +#include + +#include + +#include +#include +#include + +#include "socket.h" + +#include + +namespace W +{ + class Server: + public QObject + { + Q_OBJECT + public: + explicit Server(const String& name, QObject *parent = 0); + ~Server(); + + void listen(uint16_t port); + void stop(); + + const Socket& getConnection(uint64_t p_id) const; + uint64_t getConnectionsCount() const; + void closeConnection(uint64_t p_id); + void openConnection(const String& addr, const Uint64& port); + String getName() const; + + private: + uint64_t lastId; + std::set pool; + std::map connections; + QWebSocketServer* server; + String name; + + Socket* createSocket(QWebSocket* socket); + + signals: + void newConnection(const W::Socket&); + void closedConnection(const W::Socket&); + void connectionCountChange(uint64_t count); + + private slots: + void onNewConnection(); + void onServerError(QWebSocketProtocol::CloseCode code); + + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketNegotiationId(uint64_t p_id); + + private: + class HandshakeNameError: + public Utils::Exception + { + public: + HandshakeNameError():Exception(){} + + std::string getMessage() const{return "Name of connected socket haven't been found, but registering returned an error";} + }; + + class SocketAccessError: + public Utils::Exception + { + public: + SocketAccessError():Exception(){} + + std::string getMessage() const{return "An attempt to access non existing socket";} + }; + }; +} + +#endif // SERVER_H diff --git a/lib/wSocket/socket.cpp b/lib/wSocket/socket.cpp new file mode 100644 index 0000000..fa26a92 --- /dev/null +++ b/lib/wSocket/socket.cpp @@ -0,0 +1,256 @@ +#include "socket.h" + +#include + +using std::cout; +using std::endl; + +W::Socket::Socket(const W::String& p_name, QObject* parent): + QObject(parent), + serverCreated(false), + state(disconnected_s), + dState(dSize), + socket(new QWebSocket()), + id(0), + name(p_name), + remoteName(), + helperBuffer(new W::ByteArray(4)) +{ + socket->setParent(this); + setHandlers(); +} + +W::Socket::Socket(const W::String& p_name, QWebSocket* p_socket, uint64_t p_id, QObject* parent): + QObject(parent), + serverCreated(true), + state(disconnected_s), + dState(dSize), + socket(p_socket), + id(p_id), + name(p_name), + remoteName(), + helperBuffer(new W::ByteArray(4)) +{ + socket->setParent(this); + setHandlers(); +} + +W::Socket::~Socket() +{ + close(); + + delete helperBuffer; +} + +void W::Socket::open(const W::String& addr, const W::Uint64& port) +{ + if (state == disconnected_s) { + String::StdStr url_str("ws://" + addr.toString() + ":" + port.toString()); + QUrl url(url_str.c_str()); + remoteName = String(); + state = connecting_s; + socket->open(url); + } +} + +void W::Socket::close() +{ + if (state != disconnected_s && state != disconnecting_s) { + state = disconnecting_s; + socket->close(); + } +} + +void W::Socket::send(const W::Event& ev) const +{ + //std::cout << "Sending event: " << ev.toString() << std::endl; + W::Object::size_type size = ev.size(); + ByteArray *wba = new ByteArray(size + 5); + wba->push32(size); + wba->push8(ev.getType()); + ev.serialize(*wba); + + QByteArray ba = QByteArray::fromRawData(wba->getData(), wba->size()); + socket->sendBinaryMessage(ba); + delete wba; +} + +W::Uint64 W::Socket::getId() const +{ + return id; +} + +W::String W::Socket::getRemoteName() const +{ + return remoteName; //TODO may be throw the exception, when socket is not connected? +} + +W::String W::Socket::getName() const +{ + return name; +} + +void W::Socket::setHandlers() { + connect(socket, SIGNAL(connected()), SLOT(onSocketConnected())); + connect(socket, SIGNAL(disconnected()), SLOT(onSocketDisconnected())); + connect(socket, SIGNAL(binaryMessageReceived(const QByteArray&)), SLOT(onBinaryMessageReceived(const QByteArray&))); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(onSocketError(QAbstractSocket::SocketError))); +} + +void W::Socket::onSocketConnected() +{ + dState = dSize; + delete helperBuffer; + helperBuffer = new W::ByteArray(4); +} + +void W::Socket::onSocketDisconnected() +{ + state = disconnected_s; + emit disconnected(); +} + +void W::Socket::onBinaryMessageReceived(const QByteArray& ba) +{ + int i = 0; + while (i < ba.size()) { + switch (dState) { + case dSize: + i = helperBuffer->fill(ba.data(), ba.size(), i); + + if (helperBuffer->filled()) { + int size = helperBuffer->pop32(); + delete helperBuffer; + helperBuffer = new W::ByteArray(size + 1); + dState = dBody; + } + break; + case dBody: + i = helperBuffer->fill(ba.data(), ba.size(), i); + + if (helperBuffer->filled()) { + Event* ev = static_cast(W::Object::fromByteArray(*helperBuffer)); + onEvent(ev); + + delete ev; + + delete helperBuffer; + helperBuffer = new W::ByteArray(4); + dState = dSize; + } + break; + } + } +} + +void W::Socket::onEvent(W::Event* ev) +{ + if (ev->isSystem()) { + const Vocabulary& vc = static_cast(ev->getData()); + const String& command = static_cast(vc.at(u"command")); + + if (command == u"setId") { + if (serverCreated) { + if (state == connecting_s) { + emit negotiationId(static_cast(vc.at(u"id"))); + } else { + throw ErrorIdSetting(); + } + } else { + setId(static_cast(vc.at(u"id"))); + setRemoteName(); + } + } else if (command == u"setName") { + setName(static_cast(vc.at(u"name"))); + if (static_cast(vc.at(u"yourName")) != name) { + setRemoteName(); + } + emit connected(); + } + } else { + emit message(*ev); + } +} + + +void W::Socket::setId(const W::Uint64& p_id) +{ + if (state == connecting_s) + { + id = p_id; + } + else + { + throw ErrorIdSetting(); + } +} + +void W::Socket::setRemoteId() +{ + if (state == disconnected_s) { + state = connecting_s; + } + String command(u"setId"); + Vocabulary *vc = new Vocabulary(); + + vc->insert(u"command", command); + vc->insert(u"id", id); + + Address addr; + Event ev(addr, vc, true); + ev.setSenderId(id); + send(ev); +} + +void W::Socket::setRemoteName() +{ + String command(u"setName"); + Vocabulary *vc = new Vocabulary(); + + vc->insert(u"command", command); + vc->insert(u"name", name); + vc->insert(u"yourName", remoteName); + + Address addr; + Event ev(addr, vc, true); + ev.setSenderId(id); + send(ev); + +} + +void W::Socket::setName(const W::String& p_name) +{ + if (state == connecting_s) + { + remoteName = p_name; + state = connected_s; + } + else + { + throw ErrorNameSetting(); + } +} + +void W::Socket::cantDeliver(const W::Event& event) const +{ + String command(u"cantDeliver"); + Vocabulary *vc = new Vocabulary(); + + vc->insert(u"command", command); + vc->insert(u"event", event); + + Address addr; + Event ev(addr, vc, true); + ev.setSenderId(id); + send(ev); +} + +void W::Socket::onSocketError(QAbstractSocket::SocketError err) +{ + if (state == connecting_s) { + state = disconnected_s; + } + //socket->close(); + emit error(err, socket->errorString()); +} + diff --git a/lib/wSocket/socket.h b/lib/wSocket/socket.h new file mode 100644 index 0000000..31a9bd0 --- /dev/null +++ b/lib/wSocket/socket.h @@ -0,0 +1,108 @@ +#ifndef SOCKET_H +#define SOCKET_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace W +{ + class Socket: + public QObject + { + Q_OBJECT + friend class Server; + + enum State + { + disconnected_s, + disconnecting_s, + connecting_s, + connected_s + }; + + enum DeserializationState { + dSize, + dBody + }; + + public: + explicit Socket(const String& p_name, QObject* parent = 0); + ~Socket(); + + void send(const Event& ev) const; + void open(const String& addr, const Uint64& port); + void close(); + + Uint64 getId() const; + String getRemoteName() const; + String getName() const; + typedef QAbstractSocket::SocketError SocketError; + + private: + explicit Socket(const String& p_name, QWebSocket *p_socket, uint64_t p_id, QObject *parent = 0); + + void setHandlers(); + void setId(const Uint64& p_id); + void setRemoteId(); + void setRemoteName(); + void setName(const String& p_name); + + bool serverCreated; + State state; + DeserializationState dState; + QWebSocket *socket; + Uint64 id; + String name; + String remoteName; + ByteArray* helperBuffer; + + signals: + void connected(); + void disconnected(); + void negotiationId(uint64_t p_id); + void error(W::Socket::SocketError err, const QString& msg); + void message(const W::Event&); + void proxy(const W::Event&); + + public slots: + void cantDeliver(const Event& event) const; + + private slots: + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketError(QAbstractSocket::SocketError err); + void onBinaryMessageReceived(const QByteArray& ba); + void onEvent(W::Event* ev); + + private: + class ErrorIdSetting: + public Utils::Exception + { + public: + ErrorIdSetting():Exception(){} + + std::string getMessage() const{return "An attempt to set id to the socket not in connecting state";} + }; + + class ErrorNameSetting: + public Utils::Exception + { + public: + ErrorNameSetting():Exception(){} + + std::string getMessage() const{return "An attempt to set name to the socket not in connecting state";} + }; + + }; +} + +#endif // SOCKET_H diff --git a/lib/wSsh/CMakeLists.txt b/lib/wSsh/CMakeLists.txt new file mode 100644 index 0000000..167eb7d --- /dev/null +++ b/lib/wSsh/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 2.8.12) +project(wSsh) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + sshsocket.h + qsshsocket.h +) + +set(SOURCES + sshsocket.cpp + qsshsocket.cpp +) + +add_library(wSsh ${HEADERS} ${SOURCES}) + +target_link_libraries(wSsh Qt5::Core) +target_link_libraries(wSsh ssh) +target_link_libraries(wSsh ssh_threads) diff --git a/lib/wSsh/qsshsocket.cpp b/lib/wSsh/qsshsocket.cpp new file mode 100644 index 0000000..db9be2c --- /dev/null +++ b/lib/wSsh/qsshsocket.cpp @@ -0,0 +1,186 @@ +#include "qsshsocket.h" +#include + +bool QSshSocket::lib_ssh_inited = false; + +QSshSocket::QSshSocket(QObject * parent) + :QObject(parent), + loggedIn(false), + session(0), + m_connected(false), + executing(false), + command(0) +{ + if (!lib_ssh_inited) { + lib_ssh_init(); + lib_ssh_inited = true; + } + qRegisterMetaType(); //not sure if it supposed to be here +} + +QSshSocket::~QSshSocket() +{ +} + +void QSshSocket::disconnect() +{ + if (m_connected) { + loggedIn = false; + m_connected = false; + + if (executing) { + destroyCommand(); + } + ssh_disconnect(session); + ssh_free(session); + session = 0; + emit disconnected(); + } +} + +void QSshSocket::connect(QString host, int port) +{ + if (!m_connected) { + session = ssh_new(); + int verbosity = SSH_LOG_PROTOCOL; + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(session, SSH_OPTIONS_HOST, host.toUtf8().data()); + ssh_options_set(session, SSH_OPTIONS_PORT, &port); + + int connectionResponse = ssh_connect(session); + + if (connectionResponse == SSH_OK) { + m_connected = true; + emit connected(); + } else { + ssh_disconnect(session); + ssh_free(session); + session = 0; + emit error(SessionCreationError); + } + } else { + throw 1; //TODO + } +} +void QSshSocket::login(QString user, QString password) +{ + if (m_connected && !loggedIn) { + int worked = ssh_userauth_password(session, user.toUtf8().data(), password.toUtf8().data()); + + if (worked == SSH_OK) { + loggedIn = true; + emit loginSuccessful(); + } else { + emit error(PasswordAuthenticationFailedError); + disconnect(); + } + } else { + throw 2; //TODO + } + +} + +void QSshSocket::executeCommand(QString p_command) +{ + if (executing) { + //todo + return; + } + ssh_channel channel = ssh_channel_new(session); + if (ssh_channel_open_session(channel) != SSH_OK) { + emit error(ChannelCreationError); + } + int success; + do { + success = ssh_channel_request_exec(channel, p_command.toUtf8().data()); + } while (success == SSH_AGAIN); + + if (success != SSH_OK) { + ssh_channel_close(channel); + ssh_channel_free(channel); + emit error(WriteError); + } else { + qintptr fd = ssh_get_fd(session); + QSocketNotifier* readNotifier = new QSocketNotifier(fd, QSocketNotifier::Read); + QObject::connect(readNotifier, SIGNAL(activated(int)), this, SLOT(socketRead(int))); + + command = new Command{fd, p_command, channel, readNotifier}; + executing = true; + } +} + + +bool QSshSocket::isConnected() +{ + return m_connected; +} + +bool QSshSocket::isLoggedIn() +{ + return loggedIn; +} + +bool QSshSocket::isExecuting() +{ + return executing; +} + + +void QSshSocket::socketRead(int ptr) +{ + command->notifier->setEnabled(false); + + char* buffer = new char[1048576]; + + int totalBytes = 0; + int newBytes = 0; + do { + newBytes = ssh_channel_read_nonblocking(command->channel, &buffer[totalBytes], 1048576 - totalBytes, 0); + + if (newBytes > 0) { + totalBytes += newBytes; + } + + } while (newBytes > 0); + + if (newBytes == SSH_ERROR) { + emit error(ReadError); + destroyCommand(); + } else if (ssh_channel_is_eof(command->channel) != 0) { + command->notifier->setEnabled(true); + QString response = QString::fromUtf8(buffer, totalBytes); + emit commandData(response); + emit endOfFile(); + destroyCommand(); + } else { + command->notifier->setEnabled(true); + QString response = QString::fromUtf8(buffer, totalBytes); + emit commandData(response); + } + + delete[] buffer; +} + +void QSshSocket::destroyCommand() +{ + delete command->notifier; + ssh_channel_send_eof(command->channel); + ssh_channel_close(command->channel); + ssh_channel_free(command->channel); + delete command; + executing = false; +} + +void QSshSocket::lib_ssh_init() +{ + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + ssh_init(); +} + +void QSshSocket::interrupt() +{ + if (executing) { + ssh_channel_request_send_signal(command->channel, "INT"); + } +} + diff --git a/lib/wSsh/qsshsocket.h b/lib/wSsh/qsshsocket.h new file mode 100644 index 0000000..d34da35 --- /dev/null +++ b/lib/wSsh/qsshsocket.h @@ -0,0 +1,81 @@ +#ifndef QSSHSOCKET_H +#define QSSHSOCKET_H + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +class QSshSocket: public QObject +{ + Q_OBJECT +public: + + enum SshError + { + SocketError, + SessionCreationError, + ChannelCreationError, + ReadError, + WriteError, + PasswordAuthenticationFailedError + }; + + explicit QSshSocket(QObject* parent = 0); + ~QSshSocket(); + + bool isLoggedIn(); + bool isConnected(); + bool isExecuting(); + +signals: + void connected(); + void disconnected(); + void error(QSshSocket::SshError error); + void loginSuccessful(); + void commandData(QString data); + void endOfFile(); + +public slots: + void connect(QString host, int port = 22); + void disconnect(); + void executeCommand(QString command); + void login(QString user, QString password); + void interrupt(); + +private: + struct Command + { + qintptr id; + QString command; + ssh_channel channel; + QSocketNotifier* notifier; + }; + + bool loggedIn; + ssh_session session; + bool m_connected; + bool executing; + Command* command; + static bool lib_ssh_inited; + static void lib_ssh_init(); + +private: + void destroyCommand(); + +private slots: + void socketRead(int ptr); +}; + + +Q_DECLARE_METATYPE(QSshSocket::SshError) + + +#endif // QSSHSOCKET_H diff --git a/lib/wSsh/sshsocket.cpp b/lib/wSsh/sshsocket.cpp new file mode 100644 index 0000000..1e704aa --- /dev/null +++ b/lib/wSsh/sshsocket.cpp @@ -0,0 +1,195 @@ +#include "sshsocket.h" +#include + +W::SshSocket::SshSocket(const QString& p_login, const QString& p_password, QObject* parent): + QObject(parent), + socket(new QSshSocket()), + thread(new QThread()), + login(p_login), + password(p_password), + state(Disconnected) +{ + connect(socket, SIGNAL(connected()), this, SLOT(onSocketConnected())); + connect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + connect(socket, SIGNAL(loginSuccessful()), this, SLOT(onSocketLoggedIn())); + connect(socket, SIGNAL(error(QSshSocket::SshError)), this, SLOT(onSocketError(QSshSocket::SshError))); + connect(socket, SIGNAL(commandData(QString)), this, SLOT(onSocketCommandData(QString))); + connect(socket, SIGNAL(endOfFile()), this, SLOT(onSocketEOF())); + + socket->moveToThread(thread); +} + +W::SshSocket::~SshSocket() +{ + if (state != Disconnected) { + if (state == Disconnecting) { + onSocketDisconnected(); + } else { + qDebug("Socket wasn't closed, terminating the inner thread"); + thread->terminate(); + } + } + socket->deleteLater(); + thread->deleteLater(); + //TODO; +} + +void W::SshSocket::open(const QString& address, uint16_t port) +{ + if (state == Disconnected) { + state = Connecting; + thread->start(); + QMetaObject::invokeMethod(socket, "connect", Qt::QueuedConnection, Q_ARG(QString, address), Q_ARG(int, port)); + } else { + //TODO; + } +} + +void W::SshSocket::onSocketConnected() +{ + if (state == Connecting) { + state = Connected; + authorize(); + } else { + //TODO; + } +} + +void W::SshSocket::authorize() +{ + if (state == Connected) { + state = Authorizing; + QMetaObject::invokeMethod(socket, "login", Qt::QueuedConnection, Q_ARG(QString, login), Q_ARG(QString, password)); + } else { + //TODO; + } +} + +void W::SshSocket::onSocketLoggedIn() +{ + if (state == Authorizing) { + state = Authorized; + emit opened(); + } +} + +void W::SshSocket::close() +{ + switch (state) { + case Disconnected: + //TODO; + break; + case Connecting: + case Connected: + case Authorizing: + case Authorized: + QMetaObject::invokeMethod(socket, "disconnect", Qt::QueuedConnection); + state = Disconnecting; + break; + case Disconnecting: + //TODO; + break; + } +} + +void W::SshSocket::onSocketDisconnected() +{ + if (state == Disconnecting) { + thread->quit(); + thread->wait(); + state = Disconnected; + emit closed(); + } else { + //TODO; + } +} + +void W::SshSocket::execute(const QString& command) +{ + if (state == Authorized) { + QMetaObject::invokeMethod(socket, "executeCommand", Qt::QueuedConnection, Q_ARG(QString, command)); + } else { + //TODO; + } +} + +void W::SshSocket::onSocketCommandData(QString p_data) +{ + if (state == Authorized) { + emit data(p_data); + } +} + +void W::SshSocket::onSocketError(QSshSocket::SshError p_error) +{ + QString msg; + Error errCode; + switch (p_error) { + case QSshSocket::SocketError: + msg = "There was a trouble creating a socket. Looks like you have problems with internet connectiion"; + errCode = SocketError; + break; + case QSshSocket::SessionCreationError: + msg = "No route to the remote host"; + errCode = SessionCreationError; + if (state == Connecting) { + state = Disconnected; + } + break; + case QSshSocket::ChannelCreationError: + msg = "An ssh channel could not be created"; + errCode = ChannelCreationError; + break; + case QSshSocket::ReadError: + msg = "There was an error reading the socket"; + errCode = ReadError; + break; + case QSshSocket::WriteError: + msg = "There was an error writing to the socket"; + errCode = WriteError; + break; + case QSshSocket::PasswordAuthenticationFailedError: + msg = "The credentials of a user on the remote host could not be authenticated"; + errCode = PasswordAuthenticationError; + if (state == Authorizing) { + state = Connected; + } + break; + } + + emit error(errCode, msg); +} + +void W::SshSocket::onSocketEOF() +{ + emit finished(); +} + +bool W::SshSocket::isReady() const +{ + return state == Authorized; +} + +void W::SshSocket::interrupt() +{ + if (state == Authorized) { + QMetaObject::invokeMethod(socket, "interrupt", Qt::QueuedConnection); + } else { + //TODO; + } +} + +void W::SshSocket::setLogin(const QString& lng) +{ + login = lng; +} + +void W::SshSocket::setPassword(const QString& pass) +{ + password = pass; +} + + + + + diff --git a/lib/wSsh/sshsocket.h b/lib/wSsh/sshsocket.h new file mode 100644 index 0000000..9600e66 --- /dev/null +++ b/lib/wSsh/sshsocket.h @@ -0,0 +1,69 @@ +#ifndef SSHSOCKET_H +#define SSHSOCKET_H + +#include "qsshsocket.h" + +#include +#include + +namespace W { + class SshSocket : public QObject { + Q_OBJECT + + public: + SshSocket(const QString& p_login, const QString& p_password, QObject* parent = 0); + ~SshSocket(); + + enum Error { + SocketError, + SessionCreationError, + ChannelCreationError, + ReadError, + WriteError, + PasswordAuthenticationError + }; + + void open(const QString& address, uint16_t port = 22); + void close(); + void execute(const QString& command); + void interrupt(); + bool isReady() const; + void setLogin(const QString& lng); + void setPassword(const QString& pass); + + signals: + void opened(); + void closed(); + void error(W::SshSocket::Error code, const QString& message); + void data(const QString& data); + void finished(); + + private: + void authorize(); + enum State { + Disconnected, + Connecting, + Connected, + Authorizing, + Authorized, + Disconnecting + }; + + QSshSocket* socket; + QThread* thread; + QString login; + QString password; + State state; + + private slots: + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketLoggedIn(); + void onSocketError(QSshSocket::SshError p_error); + void onSocketCommandData(QString p_data); + void onSocketEOF(); + + }; +} + +#endif // SSHSOCKET_H diff --git a/lib/wType/CMakeLists.txt b/lib/wType/CMakeLists.txt new file mode 100644 index 0000000..a917cf0 --- /dev/null +++ b/lib/wType/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 2.8.12) +project(type) + +set(HEADERS + bytearray.h + object.h + string.h + vocabulary.h + uint64.h + address.h + boolean.h + event.h + vector.h + blob.h +) + +set(SOURCES + bytearray.cpp + object.cpp + string.cpp + vocabulary.cpp + uint64.cpp + address.cpp + boolean.cpp + event.cpp + vector.cpp + blob.cpp +) + +add_library(wType ${HEADERS} ${SOURCES}) +target_link_libraries(wType utils) diff --git a/lib/wType/address.cpp b/lib/wType/address.cpp new file mode 100644 index 0000000..cd28e58 --- /dev/null +++ b/lib/wType/address.cpp @@ -0,0 +1,326 @@ +#include "address.h" + +W::Address::Address(): + Object(), + data(new List()) +{ + +} + +W::Address::Address(const W::Address::InitList& list): + Object(), + data(new List()) +{ + InitList::const_iterator itr = list.begin(); + InitList::const_iterator end = list.end(); + + for (; itr != end; ++itr) + { + data->emplace_back(String(*itr)); + } +} + +W::Address::Address(const W::Address& original): + Object(), + data(new List(*original.data)) +{ + +} + +W::Address::~Address() +{ + delete data; +} + +W::Address& W::Address::operator=(const W::Address& original) +{ + if (&original != this) + { + data->clear(); + data->insert(data->end(), original.data->begin(), original.data->end()); + } + return *this; +} + +W::Object::size_type W::Address::length() const +{ + return data->size(); +} + +W::Object::objectType W::Address::getType() const +{ + return type; +} + +W::Object::StdStr W::Address::toString() const +{ + StdStr str; + + List::const_iterator itr; + List::const_iterator beg = data->begin(); + List::const_iterator end = data->end(); + + str += '['; + for (itr = beg; itr != end; ++itr) + { + if (itr != beg) + { + str += ", "; + } + str += itr->toString(); + } + str += ']'; + + return str; +} + +W::Object* W::Address::copy() const +{ + return new Address(*this); +} + +void W::Address::serialize(W::ByteArray& out) const +{ + out.push32(length()); + + List::const_iterator itr; + List::const_iterator beg = data->begin(); + List::const_iterator end = data->end(); + + for (itr = beg; itr != end; ++itr) + { + itr->serialize(out); + } +} + +void W::Address::deserialize(W::ByteArray& in) +{ + data->clear(); + + size_type length = in.pop32(); + + for (size_type i = 0; i != length; ++i) + { + data->emplace_back(String()); + data->back().deserialize(in); + } +} + +bool W::Address::operator<(const W::Address& other) const +{ + return *data < *other.data; +} + +bool W::Address::operator>(const W::Address& other) const +{ + return *data > *other.data; +} + +bool W::Address::operator==(const W::Address& other) const +{ + return *data == *other.data; +} + +bool W::Address::operator!=(const W::Address& other) const +{ + return *data != *other.data; +} + +bool W::Address::operator<=(const W::Address& other) const +{ + return *data <= *other.data; +} + +bool W::Address::operator>=(const W::Address& other) const +{ + return *data >= *other.data; +} + +bool W::Address::begins(const W::Address& other) const +{ + if (other.length() > length()) + { + return false; + } + + List::const_iterator itr_o = other.data->begin(); + List::const_iterator end_o = other.data->end(); + + List::const_iterator itr_i = data->begin(); + + while (itr_o != end_o) + { + if (*itr_o != *itr_i) { + return false; + } + + ++itr_o; + ++itr_i; + } + + return true; +} + +bool W::Address::ends(const W::Address& other) const +{ + if (other.length() > length()) + { + return false; + } + + List::const_reverse_iterator itr_o = other.data->rbegin(); + List::const_reverse_iterator end_o = other.data->rend(); + + List::const_reverse_iterator itr_i = data->rbegin(); + + while (itr_o != end_o) + { + if (*itr_o != *itr_i) { + return false; + } + + ++itr_o; + ++itr_i; + } + + return true; +} + +bool W::Address::contains(const W::Address& other, int position) const +{ + if (other.length() > length() - position) + { + return false; + } + + bool res = true; + + List::const_iterator itr_o = other.data->begin(); + List::const_iterator end_o = other.data->end(); + + List::const_iterator itr_i = data->begin(); + + for (int i = 0; i < position; ++i) { + ++itr_i; + } + + while (res == true && itr_o != end_o) + { + res = *itr_o == *itr_i; + + ++itr_o; + ++itr_i; + } + + return res; +} + +W::Address& W::Address::operator+=(const W::Address& other) +{ + data->insert(data->end(), other.data->begin(), other.data->end()); + return *this; +} + +W::Address& W::Address::operator+=(const W::String& other) +{ + data->push_back(other); + return *this; +} + +W::Address& W::Address::operator+=(const W::String::u16string& other) +{ + String hop(other); + return operator+=(hop); +} + +W::Address W::Address::operator>>(W::Object::size_type count) const +{ + W::Address res; + if (count < length()) + { + List::const_iterator itr = data->end(); + for (size_type i = 0; i != count; ++i) + { + itr--; + } + List::const_iterator beg = data->begin(); + res.data->insert(res.data->end(), beg, itr); + } + return res; +} + +W::Address W::Address::operator<<(W::Object::size_type count) const +{ + W::Address res; + if (count < length()) + { + List::const_iterator itr = data->begin(); + for (size_type i = 0; i != count; ++i) + { + itr++; + } + List::const_iterator end = data->end(); + res.data->insert(res.data->end(), itr, end); + } + return res; +} + +W::Address W::Address::operator+(const W::Address& other) const +{ + W::Address res; + res += *this; + res += other; + + return res; +} + +W::Address & W::Address::operator+=(const Uint64& other) +{ + data->push_back(String(other.toString())); + + return *this; +} + + +W::Address W::Address::operator+(const Uint64& other) const +{ + W::Address res; + res += *this; + res += other; + + return res; +} + + +const W::String& W::Address::front() const +{ + return data->front(); +} + +const W::String& W::Address::back() const +{ + return data->back(); +} + +bool W::Address::operator==(const W::Object& other) const +{ + if (sameType(other)) { + return operator==(static_cast(other)); + } else { + return false; + } +} + +W::Object::size_type W::Address::size() const +{ + size_type size = 4; + List::const_iterator itr = data->begin(); + List::const_iterator end = data->end(); + + for (; itr != end; ++itr) + { + size += itr->size(); + } + + return size; +} diff --git a/lib/wType/address.h b/lib/wType/address.h new file mode 100644 index 0000000..8dab428 --- /dev/null +++ b/lib/wType/address.h @@ -0,0 +1,70 @@ +#ifndef ADDRESS_H +#define ADDRESS_H + +#include "object.h" +#include "string.h" +#include "uint64.h" + +#include +#include + +namespace W +{ + class Address: + public Object + { + typedef std::list List; + typedef std::initializer_list InitList; + + public: + Address(); + Address(const InitList& list); + Address(const Address& original); + ~Address(); + + Address& operator=(const Address& original); + + StdStr toString() const override; + Object* copy() const override; + size_type length() const override; + size_type size() const override; + + objectType getType() const override; + + void serialize(ByteArray& out) const override; + void deserialize(ByteArray& in) override; + + bool operator==(const Object & other) const override; + + bool operator<(const Address& other) const; + bool operator>(const Address& other) const; + bool operator<=(const Address& other) const; + bool operator>=(const Address& other) const; + bool operator==(const Address& other) const; + bool operator!=(const Address& other) const; + + bool begins(const Address& other) const; + bool ends(const Address& other) const; + bool contains(const Address& other, int position) const; + + Address& operator+=(const Address& other); + Address& operator+=(const String& other); + Address& operator+=(const Uint64& other); + Address& operator+=(const String::u16string& other); + + Address operator>>(size_type count) const; + Address operator<<(size_type count) const; + Address operator+(const Address& other) const; + Address operator+(const Uint64& other) const; + + static const objectType type = address; + + const String& front() const; + const String& back() const; + + private: + List *data; + }; +} + +#endif // ADDRESS_H diff --git a/lib/wType/blob.cpp b/lib/wType/blob.cpp new file mode 100644 index 0000000..02991c2 --- /dev/null +++ b/lib/wType/blob.cpp @@ -0,0 +1,146 @@ +#include "blob.h" +#include +#include + +W::Blob::Blob(): + W::Object(), + hasData(false), + dataSize(0), + data(0), + qDataView() +{ +} + +W::Blob::Blob(uint32_t size, char* p_data): + W::Object(), + hasData(true), + dataSize(size), + data(p_data), + qDataView(QByteArray::fromRawData(p_data, size)) +{ +} + +W::Blob::Blob(const W::Blob& original): + W::Object(), + hasData(original.data), + dataSize(original.dataSize), + data(0), + qDataView() +{ + if (hasData) { + data = new char[dataSize]; + std::copy(original.data, original.data + dataSize, data); + qDataView = QByteArray::fromRawData(data, dataSize); + } +} + +W::Blob::~Blob() +{ + if (hasData) { + delete [] data; + } +} + +W::Blob & W::Blob::operator=(const W::Blob& original) +{ + if (&original != this) + { + if (hasData) { + delete [] data; + qDataView = QByteArray(); + } + hasData = original.hasData; + dataSize = original.dataSize; + if (hasData) { + data = new char[dataSize]; + std::copy(original.data, original.data + dataSize, data); + qDataView = QByteArray::fromRawData(data, dataSize); + } + } + return *this; +} + +W::Object * W::Blob::copy() const +{ + return new W::Blob(*this); +} + +W::Object::objectType W::Blob::getType() const +{ + return type; +} + +W::Object::size_type W::Blob::length() const +{ + return dataSize; +} + +void W::Blob::serialize(W::ByteArray& out) const +{ + out.push32(length()); + for (uint32_t i = 0; i < dataSize; ++i) { + out.push8(data[i]); + } +} + +void W::Blob::deserialize(W::ByteArray& in) +{ + if (hasData) { + delete [] data; + qDataView = QByteArray(); + } + + dataSize = in.pop32(); + if (dataSize > 0) { + hasData = true; + data = new char[dataSize]; + for (uint32_t i = 0; i < dataSize; ++i) { + data[i] = in.pop8(); + } + qDataView = QByteArray::fromRawData(data, dataSize); + } else { + hasData = false; + } +} + +W::Object::StdStr W::Blob::toString() const +{ + return "Blob <" + std::to_string(dataSize) + ">"; +} + +bool W::Blob::operator==(const W::Blob& other) const +{ + if (dataSize != other.dataSize) { + return false; + } else { + bool equals = true; + uint64_t i = 0; + + while (equals && i < dataSize) { + equals = data[i] == other.data[i]; //TODO not sure about the c++ syntax if i'm comparing values or addresses this time + ++i; + } + + return equals; + } +} + +bool W::Blob::operator==(const W::Object& other) const +{ + if (sameType(other)) { + return operator==(static_cast(other)); + } else { + return false; + } +} + +W::Object::size_type W::Blob::size() const +{ + return dataSize + 4; +} + +const QByteArray & W::Blob::byteArray() const +{ + return qDataView; +} + diff --git a/lib/wType/blob.h b/lib/wType/blob.h new file mode 100644 index 0000000..e8fdb55 --- /dev/null +++ b/lib/wType/blob.h @@ -0,0 +1,46 @@ +#ifndef BLOB_H +#define BLOB_H + +#include "object.h" +#include + +namespace W +{ + class Blob: public Object + { + public: + Blob(); + Blob(uint32_t size, char* p_data); + Blob(const Blob& original); + ~Blob(); + + Blob& operator=(const Blob& original); + + StdStr toString() const override; + Object* copy() const override; + size_type length() const override; + size_type size() const override; + + objectType getType() const override; + + bool operator==(const W::Object & other) const override; + + void serialize(ByteArray& out) const override; + void deserialize(ByteArray& in) override; + + bool operator==(const Blob& other) const; + + static const objectType type = blob; + + const QByteArray& byteArray() const; + + protected: + bool hasData; + uint32_t dataSize; + char* data; + QByteArray qDataView; + + }; +} + +#endif // BLOB_H diff --git a/lib/wType/boolean.cpp b/lib/wType/boolean.cpp new file mode 100644 index 0000000..04b7377 --- /dev/null +++ b/lib/wType/boolean.cpp @@ -0,0 +1,146 @@ +#include "boolean.h" + +W::Boolean::Boolean(): + Object(), + data(false) +{ + +} + +W::Boolean::Boolean(bool value): + Object(), + data(value) +{ + +} + +W::Boolean::Boolean(const W::Boolean& original): + Object(), + data(original.data) +{ + +} + +W::Boolean::~Boolean() +{ + +} + +W::Boolean& W::Boolean::operator=(const W::Boolean& original) +{ + data = original.data; + return *this; +} + +W::Boolean& W::Boolean::operator=(bool original) +{ + data = original; + return *this; +} + +W::Object::StdStr W::Boolean::toString() const +{ + StdStr str; + if (data) + { + str = "true"; + } + else + { + str = "false"; + } + return str; +} + +W::Object::objectType W::Boolean::getType() const +{ + return Boolean::type; +} + +W::Object::size_type W::Boolean::length() const +{ + return 1; +} + +W::Object* W::Boolean::copy() const +{ + return new Boolean(*this); +} + +bool W::Boolean::operator>(const W::Boolean& other) const +{ + return data > other.data; +} + +bool W::Boolean::operator<(const W::Boolean& other) const +{ + return data < other.data; +} + +bool W::Boolean::operator==(const W::Boolean& other) const +{ + return data == other.data; +} + +bool W::Boolean::operator!=(const W::Boolean& other) const +{ + return data != other.data; +} + +bool W::Boolean::operator<=(const W::Boolean& other) const +{ + return data <= other.data; +} + +bool W::Boolean::operator>=(const W::Boolean& other) const +{ + return data >= other.data; +} + +void W::Boolean::serialize(W::ByteArray& out) const +{ + uint8_t val; + if (data) + { + val = 253; + } + else + { + val = 0; + } + + out.push8(val); +} + +void W::Boolean::deserialize(W::ByteArray& in) +{ + uint8_t val = in.pop8(); + + if (val == 253) + { + data = true; + } + else + { + data = false; + } +} + +W::Boolean::operator bool() const +{ + return data; +} + +bool W::Boolean::operator==(const W::Object& other) const +{ + if (sameType(other)) { + return operator==(static_cast(other)); + } else { + return false; + } +} + +W::Object::size_type W::Boolean::size() const +{ + return 1; +} diff --git a/lib/wType/boolean.h b/lib/wType/boolean.h new file mode 100644 index 0000000..902837a --- /dev/null +++ b/lib/wType/boolean.h @@ -0,0 +1,49 @@ +#ifndef BOOLEAN_H +#define BOOLEAN_H + +#include "object.h" + +namespace W +{ + class Boolean: + public Object + { + public: + Boolean(); + explicit Boolean(bool value); + Boolean(const Boolean& original); + + ~Boolean(); + + Boolean& operator=(const Boolean& original); + Boolean& operator=(bool original); + + StdStr toString() const override; + Object* copy() const override; + size_type length() const override; + size_type size() const override; + + objectType getType() const override; + bool operator==(const W::Object & other) const override; + + bool operator<(const Boolean& other) const; + bool operator>(const Boolean& other) const; + bool operator<=(const Boolean& other) const; + bool operator>=(const Boolean& other) const; + bool operator==(const Boolean& other) const; + bool operator!=(const Boolean& other) const; + + static const objectType type = boolean; + + void serialize(ByteArray& out) const override; + void deserialize(ByteArray& in) override; + + operator bool() const; + + private: + bool data; + + }; +} + +#endif // BOOLEAN_H diff --git a/lib/wType/bytearray.cpp b/lib/wType/bytearray.cpp new file mode 100644 index 0000000..d71b7ac --- /dev/null +++ b/lib/wType/bytearray.cpp @@ -0,0 +1,167 @@ +#include "bytearray.h" +#include + +W::ByteArray::ByteArray(size_type size): + data(new Container(size)), + shiftBegin(0), + shiftEnd(0), + referenceMode(false) +{ + +} + +W::ByteArray::ByteArray(const W::ByteArray& original): + data(new Container(*(original.data))), + shiftBegin(original.shiftBegin), + shiftEnd(original.shiftEnd), + referenceMode(original.referenceMode) +{ + +} + +W::ByteArray::~ByteArray() +{ + delete data; +} + +W::ByteArray& W::ByteArray::operator=(const W::ByteArray& original) +{ + if (&original != this) + { + delete data; + data = new Container(*(original.data)); + shiftBegin = original.shiftBegin; + shiftEnd = original.shiftEnd; + referenceMode = original.referenceMode; + } + return *this; +} + +W::ByteArray::size_type W::ByteArray::size() const +{ + return shiftEnd - shiftBegin; +} + +void W::ByteArray::push8(uint8_t integer) +{ + if (referenceMode) { + //TODO not sure + } + data->at(shiftEnd) = integer; + ++shiftEnd; +} + +void W::ByteArray::push16(uint16_t integer) +{ + uint16_t n16char = htons(integer); + + push8(((uint8_t*)&n16char)[0]); + push8(((uint8_t*)&n16char)[1]); +} + + +void W::ByteArray::push32(uint32_t integer) +{ + uint32_t converted = htonl(integer); + for (uint8_t i(0); i < 4; ++i) + { + push8(((uint8_t*)&converted)[i]); + } +} + +void W::ByteArray::push64(uint64_t integer) +{ + uint32_t h = integer >> 32; + uint32_t l = integer & 0x00000000FFFFFFFF; + + uint32_t convertedh = htonl(h); + for (uint8_t i(0); i < 4; ++i) + { + push8(((uint8_t*)&convertedh)[i]); + } + + uint32_t convertedl = htonl(l); + for (uint8_t i(0); i < 4; ++i) + { + push8(((uint8_t*)&convertedl)[i]); + } +} + + +uint8_t W::ByteArray::pop8() +{ + uint8_t byte = data->at(shiftBegin); + ++shiftBegin; + return byte; +} + +uint16_t W::ByteArray::pop16() +{ + uint8_t h = pop8(); + uint8_t l = pop8(); + + uint8_t src[2] = {h, l}; + + return ntohs(*((uint16_t*) src)); +} + + +uint32_t W::ByteArray::pop32() +{ + uint8_t src[4]; //TODO optimize? + + for (uint8_t i(0); i < 4; ++i) + { + src[i] = pop8(); + } + + return ntohl(*((uint32_t*) src)); +} + +uint64_t W::ByteArray::pop64() +{ + uint8_t srch[4] = {0,0,0,0}; + uint8_t srcl[4] = {0,0,0,0}; + + for (uint8_t i(0); i < 4; ++i) + { + srch[i] = pop8(); + } + + for (uint8_t i(0); i < 4; ++i) + { + srcl[i] = pop8(); + } + + uint64_t h = ntohl(*((uint32_t*) srch)); + uint32_t l = ntohl(*((uint32_t*) srcl)); + + return (h << 32) | l; +} + + +char * W::ByteArray::getData() +{ + return data->data(); //TODO not actually sure about this, may be need to check first about shifts and refence? +} + +W::ByteArray::size_type W::ByteArray::maxSize() const +{ + return data->size(); +} + +bool W::ByteArray::filled() const +{ + return shiftEnd == data->size(); +} + +W::ByteArray::size_type W::ByteArray::fill(const char* data, W::ByteArray::size_type size, W::ByteArray::size_type shift) +{ + size_type i = shift; + while (i < size && !filled()) { + push8(data[i]); + ++i; + } + + return i; +} diff --git a/lib/wType/bytearray.h b/lib/wType/bytearray.h new file mode 100644 index 0000000..7f3da37 --- /dev/null +++ b/lib/wType/bytearray.h @@ -0,0 +1,49 @@ +#ifndef BYTEARRAY_H +#define BYTEARRAY_H + +#include +#include + +namespace W +{ + class ByteArray + { + friend class Socket; + typedef std::vector Container; + + public: + typedef uint32_t size_type; + + ByteArray(size_type size); + ByteArray(const ByteArray& original); + ~ByteArray(); + + ByteArray& operator=(const ByteArray& original); + + void push8(uint8_t integer); + void push16(uint16_t integer); + void push32(uint32_t integer); + void push64(uint64_t integer); + + uint8_t pop8(); + uint16_t pop16(); + uint32_t pop32(); + uint64_t pop64(); + + size_type size() const; + size_type maxSize() const; + bool filled() const; + size_type fill(const char* data, size_type size, size_type shift = 0); + char* getData(); + + private: + Container *data; + size_type shiftBegin; + size_type shiftEnd; + bool referenceMode; + + }; +} + + +#endif // BYTEARRAY_H diff --git a/lib/wType/event.cpp b/lib/wType/event.cpp new file mode 100644 index 0000000..5e68bf4 --- /dev/null +++ b/lib/wType/event.cpp @@ -0,0 +1,179 @@ +#include "event.h" + +W::Event::Event(): + Object(), + system(false), + destination(), + sender(0), + data(0) +{ + +} + +W::Event::Event(const W::Address& p_addr, const W::Object& p_data, bool p_system): + Object(), + system(p_system), + destination(p_addr), + sender(0), + data(p_data.copy()) +{ + +} + +W::Event::Event(const W::Address& p_addr, W::Object* p_data, bool p_system): + Object(), + system(p_system), + destination(p_addr), + sender(0), + data(p_data) +{ + +} + + +W::Event::Event(const W::Event& original): + Object(), + system(original.system), + destination(original.destination), + sender(original.sender), + data(original.data->copy()) +{ + +} + +W::Event::~Event() +{ + delete data; +} + +W::Event& W::Event::operator=(const W::Event& original) +{ + if (&original != this) + { + delete data; + + system = original.system; + destination = original.destination; + sender = original.sender; + data = original.data->copy(); + } + return *this; +} + +W::Object* W::Event::copy() const +{ + return new Event(*this); +} + +W::Object::objectType W::Event::getType() const +{ + return Event::type; +} + +W::Object::size_type W::Event::length() const +{ + size_type my_size = 2; + my_size += destination.length(); + my_size += data->length(); + return my_size; +} + +W::Object::StdStr W::Event::toString() const +{ + StdStr result; + + result += "{"; + + result += "system: "; + result += system.toString(); + + result += ", desitnation: "; + result += destination.toString(); + + result += ", sender: "; + result += sender.toString(); + + result += ", data: "; + result += data->toString(); + + result += "}"; + + return result; +} + +void W::Event::serialize(W::ByteArray& out) const +{ + system.serialize(out); + if (!system) + { + destination.serialize(out); + sender.serialize(out); + } + + out.push8(data->getType()); + data->serialize(out); +} + +void W::Event::deserialize(W::ByteArray& in) +{ + system.deserialize(in); + if (!system) + { + destination.deserialize(in); + sender.deserialize(in); + } + + delete data; + + data = Object::fromByteArray(in); +} + +bool W::Event::isSystem() const +{ + return system; +} + +const W::Address& W::Event::getDestination() const +{ + return destination; +} + +uint64_t W::Event::getSenderId() const +{ + return sender; +} + +const W::Object& W::Event::getData() const +{ + return *data; +} + +void W::Event::setSenderId(const W::Uint64& senderId) +{ + sender = senderId; +} + +void W::Event::setSenderId(uint64_t senderId) +{ + sender = Uint64(senderId); +} + +bool W::Event::operator==(const W::Object& other) const +{ + if (sameType(other)) { + const W::Event& oev = static_cast(other); + return destination == oev.destination && system == oev.system && sender == oev.sender && *data == *(oev.data); + } else { + return false; + } +} + +W::Object::size_type W::Event::size() const +{ + size_type sz = system.size() + data->size() + 1; + if (!system) + { + sz += destination.size() + sender.size(); + } + return sz; +} diff --git a/lib/wType/event.h b/lib/wType/event.h new file mode 100644 index 0000000..6ff0c9b --- /dev/null +++ b/lib/wType/event.h @@ -0,0 +1,53 @@ +#ifndef EVENT_H +#define EVENT_H + +#include "object.h" +#include "address.h" +#include "uint64.h" +#include "boolean.h" + +namespace W +{ + class Event: + public Object + { + public: + Event(); + Event(const Address& p_addr, const Object& p_data, bool p_system = false); + Event(const Address& p_addr, Object* p_data, bool p_system = false); + Event(const Event& original); + ~Event(); + + Event& operator=(const Event& original); + + StdStr toString() const override; + Object* copy() const override; + size_type length() const override; + size_type size() const override; + objectType getType() const override; + + bool operator==(const W::Object & other) const override; + + static const objectType type = event; + + void serialize(ByteArray& out) const override; + void deserialize(ByteArray& in) override; + + bool isSystem() const; + const Address& getDestination() const; + uint64_t getSenderId() const; + const Object& getData() const; + + void setSenderId(const Uint64& senderId); + void setSenderId(uint64_t senderId); + + private: + Boolean system; + Address destination; + Uint64 sender; + Object* data; + + }; +} + +#endif // EVENT_H diff --git a/lib/wType/object.cpp b/lib/wType/object.cpp new file mode 100644 index 0000000..d567867 --- /dev/null +++ b/lib/wType/object.cpp @@ -0,0 +1,106 @@ +#include "object.h" + +#include "string.h" +#include "vocabulary.h" +#include "uint64.h" +#include "address.h" +#include "boolean.h" +#include "event.h" +#include "vector.h" +#include "blob.h" +#include +//#include + +W::Object::Object() +{ + +} + +W::Object::~Object() +{ + +} + +W::Object* W::Object::fromByteArray(ByteArray& in) +{ + uint32_t type = in.pop8(); + + Object *answer; + + switch (type) + { + case string: + answer = new String(); + break; + + case vocabulary: + answer = new Vocabulary(); + break; + + case uint64: + answer = new Uint64(); + break; + + case address: + answer = new Address(); + break; + + case boolean: + answer = new Boolean(); + break; + + case event: + answer = new Event(); + break; + + case vector: + answer = new Vector(); + break; + + case blob: + answer = new Blob(); + break; + } + + answer->deserialize(in); + return answer; +} + +bool W::Object::sameType(const W::Object& other) const +{ + return getType() == other.getType(); +} + +bool W::Object::operator!=(const W::Object& other) const +{ + return !operator==(other); +} + +W::Object::StdStr W::Object::getTypeName(W::Object::objectType type) +{ + switch (type) { + case string: + return "String"; + case vocabulary: + return "Vocabulary"; + + case uint64: + return "Uint64"; + + case address: + return "Address"; + + case boolean: + return "Boolean"; + + case event: + return "Event"; + + case vector: + return "Vector"; + + case blob: + return "Blob"; + } +} + diff --git a/lib/wType/object.h b/lib/wType/object.h new file mode 100644 index 0000000..8a37360 --- /dev/null +++ b/lib/wType/object.h @@ -0,0 +1,55 @@ +#ifndef WOBJCET_H +#define WOBJCET_H + +#include +#include +#include "bytearray.h" + +namespace W { + + class Object + { + friend class ByteArray; + public: + enum objectType + { + string, + vocabulary, + uint64, + address, + boolean, + event, + vector, + blob + }; + + typedef uint64_t size_type; + typedef std::string StdStr; + + Object(); + virtual ~Object(); + + virtual StdStr toString() const = 0; + virtual Object* copy() const = 0; + virtual size_type length() const = 0; //object specific size - like amount of subelements or amount of characters + virtual size_type size() const = 0; //object size in bytes + virtual bool operator==(const Object& other) const = 0; + virtual bool operator!=(const Object& other) const; //TODO may be make it also pure virtual? + + virtual objectType getType() const = 0; + + virtual void serialize(ByteArray& out) const = 0; + virtual void deserialize(ByteArray& in) = 0; + + static StdStr getTypeName(objectType type); + + protected: + bool sameType(const Object& other) const; + + public: + static Object* fromByteArray(ByteArray& in); + + }; +} + +#endif // WOBJCET_H diff --git a/lib/wType/string.cpp b/lib/wType/string.cpp new file mode 100644 index 0000000..afeda36 --- /dev/null +++ b/lib/wType/string.cpp @@ -0,0 +1,208 @@ +#include "string.h" + +#include +#include + +W::String::String(): + Object(), + data(new u16string()) +{ + +} + +W::String::String(const u16string& p_data): + Object(), + data(new u16string(p_data)) +{ + +} + +W::String::String(const char16_t* p_data): + Object(), + data(new u16string(p_data)) +{ + +} + + +W::String::String(const W::String& original): + Object(), + data(new u16string(*original.data)) +{ + +} + +W::String::String(const StdStr p_data): + Object(), + data(0) +{ + std::wstring_convert,char16_t> convert; + std::u16string u16 = convert.from_bytes(p_data); + + data = new u16string(u16); +} + + +W::String::~String() +{ + delete data; +} + +W::String& W::String::operator=(const W::String& original) +{ + if (&original != this) + { + delete data; + data = new u16string(*(original.data)); + } + return *this; +} + + +W::Object* W::String::copy() const +{ + return new String(*this); +} + +W::Object::StdStr W::String::toString() const +{ + std::wstring_convert, char16_t> convertor; + StdStr result = convertor.to_bytes(*data); + + return result; +} + +W::Object::size_type W::String::length() const +{ + return data->size(); +} + +void W::String::serialize(W::ByteArray& out) const +{ + out.push32(length());; + + u16string::const_iterator itr = data->begin(); + u16string::const_iterator end = data->end(); + + for(; itr != end; ++itr) + { + out.push16(*itr); + } +} + +void W::String::deserialize(W::ByteArray& in) +{ + data->clear(); + + size_type length = in.pop32(); + + for (size_type i = 0; i != length; ++i) + { + data->push_back(in.pop16()); + } +} + +W::Object::objectType W::String::getType() const +{ + return String::type; +} + +bool W::String::operator<(const W::String& other) const +{ + return (*data) < *(other.data); +} + +bool W::String::operator>(const W::String& other) const +{ + return (*data) > *(other.data); +} + +bool W::String::operator<=(const W::String& other) const +{ + return (*data) <= *(other.data); +} + +bool W::String::operator>=(const W::String& other) const +{ + return (*data) >= *(other.data); +} + +bool W::String::operator!=(const W::String& other) const +{ + return (*data) != *(other.data); +} + +bool W::String::operator==(const W::String& other) const +{ + return (*data) == *(other.data); +} + +bool W::String::operator==(const char16_t* other) const +{ + return *data == other; +} + +bool W::String::operator!=(const char16_t* other) const +{ + return *data != other; +} + +W::String::operator u16string() const +{ + return *data; +} + +W::String & W::String::operator+=(int number) +{ + StdStr str = std::to_string(number);; + std::wstring_convert,char16_t> convert; + std::u16string u16 = convert.from_bytes(str); + (*data) += u16; + + return *this; +} + +W::String W::String::operator+(const W::String& other) const +{ + return W::String((*data) + *(other.data)); +} + +W::String & W::String::operator+=(const W::String& other) +{ + (*data) += *(other.data); + return *this; +} + +W::String::size_type W::String::findFirstOf(const W::String& str) const +{ + return data->find_first_of(*(str.data)); +} + +W::String::size_type W::String::findLastOf(const W::String& str) const +{ + return data->find_last_of(*(str.data)); +} + +W::String W::String::substr(size_type start, size_type length) const +{ + return W::String(data->substr(start, length)); +} + +uint64_t W::String::toUint64() const +{ + return std::stoull(toString()); +} + +bool W::String::operator==(const W::Object& other) const +{ + if (sameType(other)) { + return operator==(static_cast(other)); + } else { + return false; + } +} + +W::Object::size_type W::String::size() const +{ + return data->size() * 2 + 4; +} diff --git a/lib/wType/string.h b/lib/wType/string.h new file mode 100644 index 0000000..9586e87 --- /dev/null +++ b/lib/wType/string.h @@ -0,0 +1,65 @@ +#ifndef W_STRING_H +#define W_STRING_H + +#include "object.h" + +namespace W{ + + class String : public Object + { + public: + typedef std::u16string u16string; + + String(); + explicit String(const u16string& p_data); + explicit String(const char16_t* p_data); + explicit String(const StdStr p_data); + String(const String& original); + + ~String(); + + String& operator=(const String& original); + + StdStr toString() const override; + Object* copy() const override; + size_type length() const override; + size_type size() const override; + objectType getType() const override; + + bool operator==(const W::Object & other) const override; + + bool operator<(const String& other) const; + bool operator>(const String& other) const; + bool operator<=(const String& other) const; + bool operator>=(const String& other) const; + bool operator==(const String& other) const; + bool operator!=(const String& other) const; + + bool operator==(const char16_t* other) const; + bool operator!=(const char16_t* other) const; + + static const objectType type = string; + + operator u16string() const; + uint64_t toUint64() const; + + void serialize(ByteArray& out) const override; + void deserialize(ByteArray& in) override; + + String& operator+=(int); + String& operator+=(const String& other); + String operator+(const String& other) const; + + size_type findLastOf(const W::String& str) const; + size_type findFirstOf(const W::String& str) const; + + String substr(size_type start, size_type length = u16string::npos) const; + + private: + u16string* data; + + }; + +} + +#endif // W_STRING_H diff --git a/lib/wType/uint64.cpp b/lib/wType/uint64.cpp new file mode 100644 index 0000000..d0b3aa1 --- /dev/null +++ b/lib/wType/uint64.cpp @@ -0,0 +1,114 @@ +#include "uint64.h" +#include + +W::Uint64::Uint64(): + Object(), + data(0) +{ + +} + +W::Uint64::Uint64(const uint64_t& original): + Object(), + data(original) +{ + +} + +W::Uint64::Uint64(const W::Uint64& original): + Object(), + data(original.data) +{ + +} + +W::Uint64::~Uint64() +{ + +} + +W::Uint64& W::Uint64::operator=(const W::Uint64& original) +{ + data = original.data; + + return *this; +} + +W::Object::StdStr W::Uint64::toString() const +{ + return std::to_string(data); +} + +W::Object* W::Uint64::copy() const +{ + return new W::Uint64(*this); +} + +W::Object::size_type W::Uint64::length() const +{ + return 1; +} + +W::Object::size_type W::Uint64::size() const +{ + return 8; +} + +W::Object::objectType W::Uint64::getType() const +{ + return type; +} + +bool W::Uint64::operator<(const W::Uint64& other) const +{ + return data < other.data; +} + +bool W::Uint64::operator>(const W::Uint64& other) const +{ + return data > other.data; +} + +bool W::Uint64::operator==(const W::Uint64& other) const +{ + return data == other.data; +} + +bool W::Uint64::operator!=(const W::Uint64& other) const +{ + return data != other.data; +} + +bool W::Uint64::operator<=(const W::Uint64& other) const +{ + return data <= other.data; +} + +bool W::Uint64::operator>=(const W::Uint64& other) const +{ + return data >= other.data; +} + +void W::Uint64::serialize(W::ByteArray& out) const +{ + out.push64(data); +} + +void W::Uint64::deserialize(W::ByteArray& in) +{ + data = in.pop64(); +} + +W::Uint64::operator uint64_t() const +{ + return data; +} + +bool W::Uint64::operator==(const W::Object& other) const +{ + if (sameType(other)) { + return operator==(static_cast(other)); + } else { + return false; + } +} diff --git a/lib/wType/uint64.h b/lib/wType/uint64.h new file mode 100644 index 0000000..ea37b26 --- /dev/null +++ b/lib/wType/uint64.h @@ -0,0 +1,49 @@ +#ifndef UINT64_H +#define UINT64_H + +#include + +#include "object.h" + +namespace W +{ + class Uint64: + public Object + { + public: + Uint64(); + explicit Uint64(const uint64_t& original); + Uint64(const Uint64& original); + ~Uint64(); + + Uint64& operator=(const Uint64& original); + + StdStr toString() const override; + Object* copy() const override; + size_type length() const override; + size_type size() const override; + objectType getType() const override; + + bool operator==(const W::Object & other) const override; + + bool operator<(const Uint64& other) const; + bool operator>(const Uint64& other) const; + bool operator<=(const Uint64& other) const; + bool operator>=(const Uint64& other) const; + bool operator==(const Uint64& other) const; + bool operator!=(const Uint64& other) const; + + static const objectType type = uint64; + + void serialize(ByteArray& out) const override; + void deserialize(ByteArray& in) override; + + operator uint64_t() const; + + private: + uint64_t data; + + }; +} + +#endif // UINT64_H diff --git a/lib/wType/vector.cpp b/lib/wType/vector.cpp new file mode 100644 index 0000000..626df6f --- /dev/null +++ b/lib/wType/vector.cpp @@ -0,0 +1,191 @@ +#include "vector.h" +#include + +W::Vector::Vector(): + Object(), + data(new Vec()) +{ + +} + +W::Vector::Vector(const W::Vector& original): + Object(), + data(new Vec()) +{ + data->reserve(original.data->capacity()); + + Vec::const_iterator itr = original.data->begin(); + Vec::const_iterator end = original.data->end(); + + for (; itr != end; ++itr) + { + data->push_back((*itr)->copy()); + } +} + + +W::Vector::~Vector() +{ + clear(); + delete data; +} + +W::Vector& W::Vector::operator=(const W::Vector& original) +{ + if (&original != this) + { + clear(); + Vec::const_iterator itr = original.data->begin(); + Vec::const_iterator end = original.data->end(); + + for (; itr != end; ++itr) + { + data->push_back((*itr)->copy()); + } + } + return *this; +} + +void W::Vector::clear() +{ + Vec::iterator itr = data->begin(); + Vec::iterator end = data->end(); + + for (; itr != end; ++itr) + { + delete (*itr); + } + + data->clear(); +} + +W::Object::StdStr W::Vector::toString() const +{ + StdStr result; + + Vec::const_iterator itr; + Vec::const_iterator beg = data->begin(); + Vec::const_iterator end = data->end(); + + result += "["; + for (itr = beg; itr != end; ++itr) + { + if (itr != beg) + { + result += ", "; + } + result += (*itr)->toString(); + } + result += "]"; + + return result; +} + +W::Object* W::Vector::copy() const +{ + return new Vector(*this); +} + +W::Object::size_type W::Vector::length() const +{ + return data->size(); +} + +W::Object::size_type W::Vector::size() const +{ + size_type size = 4; + + Vec::const_iterator itr = data->begin(); + Vec::const_iterator end = data->end(); + for (; itr != end; ++itr) + { + size += (*itr)->size() + 1; + } + + return size; +} + +W::Object::objectType W::Vector::getType() const +{ + return Vector::type; +} + +void W::Vector::serialize(W::ByteArray& out) const +{ + out.push32(length()); + + Vec::const_iterator itr = data->begin(); + Vec::const_iterator end = data->end(); + for (; itr != end; ++itr) + { + out.push8((*itr)->getType()); + (*itr)->serialize(out); + } +} + +void W::Vector::deserialize(W::ByteArray& in) +{ + data->clear(); + + size_type length = in.pop32(); + + for (size_type i = 0; i != length; ++i) + { + Object* value = Object::fromByteArray(in); + data->push_back(value); + } +} + +void W::Vector::push(const W::Object& value) +{ + data->push_back(value.copy()); +} + +void W::Vector::push(W::Object* value) +{ + data->push_back(value); +} + +const W::Object& W::Vector::at(uint64_t index) const +{ + if (index >= length()) { + throw NoElement(index); + } + return *(data->at(index)); +} + +W::Vector::NoElement::NoElement(uint64_t index): + Exception(), + key(index) +{ + +} +std::string W::Vector::NoElement::getMessage() const +{ + std::string msg("No element has been found by index: "); + msg += std::to_string(key); + + return msg; +} + + +bool W::Vector::operator==(const W::Object& other) const +{ + if (sameType(other)) { + return operator==(static_cast(other)); + } else { + return false; + } +} + +bool W::Vector::operator==(const W::Vector& other) const +{ + bool equals = data->size() == other.data->size(); + int i = 0; + while (equals && i != data->size()) { + equals = data->at(i) == other.data->at(i); + ++i; + } + + return equals; +} diff --git a/lib/wType/vector.h b/lib/wType/vector.h new file mode 100644 index 0000000..958cfcb --- /dev/null +++ b/lib/wType/vector.h @@ -0,0 +1,60 @@ +#ifndef VECTOR_H +#define VECTOR_H + +#include "object.h" +#include +#include +#include + +namespace W +{ + class Vector: + public Object + { + typedef std::vector Vec; + + public: + Vector(); + Vector(const Vector& original); + ~Vector(); + + Vector& operator=(const Vector& original); + + StdStr toString() const override; + Object* copy() const override; + size_type length() const override; + size_type size() const override; + objectType getType() const override; + + bool operator==(const W::Object & other) const override; + bool operator==(const W::Vector & other) const; + + void clear(); + void push(const Object& value); + void push(Object* value); + const Object& at(uint64_t index) const; + + static const objectType type = vector; + + void serialize(ByteArray& out) const override; + void deserialize(ByteArray& in) override; + + class NoElement: + Utils::Exception + { + public: + NoElement(uint64_t index); + + std::string getMessage() const; + + private: + uint64_t key; + }; + + private: + Vec *data; + + }; +} + +#endif // VECTOR_H diff --git a/lib/wType/vocabulary.cpp b/lib/wType/vocabulary.cpp new file mode 100644 index 0000000..a87f853 --- /dev/null +++ b/lib/wType/vocabulary.cpp @@ -0,0 +1,296 @@ +#include "vocabulary.h" + +W::Vocabulary::Vocabulary(): + Object(), + data(new Map()) +{ + +} + +W::Vocabulary::Vocabulary(const W::Vocabulary& original): + Object(), + data(new Map()) +{ + Map::const_iterator itr = original.data->begin(); + Map::const_iterator end = original.data->end(); + + for (; itr != end; ++itr) + { + (*data)[itr->first] = itr->second->copy(); + } +} + + +W::Vocabulary::~Vocabulary() +{ + clear(); + delete data; +} + +W::Vocabulary& W::Vocabulary::operator=(const W::Vocabulary& original) +{ + if (&original != this) + { + clear(); + Map::const_iterator itr = original.data->begin(); + Map::const_iterator end = original.data->end(); + + for (; itr != end; ++itr) + { + (*data)[itr->first] = itr->second->copy(); + } + } + return *this; +} + +void W::Vocabulary::clear() +{ + Map::iterator itr = data->begin(); + Map::iterator end = data->end(); + + for (; itr != end; ++itr) + { + delete itr->second; + } + + data->clear(); +} + +W::Object::StdStr W::Vocabulary::toString() const +{ + StdStr result; + + Map::const_iterator itr; + Map::const_iterator beg = data->begin(); + Map::const_iterator end = data->end(); + + result += "{"; + for (itr = beg; itr != end; ++itr) + { + if (itr != beg) + { + result += ", "; + } + result += itr->first.toString(); + result += ": "; + result += itr->second->toString(); + } + result += "}"; + + return result; +} + +W::Object* W::Vocabulary::copy() const +{ + return new Vocabulary(*this); +} + +W::Object::size_type W::Vocabulary::length() const +{ + return data->size(); +} + +W::Object::size_type W::Vocabulary::size() const +{ + size_type size = 4; + + Map::const_iterator itr = data->begin(); + Map::const_iterator end = data->end(); + + for (; itr != end; ++itr) + { + size += itr->first.size(); + size += itr->second->size() + 1; + } + + return size; +} + + +W::Object::objectType W::Vocabulary::getType() const +{ + return Vocabulary::type; +} + +void W::Vocabulary::serialize(W::ByteArray& out) const +{ + out.push32(length()); + + Map::const_iterator itr = data->begin(); + Map::const_iterator end = data->end(); + for (; itr != end; ++itr) + { + itr->first.serialize(out); + out.push8(itr->second->getType()); + itr->second->serialize(out); + } +} + +void W::Vocabulary::deserialize(W::ByteArray& in) +{ + data->clear(); + + size_type length = in.pop32(); + + for (size_type i = 0; i != length; ++i) + { + String key; + key.deserialize(in); + Object* value = Object::fromByteArray(in); + + (*data)[key] = value; + } +} + +void W::Vocabulary::insert(const W::String::u16string& key, const W::Object& value) +{ + String strKey(key); + insert(strKey, value); +} + +void W::Vocabulary::insert(const W::String& key, const W::Object& value) +{ + Map::const_iterator itr = data->find(key); + + if (itr != data->end()) + { + delete itr->second; + } + (*data)[key] = value.copy(); +} + +void W::Vocabulary::insert(const String::u16string& key, W::Object* value) +{ + String strKey(key); + insert(strKey, value); +} + +void W::Vocabulary::insert(const W::String& key, W::Object* value) +{ + Map::const_iterator itr = data->find(key); + + if (itr != data->end()) + { + delete itr->second; + } + (*data)[key] = value; +} + + +const W::Object& W::Vocabulary::at(const String::u16string& key) const +{ + String strKey(key); + return at(strKey); +} + +const W::Object& W::Vocabulary::at(const W::String& key) const +{ + Map::const_iterator itr = data->find(key); + + if (itr == data->end()) + { + throw NoElement(key); + } + + return *(itr->second); +} + +bool W::Vocabulary::has(const String::u16string& key) const +{ + String strKey(key); + return has(strKey); +} + +bool W::Vocabulary::has(const W::String& key) const +{ + Map::const_iterator itr = data->find(key); + + return itr != data->end(); +} + +void W::Vocabulary::erase(const String::u16string& key) +{ + String strKey(key); + erase(strKey); +} + +void W::Vocabulary::erase(const W::String& key) +{ + Map::const_iterator itr = data->find(key); + + if (itr == data->end()) + { + throw NoElement(key); + } + + data->erase(itr); +} + +W::Vector W::Vocabulary::keys() const +{ + Vector keys; + Map::iterator itr = data->begin(); + Map::iterator end = data->end(); + + for (; itr != end; ++itr) + { + keys.push(itr->first); + } + + return keys; +} + + +W::Vocabulary::NoElement::NoElement(const W::String& p_key): + Exception(), + key(p_key) +{ + +} + + +std::string W::Vocabulary::NoElement::getMessage() const +{ + std::string msg("No element has been found by key: "); + msg += key.toString(); + + return msg; +} + +bool W::Vocabulary::operator==(const W::Object& other) const +{ + if (sameType(other)) { + return operator==(static_cast(other)); + } else { + return false; + } +} + +bool W::Vocabulary::operator==(const W::Vocabulary& other) const +{ + bool equal = data->size() == other.data->size(); + Map::const_iterator mItr = data->begin(); + Map::const_iterator oItr = other.data->begin(); + + while (equal && mItr != data->end()) { + equal = (mItr->first == oItr->first) && (mItr->second == oItr->second); + ++mItr; + ++oItr; + } + + return equal; +} + +W::Vocabulary * W::Vocabulary::extend(const W::Vocabulary& first, const W::Vocabulary& second) +{ + W::Vocabulary* vc = static_cast(first.copy()); + + Map::const_iterator itr = second.data->begin(); + Map::const_iterator end = second.data->end(); + + for (; itr != end; ++itr) { + vc->data->operator[](itr->first) = itr->second; + } + + return vc; +} diff --git a/lib/wType/vocabulary.h b/lib/wType/vocabulary.h new file mode 100644 index 0000000..458a073 --- /dev/null +++ b/lib/wType/vocabulary.h @@ -0,0 +1,72 @@ +#ifndef VOCABULARY_H +#define VOCABULARY_H + +#include "object.h" +#include "string.h" +#include "vector.h" + +#include + +#include + +namespace W +{ + class Vocabulary: + public Object + { + typedef std::map Map; + + public: + Vocabulary(); + Vocabulary(const Vocabulary& original); + ~Vocabulary(); + + Vocabulary& operator=(const Vocabulary& original); + + StdStr toString() const override; + Object* copy() const override; + size_type length() const override; + size_type size() const override; + objectType getType() const override; + + bool operator==(const W::Object & other) const override; + bool operator==(const W::Vocabulary& other) const; + + void clear(); + void insert(const String::u16string& key, const Object& value); + void insert(const String& key, const Object& value); + void insert(const String::u16string& key, Object* value); + void insert(const String& key, Object* value); + const Object& at(const String::u16string& key) const; + const Object& at(const String& key) const; + bool has(const String::u16string& key) const; + bool has(const String& key) const; + void erase(const String::u16string& key); + void erase(const String& key); + Vector keys() const; + + static const objectType type = vocabulary; + + void serialize(ByteArray& out) const override; + void deserialize(ByteArray& in) override; + + static W::Vocabulary* extend(const W::Vocabulary& first, const W::Vocabulary& second); + + class NoElement: + Utils::Exception + { + public: + NoElement(const String& p_key); + + std::string getMessage() const; + + private: + String key; + }; + + private: + Map *data; + + }; +} +#endif // VOCABULARY_H diff --git a/libjs/CMakeLists.txt b/libjs/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/libjs/polymorph/CMakeLists.txt b/libjs/polymorph/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/libjs/polymorph/dependency.js b/libjs/polymorph/dependency.js new file mode 100644 index 0000000..f8fc493 --- /dev/null +++ b/libjs/polymorph/dependency.js @@ -0,0 +1,76 @@ +"use strict"; + +var Dependency = function(options) { + if (!Dependency.configured) { + throw new Error("Unable to create a dependency without static variables configuration, use Dependency.configure first"); + } + this._string = options.string; + this._text = options.text; + + this._parse(); +} + +Dependency.configured = false; +Dependency.configure = function(libDir, target) { + this.libDir = libDir; + this.target = target; + var tPath = target.replace(libDir, "lib"); + tPath = tPath.replace(".js", ""); + tPath = tPath.split("/"); + tPath.pop(); + this.path = tPath; + this.configured = true; +}; + +Dependency.prototype.log = function() { + console.log("---------------------------------"); + console.log("Found string: " + this._string); + console.log("Captured dependency: " + this._text); + console.log("Output dependency: " + this._name); + console.log("Lib is: " + Dependency.libDir); + console.log("Target is: " + Dependency.target); + console.log("---------------------------------"); +} + +Dependency.prototype._parse = function() { + var fl = this._text[0]; + var dArr = this._text.split("/"); + + if (fl !== ".") { + this._name = "lib/" + this._text + "/index"; + } else { + var summ = Dependency.path.slice(); + this._name = ""; + + for (var i = 0; i < dArr.length; ++i) { + var hop = dArr[i]; + switch (hop) { + case ".": + case "": + break; + case "..": + summ.pop(); + break + default: + summ.push(hop); + break; + } + } + + for (var j = 0; j < summ.length; ++j) { + if (j !== 0) { + this._name += "/" + } + this._name += summ[j]; + } + } +} + +Dependency.prototype.modify = function(line) { + return line.replace(this._text, this._name); +} +Dependency.prototype.toString = function() { + return this._name; +} + +module.exports = Dependency; diff --git a/libjs/polymorph/depresolver.js b/libjs/polymorph/depresolver.js new file mode 100644 index 0000000..1cb62e5 --- /dev/null +++ b/libjs/polymorph/depresolver.js @@ -0,0 +1,51 @@ +"use strict"; +var Dependency = require("./dependency"); + +var DepResolver = function(options) { + Dependency.configure(options.libDir, options.target) + + this._reg1 = /(?:require\s*\((?:\"(.*)\"|\'(.*)\')\)[^;,]*(?:[;,]|$))(?!\s*\/\/not\sa\sd)/g; + this._reg2 = /(?:\"(.+)\"|\'(.+)\'),{0,1}(?=\s*\/\/resolve as d)/g; +} + +DepResolver.prototype.resolve = function(lines) { + var regres; + var dep; + var header = []; + var dependencies = []; + dependencies.push("var defineArray = [];\n"); + + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + var found = false; + while((regres = this._reg1.exec(line)) !== null) { + dep = new Dependency({ + string: regres[0], + text: regres[1] + }); + lines[i] = dep.modify(line); + dependencies.push("defineArray.push(\"" + dep.toString() + "\");"); + found = true; + } + if (!found) { + while((regres = this._reg2.exec(line)) !== null) { + dep = new Dependency({ + string: regres[0], + text: regres[1] + }); + + lines[i] = dep.modify(line); + } + } + + } + header.push("define(moduleName, defineArray, function() {"); + + return { + header: header, + dependencies: dependencies, + bottom: "});" + } +}; + +module.exports = DepResolver; diff --git a/libjs/polymorph/index.js b/libjs/polymorph/index.js new file mode 100644 index 0000000..22735b7 --- /dev/null +++ b/libjs/polymorph/index.js @@ -0,0 +1,75 @@ +"use strict"; +var fs = require("fs"); +var DepResolver = require("./depresolver"); +var pathCheck = require("./pathCheck"); + +var path = process.argv[2]; +var target = process.argv[3]; +var moduleName = process.argv[4]; +var env = process.argv[5]; +var libDir = process.argv[6]; + +var isNode = true; +if (env === "browser") { + isNode = false; +} + +var dr = new DepResolver({ + target: target, + libDir: libDir +}); + +fs.readFile(path, function(err, buffer) { + if (err) { + throw err; + } + console._stdout.write(String.fromCharCode(27) + "[1;32m"); + console._stdout.write("polymorph: "); + console._stdout.write(String.fromCharCode(27) + "[0m"); + console._stdout.write("parsing " + moduleName + " for " + env); + console._stdout.write("\n"); + + var file = buffer.toString(); + var output = ""; + if (!isNode) { + file = file.replace(/module.exports[\s]*=/g, "return"); + file = file.replace(/\"use\sstrict\";/g, ""); + var lines = file.split("\n"); + + output = "\"use strict\";\n" + + "(function(global) {\n" + + " var moduleName = \""+moduleName+"\"\n"; + + var add = dr.resolve(lines); + + for (var i = 0; i < add.dependencies.length; ++i) { + output += " " + add.dependencies[i] + "\n"; + } + + output += "\n"; + + for (var i = 0; i < add.header.length; ++i) { + output += " " + add.header[i] + "\n"; + } + + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + output += " " + line + "\n"; + } + + output += " " + add.bottom + "\n"; + output += "})(window);"; + } else { + output = file; + } + var p = target.split("/"); + p[1] = "/" + p[1] + pathCheck(p.slice(1, -1), function(err) { + if (err) throw err; + fs.writeFile(target, output, function(err) { + if (err) throw err; + process.exit(0); + }); + }) +}) + diff --git a/libjs/polymorph/pathCheck.js b/libjs/polymorph/pathCheck.js new file mode 100644 index 0000000..c5804d9 --- /dev/null +++ b/libjs/polymorph/pathCheck.js @@ -0,0 +1,34 @@ +"use strict"; +const fs = require("fs"); + +function pathCheck(/*Array*/path, cb) { + fs.access(path[0], function(err) { + if (err) { + if (err.code === 'ENOENT') { + fs.mkdir(path[0], function() { + if (path.length === 1) { + cb(); + } else { + let nPath = path.slice(); + let out = nPath.splice(1, 1); + nPath[0] += "/" + out[0]; + pathCheck(nPath, cb); + } + }) + } else { + cb(err); + } + } else { + if (path.length === 1) { + cb(); + } else { + let nPath = path.slice(); + let out = nPath.splice(1, 1); + nPath[0] += "/" + out[0]; + pathCheck(nPath, cb); + } + } + }) +} + +module.exports = pathCheck; diff --git a/libjs/utils/CMakeLists.txt b/libjs/utils/CMakeLists.txt new file mode 100644 index 0000000..2d5d83b --- /dev/null +++ b/libjs/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(class.js class.js) +configure_file(subscribable.js subscribable.js) +configure_file(globalMethods.js globalMethods.js) \ No newline at end of file diff --git a/libjs/utils/class.js b/libjs/utils/class.js new file mode 100644 index 0000000..1db1159 --- /dev/null +++ b/libjs/utils/class.js @@ -0,0 +1,52 @@ +"use strict"; + +var Class = function() {}; + +Class.inherit = function(proto) { + var superClass = this; + var Base = function() {}; + var subclass = proto && (proto.constructor !== Object) ? proto.constructor : function() { + superClass.apply(this, arguments) + }; + + Base.prototype = this.prototype; + var fn = new Base(); + + for (var key in proto) { + if (proto.hasOwnProperty(key)) { + fn[key] = proto[key]; + } + } + for (var method in this) { + if (this.hasOwnProperty(method)) { + subclass[method] = this[method]; + } + } + + fn.constructor = subclass; + subclass.prototype = subclass.fn = fn; + + return subclass; +} + +module.exports = Class.inherit({ + className: "Class", + constructor: function() { + if (!(this instanceof Class)) { + throw new SyntaxError("You didn't call \"new\" operator"); + } + + Class.call(this); + this._uncyclic = []; + }, + destructor: function() { + for (var i = 0; i < this._uncyclic.length; ++i) { + this._uncyclic[i].call(this); + } + + for (var key in this) { + this[key] = undefined; + delete this[key]; + } + } +}); diff --git a/libjs/utils/globalMethods.js b/libjs/utils/globalMethods.js new file mode 100644 index 0000000..c104d9a --- /dev/null +++ b/libjs/utils/globalMethods.js @@ -0,0 +1,69 @@ +"use strict"; + +global.W = { + extend: function () { + var lTarget = arguments[0] || {}; + var lIndex = 1; + var lLength = arguments.length; + var lDeep = false; + var lOptions, lName, lSrc, lCopy, lCopyIsArray, lClone; + + // Handle a deep copy situation + if (typeof lTarget === "boolean") { + lDeep = lTarget; + lTarget = arguments[1] || {}; + // skip the boolean and the target + lIndex = 2; + } + + // Handle case when target is a string or something (possible in deep + // copy) + if (typeof lTarget !== "object" && typeof lTarget != "function") { + lTarget = {}; + } + + if (lLength === lIndex) { + lTarget = this; + --lIndex; + } + + for (; lIndex < lLength; lIndex++) { + // Only deal with non-null/undefined values + if ((lOptions = arguments[lIndex]) != undefined) { + // Extend the base object + for (lName in lOptions) { + lSrc = lTarget[lName]; + lCopy = lOptions[lName]; + + // Prevent never-ending loop + if (lTarget === lCopy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if (lDeep && lCopy && (Object.isObject(lCopy) || (lCopyIsArray = Array.isArray(lCopy)))) { + if (lCopyIsArray) { + lCopyIsArray = false; + lClone = lSrc && Array.isArray(lSrc) ? lSrc : []; + + } else { + lClone = lSrc && Object.isObject(lSrc) ? lSrc : {}; + } + + // Never move original objects, clone them + lTarget[lName] = Object.extend(lDeep, lClone, lCopy); + + // Don't bring in undefined values + } else { + if (lCopy !== undefined) { + lTarget[lName] = lCopy; + } + } + } + } + } + + // Return the modified object + return lTarget; + } +}; diff --git a/libjs/utils/subscribable.js b/libjs/utils/subscribable.js new file mode 100644 index 0000000..6783bc2 --- /dev/null +++ b/libjs/utils/subscribable.js @@ -0,0 +1,94 @@ +"use strict"; + +var Class = require("./class"); + +var Subscribable = Class.inherit({ + "className": "Subscribable", + "constructor": function() { + Class.fn.constructor.call(this); + + this._events = Object.create(null); + }, + "destructor": function() { + this.off(); + Class.fn.destructor.call(this); + }, + + "on": function(name, handler, context) { + var handlers = this._events[name]; + + if (typeof name !== "string") { + throw new Error("Name of event is mandatory"); + } + + if (!(handler instanceof Function)) { + throw new Error("Handler of event is mandatory"); + } + + if (!handlers) { + handlers = []; + this._events[name] = handlers; + } + + handlers.push({ + handler: handler, + context: context || this, + once: false + }); + }, + "one": function(name) { + Subscribable.fn.on.apply(this, arguments); + this._events[name][this._events[name].length - 1].once = true; + }, + "off": function(name, handler, context) { + + if (typeof name === "string") { + if (this._events[name]) { + if (handler instanceof Function) { + var handlers = this._events[name]; + for (var i = handlers.length - 1; i >= 0 ; --i) { + if (handlers[i].handler === handler) { + if (context) { + if (handlers[i].context === context) { + handlers.splice(i, 1); + } + } else { + handlers.splice(i, 1); + } + } + } + } else { + delete this._events[name]; + } + } + } else { + this._events = Object.create(null); + } + }, + "trigger": function() { + var args = [].slice.call(arguments); + if (args.length === 0) { + throw new Error("Name of event is mandatory"); + } + var answer = false; + var name = args.shift(); + var handlers = this._events[name]; + if (handlers) { + for (var i = 0; i < handlers.length; ++i) { + var handle = handlers[i]; + answer = handle.handler.apply(handle.context, args); + if (handle.once) { + handlers.splice(i, 1); + --i; + } + + if (answer === false) { + return false; + } + } + } + return answer; + } +}); + +module.exports = Subscribable; diff --git a/libjs/wContainer/CMakeLists.txt b/libjs/wContainer/CMakeLists.txt new file mode 100644 index 0000000..2e8fe37 --- /dev/null +++ b/libjs/wContainer/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(abstractpair.js abstractpair.js) +configure_file(abstractmap.js abstractmap.js) +configure_file(abstractlist.js abstractlist.js) +configure_file(abstractorder.js abstractorder.js) +configure_file(abstractset.js abstractset.js) diff --git a/libjs/wContainer/abstractlist.js b/libjs/wContainer/abstractlist.js new file mode 100644 index 0000000..65561bb --- /dev/null +++ b/libjs/wContainer/abstractlist.js @@ -0,0 +1,204 @@ +"use strict"; +var Class = require("../utils/class"); + +var AbstractList = Class.inherit({ + "className": "AbstractList", + "constructor": function(owning) { + Class.fn.constructor.call(this); + + if (!this.constructor.dataType) { + throw new Error("An attempt to instantiate a list without declared member type"); + } + + this._owning = owning !== false; + this._begin = new ListNode(this); + this._end = this._begin; + this._size = 0; + }, + "destructor": function() { + this.clear(); + + Class.fn.destructor.call(this); + }, + "begin": function() { + return new ListIterator(this._begin); + }, + "clear": function() { + var node = this._begin; + while (node !== this._end) { + node = node._next; + node._prev.destructor(); + } + node._prev = null; + + this._begin = node; + this._size = 0; + }, + "end": function() { + return new ListIterator(this._end); + }, + "erase": function(begin, end) { + if (end === undefined) { + end = begin.clone(); + end["++"](); + } + + if (begin._node === this._begin) { + this._begin = end._node; + end._node._prev = null; + } else { + begin._node._prev._next = end._node; + end._node._prev = begin._node._prev; + } + + while(!begin["=="](end)) { + + var node = begin._node; + begin["++"](); + --this._size; + node.destructor(); + } + }, + "insert": function(data, target) { + if (target._node._list !== this) { + throw new Error("An attempt to insert to list using iterator from another container"); + } + if (!(data instanceof this.constructor.dataType)) { + throw new Error("An attempt to insert wrong data type into list"); + } + var node = new ListNode(this); + node._data = data; + + if (target._node === this._begin) { + this._begin = node; + } else { + var bItr = target.clone(); + bItr["--"](); + var before = bItr._node; + before._next = node; + node._prev = before; + bItr.destructor(); + } + + node._next = target._node; + target._node._prev = node; + + ++this._size; + }, + "pop_back": function() { + if (this._begin === this._end) { + throw new Error("An attempt to pop from empty list"); + } + var node = this._end._prev; + + if (node === this._begin) { + this._begin = this._end; + this._end._prev = null; + } else { + node._prev._next = this._end; + this._end._prev = node._prev; + } + node.destructor(); + + --this._size; + }, + "push_back": function(data) { + if (!(data instanceof this.constructor.dataType)) { + throw new Error("An attempt to insert wrong data type into list"); + } + + var node = new ListNode(this); + node._data = data; + if (this._size === 0) { + this._begin = node; + } else { + this._end._prev._next = node; + node._prev = this._end._prev; + } + + node._next = this._end; + this._end._prev = node; + + this._size++; + }, + "size": function() { + return this._size; + } +}); + +var ListNode = Class.inherit({ + "className": "ListNode", + "constructor": function(list) { + Class.fn.constructor.call(this); + + this._list = list + this._data = null; + this._next = null; + this._prev = null; + this._owning = list._owning !== false; + }, + "destructor": function() { + if (this._owning && (this._data instanceof Class)) { + this._data.destructor(); + } + delete this._list; + + Class.fn.destructor.call(this); + } +}); + +var ListIterator = Class.inherit({ + "className": "ListIterator", + "constructor": function(node) { + Class.fn.constructor.call(this); + + this._node = node; + }, + "++": function() { + if (this._node._next === null) { + throw new Error("An attempt to increment iterator, pointing at the end of container"); + } + this._node = this._node._next; + }, + "--": function() { + if (this._node._prev === null) { + throw new Error("An attempt to decrement iterator, pointing at the beginning of container"); + } + this._node = this._node._prev; + }, + "*": function() { + if (this._node._data === null) { + throw new Error("An attempt to dereference iterator, pointing at the end of container"); + } + return this._node._data; + }, + "==": function(other) { + return this._node === other._node; + }, + "clone": function() { + return new ListIterator(this._node); + } +}); + +AbstractList.dataType = undefined; +AbstractList.iterator = ListIterator; + +AbstractList.template = function(data) { + + if (!(data instanceof Function)) { + throw new Error("Wrong argument to template a list"); + } + + var List = AbstractList.inherit({ + "className": "List", + "constructor": function(owning) { + AbstractList.fn.constructor.call(this, owning); + } + }); + + List.dataType = data; + + return List; +}; + +module.exports = AbstractList; diff --git a/libjs/wContainer/abstractmap.js b/libjs/wContainer/abstractmap.js new file mode 100644 index 0000000..97efc23 --- /dev/null +++ b/libjs/wContainer/abstractmap.js @@ -0,0 +1,142 @@ +"use strict"; +var Class = require("../utils/class"); +var RBTree = require("bintrees").RBTree; +var AbstractPair = require("./abstractpair"); + +var AbstractMap = Class.inherit({ + "className": "AbstractMap", + "constructor": function(owning) { + Class.fn.constructor.call(this); + + this._owning = owning !== false; + this._data = new RBTree(function (a, b) { + if (a[">"](b)) { + return 1; + } + if (a["<"](b)) { + return -1 + } + if (a["=="](b)) { + return 0; + } + }); + }, + "destructor": function() { + this.clear(); + + Class.fn.destructor.call(this); + }, + "begin": function() { + var itr = this._data.iterator(); + if (itr.next() !== null) { + return new Iterator(itr); + } + return this.end(); + }, + "clear": function() { + if (this._owning) { + this._data.each(function(data) { + data.destructor(); + }); + } + + this._data.clear(); + }, + "each": function(funct) { + this._data.each(funct); + }, + "end": function() { + return new Iterator(this._data.iterator()); + }, + "erase": function(itr) { + var pair = itr["*"](); + if (!this._data.remove(pair)) { + throw new Error("An attempt to remove non-existing map element"); + } + if (this._owning) { + pair.destructor(); + } + }, + "find": function(key) { + var pair = new this.constructor.dataType(key); + + var iter = this._data.findIter(pair); + if (iter === null) { + return this.end(); + } + return new Iterator(iter); + }, + "insert": function(key, value) { + var pair = new this.constructor.dataType(key, value); + + if (!this._data.insert(pair)) { + throw new Error("An attempt to insert already existing element into a map"); + } + }, + "r_each": function(funct) { + this._data.reach(funct); + }, + "size": function() { + return this._data.size; + }, + "lowerBound": function(key) { + var pair = new this.constructor.dataType(key); + + return new Iterator(this._data.lowerBound(pair)); + }, + "upperBound": function(key) { + var pair = new this.constructor.dataType(key); + + return new Iterator(this._data.upperBound(pair)); + } +}); + +var Iterator = Class.inherit({ + "className": "MapIterator", + "constructor": function(rbtree_iterator) { + Class.fn.constructor.call(this); + + this._itr = rbtree_iterator; + }, + "++": function() { + if ((this._itr._cursor === null)) { + throw new Error("An attempt to increment an iterator pointing to the end of the map"); + } + this._itr.next(); + }, + "--": function() { + this._itr.prev(); + if ((this._itr._cursor === null)) { + throw new Error("An attempt to decrement an iterator pointing to the beginning of the map"); + } + }, + "==": function(other) { + return this._itr.data() === other._itr.data(); + }, + "*": function() { + if ((this._itr._cursor === null)) { + throw new Error("An attempt to dereference an iterator pointing to the end of the map"); + } + return this._itr.data(); + } +}); + +AbstractMap.dataType = undefined; +AbstractMap.iterator = Iterator; + +AbstractMap.template = function(first, second) { + var dt = AbstractPair.template(first, second); + + var Map = AbstractMap.inherit({ + "className": "Map", + "constructor": function(owning) { + AbstractMap.fn.constructor.call(this, owning); + } + }); + + Map.dataType = dt; + + return Map; +}; + +module.exports = AbstractMap; diff --git a/libjs/wContainer/abstractorder.js b/libjs/wContainer/abstractorder.js new file mode 100644 index 0000000..d52651e --- /dev/null +++ b/libjs/wContainer/abstractorder.js @@ -0,0 +1,104 @@ +"use strict"; +var Class = require("../utils/class"); +var AbstractMap = require("./abstractmap"); +var AbstractList = require("./abstractlist"); + +var AbstractOrder = Class.inherit({ + "className": "AbstractOrder", + "constructor": function(owning) { + Class.fn.constructor.call(this); + + if (!this.constructor.dataType) { + throw new Error("An attempt to instantiate an order without declared member type"); + } + + var Map = AbstractMap.template(this.constructor.dataType, this.constructor.iterator); + var List = AbstractList.template(this.constructor.dataType); + + this._owning = owning !== false; + + this._rmap = new Map(this._owning); + this._order = new List(false); + }, + "destructor": function() { + this._rmap.destructor(); + this._order.destructor(); + + Class.fn.destructor.call(this); + }, + "begin": function() { + return this._order.begin(); + }, + "clear": function() { + this._rmap.clear(); + this._order.clear(); + }, + "end": function() { + return this._order.end(); + }, + "erase": function(element) { + var itr = this._rmap.find(element); + + if (itr["=="](this._rmap.end())) { + throw new Error("An attempt to erase non existing element from an order"); + } + + var pair = itr["*"](); + + this._order.erase(pair.second); + this._rmap.erase(itr); + }, + "find": function(data) { + var itr = this._rmap.find(data); + + if (itr["=="](this._rmap.end())) { + return this.end(); + } + return itr["*"]().second; + }, + "insert": function(data, target) { + var itr = this._rmap.find(target); + + if (itr["=="](this._rmap.end())) { + throw new Error("An attempt to insert element before the non existing one"); + } + + var pair = itr["*"](); + this._order.insert(data, pair.second); + + var pointer = pair.second.clone()["--"](); + this._rmap.insert(data, pointer); + }, + "push_back": function(data) { + this._order.push_back(data); + var itr = this._order.end(); + itr["--"](); + + this._rmap.insert(data, itr); + }, + "size": function() { + return this._order.size(); + } +}); + +AbstractOrder.dataType = undefined; +AbstractOrder.iterator = AbstractList.iterator; + +AbstractOrder.template = function(data) { + if (!(data instanceof Function)) { + throw new Error("Wrong argument to template an order"); + } + + var Order = AbstractOrder.inherit({ + "className": "Order", + "constructor": function(owning) { + AbstractOrder.fn.constructor.call(this, owning); + } + }); + + Order.dataType = data; + + return Order; +} + +module.exports = AbstractOrder diff --git a/libjs/wContainer/abstractpair.js b/libjs/wContainer/abstractpair.js new file mode 100644 index 0000000..3042a99 --- /dev/null +++ b/libjs/wContainer/abstractpair.js @@ -0,0 +1,111 @@ +"use strict"; +var Class = require("../utils/class"); + +var AbstractPair = Class.inherit({ + "className": "AbstractPair", + "constructor": function(first, second) { + Class.fn.constructor.call(this); + + if (!this.constructor.firstType || !this.constructor.secondType) { + throw new Error("An attempt to instantiate a pair without declared member types"); + } + if (!(first instanceof this.constructor.firstType)) { + throw new Error("An attempt to construct a pair from wrong arguments"); + } + + this.first = first; + this.second = second; + }, + "destructor": function() { + this.first.destructor(); + if (this.second) { + this.second.destructor(); + } + + Class.fn.destructor.call(this); + }, + "<": function(other) { + if (!(other instanceof this.constructor)) { + throw new Error("Can't compare pairs with different content types"); + } + if (this.constructor.complete) { + if (this.first["<"](other.first)) { + return true; + } else if(this.first["=="](other.first)) { + this.second["<"](other.second); + } else { + return false; + } + } else { + return this.first["<"](other.first); + } + }, + ">": function(other) { + if (!(other instanceof this.constructor)) { + throw new Error("Can't compare pairs with different content types"); + } + if (this.constructor.complete) { + if (this.first[">"](other.first)) { + return true; + } else if(this.first["=="](other.first)) { + this.second[">"](other.second); + } else { + return false; + } + } else { + return this.first[">"](other.first); + } + }, + "==": function(other) { + if (!(other instanceof this.constructor)) { + throw new Error("Can't compare pairs with different content types"); + } + if (this.constructor.complete) { + return this.first["=="](other.first) && this.second["=="](other.second); + } else { + return this.first["=="](other.first); + } + } +}); + +AbstractPair.firstType = undefined; +AbstractPair.secondType = undefined; +AbstractPair.complete = false; + +AbstractPair.template = function(first, second, complete) { + if (!(first instanceof Function) || !(second instanceof Function)) { + throw new Error("An attempt to create template pair from wrong arguments"); + } + if ( + !(first.prototype["<"] instanceof Function) || + !(first.prototype[">"] instanceof Function) || + !(first.prototype["=="] instanceof Function) + ) + { + throw new Error("Not acceptable first type"); + } + if ( + complete && + ( + !(second.prototype["<"] instanceof Function) || + !(second.prototype[">"] instanceof Function) || + !(second.prototype["=="] instanceof Function) + ) + ) + { + throw new Error("Not acceptable second type"); + } + var Pair = AbstractPair.inherit({ + "className": "Pair", + "constructor": function(first, second) { + AbstractPair.fn.constructor.call(this, first, second); + } + }); + Pair.firstType = first; + Pair.secondType = second; + Pair.complete = complete === true; + + return Pair; +}; + +module.exports = AbstractPair; \ No newline at end of file diff --git a/libjs/wContainer/abstractset.js b/libjs/wContainer/abstractset.js new file mode 100644 index 0000000..f483dcd --- /dev/null +++ b/libjs/wContainer/abstractset.js @@ -0,0 +1,143 @@ +"use strict"; +var Class = require("../utils/class"); +var RBTree = require("bintrees").RBTree; + +var AbstractSet = Class.inherit({ + "className": "AbstractSet", + "constructor": function(owning) { + Class.fn.constructor.call(this); + + this._owning = owning !== false; + this._data = new RBTree(function (a, b) { + if (a[">"](b)) { + return 1; + } + if (a["<"](b)) { + return -1 + } + if (a["=="](b)) { + return 0; + } + }); + }, + "destructor": function() { + this.clear(); + + Class.fn.destructor.call(this); + }, + "begin": function() { + var itr = this._data.iterator(); + if (itr.next() !== null) { + return new Iterator(itr); + } + return this.end(); + }, + "clear": function() { + if (this._owning) { + this._data.each(function(data) { + data.destructor(); + }); + } + + this._data.clear(); + }, + "each": function(funct) { + this._data.each(funct); + }, + "end": function() { + return new Iterator(this._data.iterator()); + }, + "erase": function(itr) { + var value = itr["*"](); + if (!this._data.remove(value)) { + throw new Error("An attempt to remove non-existing set element"); + } + if (this._owning) { + value.destructor(); + } + }, + "find": function(key) { + if (!(key instanceof this.constructor.dataType)) { + throw new Error("An attempt to find wrong value type in the set"); + } + var iter = this._data.findIter(key); + if (iter === null) { + return this.end(); + } + return new Iterator(iter); + }, + "insert": function(value) { + if (!(value instanceof this.constructor.dataType)) { + throw new Error("An attempt to insert wrong value type to set"); + } + if (!this._data.insert(value)) { + throw new Error("An attempt to insert already existing element into a set"); + } + }, + "r_each": function(funct) { + this._data.reach(funct); + }, + "size": function() { + return this._data.size; + }, + "lowerBound": function(key) { + if (!(key instanceof this.constructor.dataType)) { + throw new Error("An attempt to find wrong value type in the set"); + } + return new Iterator(this._data.lowerBound(key)); + }, + "upperBound": function(key) { + if (!(key instanceof this.constructor.dataType)) { + throw new Error("An attempt to find wrong value type in the set"); + } + return new Iterator(this._data.upperBound(key)); + } +}); + +var Iterator = Class.inherit({ + "className": "SetIterator", + "constructor": function(rbtree_iterator) { + Class.fn.constructor.call(this); + + this._itr = rbtree_iterator; + }, + "++": function() { + if ((this._itr._cursor === null)) { + throw new Error("An attempt to increment an iterator pointing to the end of the set"); + } + this._itr.next(); + }, + "--": function() { + this._itr.prev(); + if ((this._itr._cursor === null)) { + throw new Error("An attempt to decrement an iterator pointing to the beginning of the set"); + } + }, + "==": function(other) { + return this._itr.data() === other._itr.data(); + }, + "*": function() { + if ((this._itr._cursor === null)) { + throw new Error("An attempt to dereference an iterator pointing to the end of the set"); + } + return this._itr.data(); + } +}); + +AbstractSet.dataType = undefined; +AbstractSet.iterator = Iterator; + +AbstractSet.template = function(type) { + var Set = AbstractSet.inherit({ + "className": "Set", + "constructor": function(owning) { + AbstractSet.fn.constructor.call(this, owning); + } + }); + + Set.dataType = type; + + return Set; +}; + +module.exports = AbstractSet; diff --git a/libjs/wController/CMakeLists.txt b/libjs/wController/CMakeLists.txt new file mode 100644 index 0000000..96b4f71 --- /dev/null +++ b/libjs/wController/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(controller.js controller.js) +configure_file(globalControls.js globalControls.js) +configure_file(link.js link.js) +configure_file(list.js list.js) +configure_file(navigationPanel.js navigationPanel.js) +configure_file(page.js page.js) +configure_file(pageStorage.js pageStorage.js) +configure_file(panesList.js panesList.js) +configure_file(string.js string.js) +configure_file(theme.js theme.js) +configure_file(themeSelecter.js themeSelecter.js) +configure_file(vocabulary.js vocabulary.js) +configure_file(attributes.js attributes.js) +configure_file(localModel.js localModel.js) +configure_file(catalogue.js catalogue.js) +configure_file(imagePane.js imagePane.js) +configure_file(file/file.js file/file.js) diff --git a/libjs/wController/attributes.js b/libjs/wController/attributes.js new file mode 100644 index 0000000..3918c74 --- /dev/null +++ b/libjs/wController/attributes.js @@ -0,0 +1 @@ +"use strict"; diff --git a/libjs/wController/catalogue.js b/libjs/wController/catalogue.js new file mode 100644 index 0000000..32486dd --- /dev/null +++ b/libjs/wController/catalogue.js @@ -0,0 +1,141 @@ +"use strict"; + +var Controller = require("./controller"); +var AbstractOrder = require("../wContainer/abstractorder"); + +var Uint64 = require("../wType/uint64"); +var Vocabulary = require("../wType/vocabulary"); +var Boolean = require("../wType/boolean"); +var String = require("../wType/string"); + +var IdOrder = AbstractOrder.template(Uint64); + +var Catalogue = Controller.inherit({ + "className": "Catalogue", + "constructor": function(addr, options) { + Controller.fn.constructor.call(this, addr); + + this._hasSorting = false; + this._hasFilter = false; + this._filter = undefined; + this.data = new IdOrder(true); + + if (options.sorting) { + this._hasSorting = true; + this._sorting = new Vocabulary(); + this._sorting.insert("ascending", new Boolean(options.sorting.ascending)); + this._sorting.insert("field", new String(options.sorting.field)); + } + if (options.filter) { + var filter = new Vocabulary(); + for (var key in options.filter) { + if (options.filter.hasOwnProperty(key)) { + filter.insert(key, options.filter[key]); + } + } + if (filter.length()) { + this._filter = filter; + this._hasFilter = true; + } else { + filter.destructor(); + } + } + + this.addHandler("get"); + this.addHandler("addElement"); + this.addHandler("removeElement"); + this.addHandler("moveElement"); + this.addHandler("clear"); + }, + "destructor": function() { + this.data.destructor(); + + if (this._hasSorting) { + this._sorting.destructor(); + } + if (this._hasFilter) { + this._filter.destructor(); + } + + Controller.fn.destructor.call(this); + }, + "addElement": function(element, before) { + if (before === undefined) { + this.data.push_back(element); + } else { + this.data.insert(element, before); + } + + this.trigger("addElement", element, before); + }, + "clear": function() { + this.data.clear(); + this.trigger("clear"); + }, + "_createSubscriptionVC": function() { + var vc = Controller.fn._createSubscriptionVC.call(this); + var p = new Vocabulary(); + + if (this._hasSorting) { + p.insert("sorting", this._sorting.clone()); + } + if (this._hasFilter) { + p.insert("filter", this._filter.clone()); + } + + vc.insert("params", p); + return vc; + }, + "_h_addElement": function(ev) { + var data = ev.getData(); + var id = data.at("id").clone(); + var before = undefined; + if (data.has("before")) { + before = data.at("before").clone(); + } + + this.addElement(id, before); + }, + "_h_get": function(ev) { + this.clear(); + + var data = ev.getData().at("data"); + var size = data.length(); + for (var i = 0; i < size; ++i) { + this.addElement(data.at(i).clone()); + } + this.initialized = true; + this.trigger("data"); + }, + "_h_moveElement": function(ev) { + var data = ev.getData(); + var id = data.at("id").clone(); + var before = undefined; + if (data.has("before")) { + before = data.at("before").clone(); + } + + this.data.erase(id); + if (before === undefined) { + this.data.push_back(element); + } else { + this.data.insert(element, before); + } + + this.trigger("moveElement", id, before); + }, + "_h_removeElement": function(ev) { + var data = ev.getData(); + + this.removeElement(data.at("id").clone()); + }, + "_h_clear": function(ev) { + this.clear(); + }, + "removeElement": function(id) { + this.data.erase(id); + this.trigger("removeElement", id); + } +}); + +module.exports = Catalogue; diff --git a/libjs/wController/controller.js b/libjs/wController/controller.js new file mode 100644 index 0000000..ece32b7 --- /dev/null +++ b/libjs/wController/controller.js @@ -0,0 +1,382 @@ +"use strict"; +var counter = 0; +var Subscribable = require("../utils/subscribable"); +var Handler = require("../wDispatcher/handler"); +var Address = require("../wType/address"); +var String = require("../wType/string"); +var Vocabulary = require("../wType/vocabulary"); +var Event = require("../wType/event"); +var counter = 0; + +var Controller = Subscribable.inherit({ + "className": "Controller", + "constructor": function(addr) { + Subscribable.fn.constructor.call(this); + + this.id = ++counter; + this.initialized = false; + + this._subscribed = false; + this._registered = false; + this._pairAddress = addr; + this._address = new Address([this.className.toString() + counter++]); + + this._handlers = []; + this._controllers = []; + this.properties = []; + this._foreignControllers = []; + + this.addHandler("properties"); + }, + "destructor": function() { + var i; + + if (this._subscribed) { + this.unsubscribe(); + } + if (this._registered) { + this.unregister(); + } + + for (i = 0; i < this._foreignControllers.length; ++i) { + this._foreignControllers[i].c.destructor(); + } + + for (i = 0; i < this._controllers.length; ++i) { + this._controllers[i].destructor(); + } + + for (i = 0; i < this._handlers.length; ++i) { + this._handlers[i].destructor(); + } + this._pairAddress.destructor(); + this._address.destructor(); + + Subscribable.fn.destructor.call(this); + }, + "addController": function(controller, before) { + if (!(controller instanceof Controller)) { + throw new Error("An attempt to add not a controller into " + this.className); + } + controller.on("serviceMessage", this._onControllerServiceMessage, this); + var index = this._controllers.length; + if (before === undefined) { + this._controllers.push(controller); + } else { + index = before; + this._controllers.splice(before, 0, controller); + } + if (this._registered) { + controller.register(this._dp, this._socket); + } + if (this._subscribed && !controller._subscribed) { + this._subscribeChildController(index); + } + this.trigger("newController", controller, index); + }, + "addForeignController": function(nodeName, ctrl) { + if (!(ctrl instanceof Controller)) { + throw new Error("An attempt to add not a controller into " + this.className); + } + + this._foreignControllers.push({n: nodeName, c: ctrl}); + ctrl.on("serviceMessage", this._onControllerServiceMessage, this); + + if (this._registered) { + global.registerForeignController(nodeName, ctrl); + } + + if (this._subscribed) { + global.subscribeForeignController(nodeName, ctrl); + } + }, + "addHandler": function(name) { + if (!(this["_h_" + name] instanceof Function)) { + throw new Error("An attempt to create handler without a handling method"); + } + + var handler = new Handler(this._address["+"](new Address([name])), this, this["_h_" + name]); + + this._handlers.push(handler); + if (this._registered) { + this._dp.registerHandler(handler); + } + }, + "clearChildren": function() { + while (this._controllers.length) { + var controller = this._controllers[this._controllers.length - 1] + this._removeController(controller); + controller.destructor(); + } + }, + "_createSubscriptionVC": function() { + return new Vocabulary(); + }, + "getPairAddress": function() { + return this._pairAddress.clone(); + }, + "_h_properties": function(ev) { + this.trigger("clearProperties"); + this.properties = []; + var data = ev.getData(); + var list = data.at("properties"); + var size = list.length(); + for (var i = 0; i < size; ++i) { + var vc = list.at(i); + var pair = {p: vc.at("property").toString(), k: vc.at("key").toString()}; + this.properties.push(pair); + this.trigger("addProperty", pair.k, pair.p); + } + }, + "_onControllerServiceMessage": function(msg, severity) { + this.trigger("serviceMessage", msg, severity); + }, + "_onSocketDisconnected": function() { + this._subscribed = false; + }, + "register": function(dp, socket) { + var i; + if (this._registered) { + throw new Error("Controller " + this._address.toString() + " is already registered"); + } + this._dp = dp; + this._socket = socket; + socket.on("disconnected", this._onSocketDisconnected, this); + + for (i = 0; i < this._controllers.length; ++i) { + this._controllers[i].register(this._dp, this._socket); + } + + for (i = 0; i < this._handlers.length; ++i) { + dp.registerHandler(this._handlers[i]); + } + + for (i = 0; i < this._foreignControllers.length; ++i) { + var pair = this._foreignControllers[i] + global.registerForeignController(pair.n, pair.c); + } + + this._registered = true; + }, + "removeController": function(ctrl) { + if (!(ctrl instanceof Controller)) { + throw new Error("An attempt to remove not a controller from " + this.className); + } + var index = this._controllers.indexOf(ctrl); + if (index !== -1) { + this._removeControllerByIndex(index); + } else { + throw new Error("An attempt to remove not not existing controller from " + this.className); + } + }, + "removeForeignController": function(ctrl) { + if (!(ctrl instanceof Controller)) { + throw new Error("An attempt to remove not a controller from " + this.className); + } + for (var i = 0; i < this._foreignControllers.length; ++i) { + if (this._foreignControllers[i].c === ctrl) { + break; + } + } + + if (i === this._foreignControllers.length) { + throw new Error("An attempt to remove not not existing controller from " + this.className); + } else { + var pair = this._foreignControllers[i]; + if (this._subscribed) { + global.unsubscribeForeignController(pair.n, pair.c); + } + + if (this._registered) { + global.registerForeignController(pair.n, pair.c); + } + + pair.c.off("serviceMessage", this._onControllerServiceMessage, this); + + this._foreignControllers.splice(i, 1); + } + }, + "_removeControllerByIndex": function(index) { + var ctrl = this._controllers[index]; + if (this._subscribed) { + this._unsubscribeChildController(index); + } + if (this._dp) { + ctrl.unregister(); + } + this._controllers.splice(index, 1); + + this.trigger("removedController", ctrl, index); + }, + "send": function(vc, handler) { + if (!this._registered) { + throw new Error("An attempt to send event from model " + this._address.toString() + " which is not registered"); + } + var addr = this._pairAddress["+"](new Address([handler])); + var id = this._socket.getId().clone(); + + vc.insert("source", this._address.clone()); + + var ev = new Event(addr, vc); + ev.setSenderId(id); + this._socket.send(ev); + ev.destructor(); + }, + "subscribe": function() { + if (this._subscribed) { + throw new Error("An attempt to subscribe model " + this._address.toString() + " which is already subscribed"); + } + this._subscribed = true; + + var vc = this._createSubscriptionVC(); + this.send(vc, "subscribe"); + + for (var i = 0; i < this._controllers.length; ++i) { + this._subscribeChildController(i) + } + + for (var i = 0; i < this._foreignControllers.length; ++i) { + var pair = this._foreignControllers[i] + global.subscribeForeignController(pair.n, pair.c); + } + }, + "_subscribeChildController": function(index) { + var ctrl = this._controllers[index]; + ctrl.subscribe(); + }, + "unregister": function() { + if (!this._registered) { + throw new Error("Controller " + this._address.toString() + " is not registered"); + } + var i; + + for (i = 0; i < this._foreignControllers.length; ++i) { + var pair = this._foreignControllers[i] + global.unregisterForeignController(pair.n, pair.c); + } + + for (i = 0; i < this._controllers.length; ++i) { + this._controllers[i].unregister(); + } + for (i = 0; i < this._handlers.length; ++i) { + this._dp.unregisterHandler(this._handlers[i]); + } + this._socket.off("disconnected", this._onSocketDisconnected, this); + delete this._dp; + delete this._socket; + + this._registered = false; + }, + "unsubscribe": function() { + if (!this._subscribed) { + throw new Error("An attempt to unsubscribe model " + this._address.toString() + " which is not subscribed"); + } + this._subscribed = false; + + if (this._socket.isOpened()) { + var vc = new Vocabulary(); + this.send(vc, "unsubscribe"); + } + + for (var i = 0; i < this._foreignControllers.length; ++i) { + var pair = this._foreignControllers[i] + global.unsubscribeForeignController(pair.n, pair.c); + } + + for (var i = 0; i < this._controllers.length; ++i) { + this._unsubscribeChildController(i); + } + }, + "_unsubscribeChildController": function(index) { + var ctrl = this._controllers[index]; + ctrl.unsubscribe(); + } +}); + +Controller.createByType = function(type, address) { + var typeName = this.ReversedModelType[type]; + if (typeName === undefined) { + throw new Error("Unknown ModelType: " + type); + } + var Type = this.constructors[typeName]; + if (Type === undefined) { + throw new Error("Constructor is not loaded yet, something is wrong"); + } + return new Type(address); +} + +Controller.initialize = function(rc, cb) { + var deps = []; + var types = []; + for (var key in this.ModelTypesPaths) { + if (this.ModelTypesPaths.hasOwnProperty(key)) { + if (!rc || rc.indexOf(key) !== -1) { + deps.push(this.ModelTypesPaths[key]); + types.push(key); + } + } + } + require(deps, function() { + for (var i = 0; i < types.length; ++i) { + Controller.constructors[types[i]] = arguments[i]; + } + cb(); + }); +} + +Controller.ModelType = { + String: 0, + List: 1, + Vocabulary: 2, + Image: 3, + Controller: 4, + + Attributes: 50, + + GlobalControls: 100, + Link: 101, + Page: 102, + PageStorage: 103, + PanesList: 104, + Theme: 105, + ThemeStorage: 106 +}; + +Controller.ReversedModelType = { + "0": "String", + "1": "List", + "2": "Vocabulary", + "3": "Image", + "4": "Controller", + + "50": "Attributes", + + "100": "GlobalControls", + "101": "Link", + "102": "Page", + "103": "PageStorage", + "104": "PanesList", + "105": "Theme", + "106": "ThemeStorage" +}; + +Controller.ModelTypesPaths = { + String: "./string", //resolve as dependency + List: "./list", //resolve as dependency + Vocabulary: "./vocabulary", //resolve as dependency + Attributes: "./attributes", //resolve as dependency + GlobalControls: "./globalControls", //resolve as dependency + Link: "./link", //resolve as dependency + Page: "./page", //resolve as dependency + PageStorage: "./pageStorage", //resolve as dependency + PanesList: "./panesList", //resolve as dependency + Theme: "./theme", //resolve as dependency + ThemeStorage: "./themeStorage", //resolve as dependency + Image: "./image" //resolve as dependency +}; + +Controller.constructors = { + Controller: Controller +}; + +module.exports = Controller; diff --git a/libjs/wController/file/file.js b/libjs/wController/file/file.js new file mode 100644 index 0000000..c922e4e --- /dev/null +++ b/libjs/wController/file/file.js @@ -0,0 +1,86 @@ +"use strict"; + +var Controller = require("../controller"); +var WVocabulary = require("../../wType/vocabulary"); + +var File = Controller.inherit({ + "className": "File", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this._hasData = false; + this._hasAdditional = false; + this.data = null; + this._additional = null; + this._need = 0; + + this.addHandler("get"); + this.addHandler("getAdditional"); + }, + "destructor": function() { + if (this._hasData) { + this.data.destructor(); + } + if (this._hasAdditional) { + this._additional.destructor(); + } + Controller.fn.destructor.call(this); + }, + "dontNeedData": function() { + --this._need; + }, + "hasData": function() { + return this._hasData + }, + "_getAdditional": function(add) { + var ac = !this._hasAdditional || !this._additional["=="](add); + if (ac) { + if (this._hasAdditional) { + this._additional.destructor(); + } + this._additional = add.clone(); + } + this._hasAdditional = true; + return ac; + }, + "getMimeType": function() { + return this._additional.at("mimeType").toString(); + }, + "_h_get": function(ev) { + var dt = ev.getData(); + + var ac = this._getAdditional(dt.at("additional")); + if (ac) { + this.trigger("additionalChange") + } + + this._hasData = true; + this.data = dt.at("data").clone(); + this.trigger("data"); + }, + "_h_getAdditional": function(ev) { + var ac = this._getAdditional(ev.getData()); + if (ac) { + this.trigger("additionalChange"); + } + }, + "needData": function() { + if (this._need === 0) { + var vc = new WVocabulary(); + + this.send(vc, "get"); + } + ++this._need; + }, + "subscribe": function() { + Controller.fn.subscribe.call(this); + + if (this._need > 0) { + var vc = new WVocabulary(); + + this.send(vc, "get"); + } + } +}); + +module.exports = File; diff --git a/libjs/wController/globalControls.js b/libjs/wController/globalControls.js new file mode 100644 index 0000000..319d7ba --- /dev/null +++ b/libjs/wController/globalControls.js @@ -0,0 +1,60 @@ +"use strict"; + +var List = require("./list"); +var String = require("./string"); +var NavigationPanel = require("./navigationPanel"); +var ThemeSelecter = require("./themeSelecter"); +var Theme = require("./theme"); + +var GlobalControls = List.inherit({ + "className": "GlobalControls", + "constructor": function(address) { + List.fn.constructor.call(this, address); + }, + "addElement": function(vc) { + List.fn.addElement.call(this, vc); + + var name = vc.at("name").toString(); + var type = vc.at("type").toString(); + var addr = vc.at("address"); + var ctrl; + var supported = true; + + switch (name) { + case "version": + ctrl = new String(addr.clone()); + break; + case "navigationPanel": + ctrl = new NavigationPanel(addr.clone()); + break; + case "themes": + ctrl = new ThemeSelecter(addr.clone()); + ctrl.on("selected", this._onThemeSelected, this); + break; + default: + supported = false; + this.trigger("serviceMessage", "Unsupported global control: " + name + " (" + type + ")", 1); + break; + } + if (supported) { + ctrl.name = name; + this.addController(ctrl); + } + }, + "clear": function() { + List.fn.clear.call(this); + this.clearChildren(); + }, + "_onThemeReady": function(theme) { + this.trigger("themeSelected", theme._data); + this.removeController(theme); + theme.destructor(); + }, + "_onThemeSelected": function(obj) { + var theme = new Theme(obj.value.clone()); + this.addController(theme); + theme.on("ready", this._onThemeReady.bind(this, theme)); + } +}); + +module.exports = GlobalControls; diff --git a/libjs/wController/image.js b/libjs/wController/image.js new file mode 100644 index 0000000..581e460 --- /dev/null +++ b/libjs/wController/image.js @@ -0,0 +1,69 @@ +"use strict"; + +var Address = require("../wType/address"); + +var Controller = require("./controller"); +var File = require("./file/file"); + +var Image = Controller.inherit({ + "className": "Image", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this.data = undefined; + this._hasCtrl = false; + this._fileCtrl = undefined; + this._need = 0; + + this.addHandler("get"); + }, + "dontNeedData": function() { + --this._need; + }, + "getMimeType": function () { + return this._fileCtrl.getMimeType(); + }, + "hasData": function() { + if (this._hasCtrl) { + return this._fileCtrl.hasData(); + } + return false; + }, + "_h_get": function(ev) { + var data = ev.getData(); + + if (this._hasCtrl) { + this.removeForeignController(this._fileCtrl); + this._fileCtrl.destructor(); + delete this._fileCtrl; + this._hasCtrl = false; + } + var strId = data.at("data").toString(); + if (strId !== "0") { + this._fileCtrl = new File(new Address(["images", strId])); + this.addForeignController("Corax", this._fileCtrl); + + this._fileCtrl.on("data", this._onControllerData, this); + + this._hasCtrl = true; + if (this._need > 0) { + this._fileCtrl.needData(); + } + } else { + this.trigger("clear"); + } + }, + "needData": function() { + if (this._need === 0 && this._hasCtrl) { + this._fileCtrl.needData(); + } + ++this._need; + }, + "_onControllerData": function() { + this.data = this._fileCtrl.data; + + this.trigger("data"); + } +}); + +module.exports = Image; diff --git a/libjs/wController/imagePane.js b/libjs/wController/imagePane.js new file mode 100644 index 0000000..077e2c1 --- /dev/null +++ b/libjs/wController/imagePane.js @@ -0,0 +1,40 @@ +"use strict"; + +var Address = require("../wType/address"); + +var Vocabulary = require("./vocabulary"); +var File = require("./file/file"); + +var ImagePane = Vocabulary.inherit({ + "className": "ImagePane", + "constructor": function(addr) { + Vocabulary.fn.constructor.call(this, addr); + + this._hasImage = false; + this.image = null; + }, + "addElement": function(key, element) { + if (key === "image" && !this._hasImage) { + this._hasImage = true; + this.image = new File(new Address(["images", element.toString()])); + this.addForeignController("Corax", this.image); + } + Vocabulary.fn.addElement.call(this, key, element); + }, + "hasImage": function() { + return this._hasImage; + }, + "removeElement": function(key) { + Vocabulary.fn.removeElement.call(this, key); + + if (key === "image" && this._hasImage) { + this.removeForeignController(this.image); + + this._hasImage = false; + this.image.destructor(); + this.image = null; + } + } +}); + +module.exports = ImagePane; diff --git a/libjs/wController/link.js b/libjs/wController/link.js new file mode 100644 index 0000000..1ba0ee2 --- /dev/null +++ b/libjs/wController/link.js @@ -0,0 +1,38 @@ +"use strict"; + +var Controller = require("./controller"); +var String = require("./string"); + +var Address = require("../wType/address"); + +var Link = Controller.inherit({ + "className": "Link", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + var hop = new Address(["label"]); + + this.targetAddress = new Address([]); + this.label = new String(addr['+'](hop)); + + this.addController(this.label); + + this.addHandler("get"); + + hop.destructor(); + }, + "destructor": function() { + this.targetAddress.destructor(); + + Controller.fn.destructor.call(this); + }, + "_h_get": function(ev) { + var data = ev.getData(); + + this.targetAddress = data.at("targetAddress").clone(); + + this.trigger("data", this.targetAddress); + } +}); + +module.exports = Link; diff --git a/libjs/wController/list.js b/libjs/wController/list.js new file mode 100644 index 0000000..cd58122 --- /dev/null +++ b/libjs/wController/list.js @@ -0,0 +1,51 @@ +"use strict"; +var Controller = require("./controller"); +var Vector = require("../wType/vector"); + +var List = Controller.inherit({ + "className": "List", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this.data = new Vector(); + + this.addHandler("get"); + this.addHandler("push"); + this.addHandler("clear"); + }, + "destructor": function() { + this.data.destructor(); + + Controller.fn.destructor.call(this); + }, + "addElement": function(element) { + this.data.push(element); + this.trigger("newElement", element); + }, + "clear": function() { + this.data.clear(); + this.trigger("clear"); + }, + "_h_clear": function(ev) { + this.clear(); + }, + "_h_get": function(ev) { + this.clear(); + + var data = ev.getData().at("data"); + var size = data.length(); + for (var i = 0; i < size; ++i) { + this.addElement(data.at(i).clone()); + } + this.initialized = true; + this.trigger("data"); + }, + "_h_push": function(ev) { + var data = ev.getData(); + + var element = data.at("data").clone(); + this.addElement(element); + } +}); + +module.exports = List; diff --git a/libjs/wController/localModel.js b/libjs/wController/localModel.js new file mode 100644 index 0000000..8b1b786 --- /dev/null +++ b/libjs/wController/localModel.js @@ -0,0 +1,28 @@ +"use strict"; +var counter = 0; +var Subscribable = require("../utils/subscribable"); + +var LocalModel = Subscribable.inherit({ + "className": "LocalModel", + "constructor": function(properties) { + Subscribable.fn.constructor.call(this); + + this.properties = []; + this._controllers = []; + + if (properties) { + for (var key in properties) { + if (properties.hasOwnProperty(key)) { + var pair = {p: key, k: properties[key]}; + this.properties.push(pair); + } + } + } + }, + "setData": function(data) { + this.data = data; + this.trigger("data"); + } +}); + +module.exports = LocalModel; diff --git a/libjs/wController/navigationPanel.js b/libjs/wController/navigationPanel.js new file mode 100644 index 0000000..ac7d30b --- /dev/null +++ b/libjs/wController/navigationPanel.js @@ -0,0 +1,24 @@ +"use strict"; +var List = require("./list"); +var Link = require("./link"); + +var NavigationPanel = List.inherit({ + "className": "NavigationPanel", + "constructor": function(addr) { + List.fn.constructor.call(this, addr); + }, + "addElement": function(element) { + var address = element.at("address").clone(); + + var ctrl = new Link(address); + this.addController(ctrl); + + List.fn.addElement.call(this, element); + }, + "clear": function() { + List.fn.clear.call(this); + this.clearChildren(); + } +}); + +module.exports = NavigationPanel; diff --git a/libjs/wController/page.js b/libjs/wController/page.js new file mode 100644 index 0000000..04571a1 --- /dev/null +++ b/libjs/wController/page.js @@ -0,0 +1,120 @@ +"use strict"; + +var Controller = require("./controller"); +var String = require("./string"); + +var Vocabulary = require("../wType/vocabulary"); +var Address = require("../wType/address"); + +var AbstractMap = require("../wContainer/abstractmap"); +var ContentMap = AbstractMap.template(Address, Object); + +var Page = Controller.inherit({ + "className": "Page", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this.data = new ContentMap(false); + + this.addHandler("get"); + this.addHandler("addItem"); + this.addHandler("removeItem"); + this.addHandler("clear"); + + this.elements = []; + }, + "destructor": function() { + this.data.destructor(); + + Controller.fn.destructor.call(this); + }, + "addItem": function(element) { + var type = element.at("type").valueOf(); + var address = element.at("address").clone(); + var col = element.at("col").valueOf(); + var row = element.at("row").valueOf(); + var colspan = element.at("colspan").valueOf(); + var rowspan = element.at("rowspan").valueOf(); + var aligment = element.at("aligment").valueOf(); + var viewType = element.at("viewType").valueOf(); + var opts = Page.deserializeOptions(element.at("viewOptions")); + + var controller = Page.createByType(type, address); + this.addController(controller); + + var el = { + type: type, + col: col, + row: row, + colspan: colspan, + rowspan: rowspan, + aligment: aligment, + viewType: viewType, + viewOptions: opts, + controller: controller + } + this.data.insert(address, el); + this.trigger("addItem", address, el); + }, + "clear": function() { + this.data.clear(); + this.trigger("clear"); + this.clearChildren(); + }, + "_h_clear": function(ev) { + this.clear(); + }, + "_h_get": function(ev) { + this.clear(); + + var data = ev.getData().at("data"); + var size = data.length(); + for (var i = 0; i < size; ++i) { + this.addItem(data.at(i).clone()); + } + }, + "_h_addItem": function(ev) { + var data = ev.getData().clone(); + + this.addItem(data); + }, + "_h_removeItem": function(ev) { + var data = ev.getData(); + + var address = data.at("address").clone(); + this.removeItem(address); + }, + "removeItem": function(address) { + var itr = this.data.find(address); + var pair = itr["*"](); + this.data.erase(itr); + + this.trigger("removeItem", pair.first); + + this.removeController(pair.second.controller); + pair.second.controller.destructor(); + } +}); + + +Page.deserializeOptions = function(vc) { + var opts = Object.create(null); + var keys = vc.getKeys(); + + for (var i = 0; i < keys.length; ++i) { + var value = vc.at(keys[i]); + + if (value instanceof Vocabulary) { + value = this.deserializeOptions(value); + } else if(value instanceof Address) { //todo vector! + value = value.clone(); + } else { + value = value.valueOf(); + } + opts[keys[i]] = value; + } + + return opts; +} + +module.exports = Page; diff --git a/libjs/wController/pageStorage.js b/libjs/wController/pageStorage.js new file mode 100644 index 0000000..658d685 --- /dev/null +++ b/libjs/wController/pageStorage.js @@ -0,0 +1,38 @@ +"use strict"; +var Controller = require("./controller"); +var Vocabulary = require("../wType/vocabulary"); +var String = require("../wType/string"); + +var PageStorage = Controller.inherit({ + "className": "PageStorage", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this.addHandler("pageAddress"); + this.addHandler("pageName"); + }, + "getPageAddress": function(url) { + var vc = new Vocabulary(); + + vc.insert("url", new String(url)); + this.send(vc, "getPageAddress"); + }, + "getPageName": function(address) { + var vc = new Vocabulary(); + + vc.insert("address", address.clone()); + this.send(vc, "getPageName"); + }, + "_h_pageAddress": function(ev) { + var data = ev.getData(); + + this.trigger("pageAddress", data.at("address").clone()); + }, + "_h_pageName": function(ev) { + var data = ev.getData(); + + this.trigger("pageName", data.at("name").toString()); + } +}); + +module.exports = PageStorage; diff --git a/libjs/wController/panesList.js b/libjs/wController/panesList.js new file mode 100644 index 0000000..00a8213 --- /dev/null +++ b/libjs/wController/panesList.js @@ -0,0 +1,80 @@ +"use strict"; +var List = require("./list"); +var ImagePane = require("./imagePane"); + +var Address = require("../wType/address"); + +var PanesList = List.inherit({ + "className": "PanesList", + "constructor": function PanesListModel(addr) { + List.fn.constructor.call(this, addr); + + this._subscriptionStart = 0; + this._subscriptionEnd = Infinity; + }, + "addElement": function(element) { + var size = this.data.length(); + List.fn.addElement.call(this, element); + + if (size >= this._subscriptionStart && size < this._subscriptionEnd) { + var controller = new ImagePane(this._pairAddress["+"](new Address([element.toString()]))); + this.addController(controller); + } + }, + "clear": function() { + List.fn.clear.call(this); + this.clearChildren(); + }, + "setSubscriptionRange": function(s, e) { + var needStart = s !== this._subscriptionStart; + var needEnd = e !== this._subscriptionEnd; + if (needStart || needEnd) { + var os = this._subscriptionStart; + var oe = this._subscriptionEnd; + this._subscriptionStart = s; + this._subscriptionEnd = e; + if (this._subscribed) { + this.trigger("rangeStart"); + if (needStart) { + if (s > os) { + var limit = Math.min(s - os, this._controllers.length); + for (var i = 0; i < limit; ++i) { + var ctrl = this._controllers[0]; + this._removeControllerByIndex(0); + ctrl.destructor(); + } + } else { + var limit = Math.min(os, e) - s; + for (var i = 0; i < limit; ++i) { + var ctrl = new ImagePane(this._pairAddress["+"](new Address([this.data.at(i + s).toString()]))); + this.addController(ctrl, i); + } + } + } + if (needEnd) { + var ce = Math.min(this.data.length(), e); + var coe = Math.min(this.data.length(), oe); + if (ce > coe) { + var start = Math.max(s, oe); + var amount = ce - start; //it can be negative, it's fine + for (var i = 0; i < amount; ++i) { + var ctrl = new ImagePane(this._pairAddress["+"](new Address([this.data.at(start + i).toString()]))); + this.addController(ctrl); + } + } else if (ce < coe) { + var amount = Math.min(coe - ce, coe - os); + for (var i = 0; i < amount; ++i) { + var index = this._controllers.length - 1; + var ctrl = this._controllers[index]; + this._removeControllerByIndex(index); + ctrl.destructor(); + } + } + } + this.trigger("rangeEnd"); + } + } + } +}); + +module.exports = PanesList; diff --git a/libjs/wController/string.js b/libjs/wController/string.js new file mode 100644 index 0000000..c58e2df --- /dev/null +++ b/libjs/wController/string.js @@ -0,0 +1,22 @@ +"use strict"; +var Controller = require("./controller"); + +var String = Controller.inherit({ + "className": "String", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this.data = ""; + + this.addHandler("get"); + }, + "_h_get": function(ev) { + var data = ev.getData(); + + this.data = data.at("data").toString(); + + this.trigger("data"); + } +}); + +module.exports = String; diff --git a/libjs/wController/theme.js b/libjs/wController/theme.js new file mode 100644 index 0000000..0d401fe --- /dev/null +++ b/libjs/wController/theme.js @@ -0,0 +1,31 @@ +"use strict"; +var Controller = require("./controller"); + +var Theme = Controller.inherit({ + "className": "Theme", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this._data = {}; + this._deferredReady = this._onReady.bind(this); + + this.addHandler("get"); + }, + "_h_get": function(ev) { + var pairs = ev.getData(); + this.trigger("clear"); + + var data = pairs.at("data"); + var keys = data.getKeys(); + for (var i = 0; i < keys.length; ++i) { + this._data[keys[i]] = data.at(keys[i]).valueOf() + } + + setTimeout(this._deferredReady, 1); + }, + "_onReady": function() { + this.trigger("ready", this._data); + } +}); + +module.exports = Theme; diff --git a/libjs/wController/themeSelecter.js b/libjs/wController/themeSelecter.js new file mode 100644 index 0000000..08746bf --- /dev/null +++ b/libjs/wController/themeSelecter.js @@ -0,0 +1,56 @@ +"use strict"; +var Controller = require("./controller"); + +var ThemeSelecter = Controller.inherit({ + "className": "ThemeSelecter", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this._data = {}; + this._selected = undefined; + + this.addHandler("get"); + this.addHandler("insert"); + }, + "destructor": function() { + for (var i = 0; i < this._views.length; ++i) { + this._views[i].off("select", this.select, this); + } + + Controller.fn.destructor.call(this); + }, + "addView": function(view) { + Controller.fn.addView.call(this, view); + + view.on("select", this.select, this); + }, + "_h_get": function(ev) { + var pairs = ev.getData(); + this.trigger("clear"); + + var data = pairs.at("data"); + var keys = data.getKeys(); + for (var i = 0; i < keys.length; ++i) { + this._data[keys[i]] = data.at(keys[i]).clone() + this.trigger("newElement", {key: keys[i], value: this._data[keys[i]] }); + } + this.select(pairs.at("default").toString()); + }, + "_h_insert": function(ev) { + var data = ev.getData(); + + var key = data.at().toString(); + var value = data.at().clone() + this._data[key] = value; + this.trigger("newElement", {key: key, value: value}); + }, + "select": function(key) { + if (!this._data[key]) { + throw new Error("No such key"); + } + this._selected = key; + this.trigger("selected", {key: key, value: this._data[key]}); + } +}); + +module.exports = ThemeSelecter; diff --git a/libjs/wController/vocabulary.js b/libjs/wController/vocabulary.js new file mode 100644 index 0000000..9905909 --- /dev/null +++ b/libjs/wController/vocabulary.js @@ -0,0 +1,69 @@ +"use strict"; +var Controller = require("./controller"); +var WVocabulary = require("../wType/vocabulary"); + +var Vocabulary = Controller.inherit({ + "className": "Vocabulary", + "constructor": function(addr) { + Controller.fn.constructor.call(this, addr); + + this.data = new WVocabulary(); + + this.addHandler("get"); + this.addHandler("change"); + this.addHandler("clear"); + }, + "destructor": function() { + this.data.destructor(); + + Controller.fn.destructor.call(this); + }, + "addElement": function(key, element) { + this.data.insert(key, element); + this.trigger("newElement", key, element); + }, + "removeElement": function(key) { + this.data.erase(key); + this.trigger("removeElement", key); + }, + "clear": function() { + this.data.clear(); + this.trigger("clear"); + }, + "_h_change": function(ev) { + var key; + var data = ev.getData(); + + var erase = data.at("erase"); + var insert = data.at("insert"); + + var eSize = erase.length(); + for (var i = 0; i < eSize; ++i) { + key = erase.at(i).toString();; + this.removeElement(key); + } + + var keys = insert.getKeys(); + for (var j = 0; j < keys.length; ++j) { + key = keys[i]; + this.addElement(key, insert.at(key).clone()); + } + this.trigger("change", data.clone()); + }, + "_h_clear": function(ev) { + this.clear(); + }, + "_h_get": function(ev) { + this.clear(); + + var data = ev.getData().at("data"); + var keys = data.getKeys(); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + this.addElement(key, data.at(key).clone()); + } + this.trigger("data"); + } +}); + +module.exports = Vocabulary; diff --git a/libjs/wDispatcher/CMakeLists.txt b/libjs/wDispatcher/CMakeLists.txt new file mode 100644 index 0000000..58d81f0 --- /dev/null +++ b/libjs/wDispatcher/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(dispatcher.js dispatcher.js) +configure_file(defaulthandler.js defaulthandler.js) +configure_file(handler.js handler.js) +configure_file(logger.js logger.js) +configure_file(parentreporter.js parentreporter.js) diff --git a/libjs/wDispatcher/defaulthandler.js b/libjs/wDispatcher/defaulthandler.js new file mode 100644 index 0000000..a57feb3 --- /dev/null +++ b/libjs/wDispatcher/defaulthandler.js @@ -0,0 +1,35 @@ +"use strict"; +var Class = require("../utils/class"); +var id = 0; + +var DefaultHandler = Class.inherit({ + "className": "DefaultHandler", + "constructor": function() { + Class.fn.constructor.call(this); + + this._id = id++; + }, + "==": function(other) { + if (!(other instanceof DefaultHandler)) { + throw new Error("Can compare only DefaultHandler with DefaultHandler"); + } + return this._id === other._id; + }, + ">": function(other) { + if (!(other instanceof DefaultHandler)) { + throw new Error("Can compare only DefaultHandler with DefaultHandler"); + } + return this._id > other._id; + }, + "<": function(other) { + if (!(other instanceof DefaultHandler)) { + throw new Error("Can compare only DefaultHandler with DefaultHandler"); + } + return this._id < other._id; + }, + "call": function(event) { + throw new Error("Attempt to call pure abstract default handler"); + } +}); + +module.exports = DefaultHandler; diff --git a/libjs/wDispatcher/dispatcher.js b/libjs/wDispatcher/dispatcher.js new file mode 100644 index 0000000..dfb2695 --- /dev/null +++ b/libjs/wDispatcher/dispatcher.js @@ -0,0 +1,91 @@ +"use strict"; +var Class = require("../utils/class"); +var Handler = require("./handler"); +var DefaultHandler = require("./defaulthandler"); +var Address = require("../wType/address"); +var AbstractMap = require("../wContainer/abstractmap"); +var AbstractOrder = require("../wContainer/abstractorder"); + +var HandlerOrder = AbstractOrder.template(Handler); +var DefaultHandlerOrder = AbstractOrder.template(DefaultHandler); +var HandlerMap = AbstractMap.template(Address, HandlerOrder); + +var Dispatcher = Class.inherit({ + "className": "Dispatcher", + "constructor": function() { + Class.fn.constructor.call(this); + + this._handlers = new HandlerMap(); + this._defautHandlers = new DefaultHandlerOrder(false); + }, + "destructor": function() { + this._handlers.destructor(); + + Class.fn.destructor.call(this); + }, + "registerHandler": function(handler) { + var itr = this._handlers.find(handler.address); + var order; + + + if (itr["=="](this._handlers.end())) { + order = new HandlerOrder(false); + this._handlers.insert(handler.address.clone(), order); + } else { + order = itr["*"](); + } + + order.push_back(handler); + }, + "unregisterHandler": function(handler) { + var itr = this._handlers.find(handler.address); + + if (!itr["=="](this._handlers.end())) { + + var ord = itr["*"]().second; + ord.erase(handler); + + if (ord.size() === 0) { + this._handlers.erase(itr); + } + + } else { + throw new Error("Can't unregister hander"); + } + }, + "registerDefaultHandler": function(dh) { + this._defautHandlers.push_back(dh); + }, + "unregisterDefaultHandler": function(dh) { + this._defautHandlers.erase(dh); + }, + "pass": function(event) { + var itr = this._handlers.find(event.getDestination()); + + if (!itr["=="](this._handlers.end())) { + var ord = itr["*"]().second; + + var o_beg = ord.begin(); + var o_end = ord.end(); + var hands = []; + + for (; !o_beg["=="](o_end); o_beg["++"]()) { + hands.push(o_beg["*"]()); + } + + for (var i = 0; i < hands.length; ++i) { + hands[i].pass(event) + } + } else { + var dhitr = this._defautHandlers.begin(); + var dhend = this._defautHandlers.end(); + for (; !dhitr["=="](dhend); dhitr["++"]()) { + if (dhitr["*"]().call(event)) { + break; + } + } + } + } +}); + +module.exports = Dispatcher; diff --git a/libjs/wDispatcher/handler.js b/libjs/wDispatcher/handler.js new file mode 100644 index 0000000..a1b0024 --- /dev/null +++ b/libjs/wDispatcher/handler.js @@ -0,0 +1,44 @@ +"use strict"; +var Class = require("../utils/class"); +var id = 0; + +var Handler = Class.inherit({ + "className": "Handler", + "constructor": function(address, instance, method) { + Class.fn.constructor.call(this); + + this._id = id++; + + this.address = address; + this._ctx = instance; + this._mth = method; + }, + "destructor": function() { + this.address.destructor(); + + Class.fn.destructor.call(this); + }, + "pass": function(event) { + this._mth.call(this._ctx, event); + }, + "==": function(other) { + if (!(other instanceof Handler)) { + throw new Error("Can compare only Handler with Handler"); + } + return this._id === other._id; + }, + ">": function(other) { + if (!(other instanceof Handler)) { + throw new Error("Can compare only Handler with Handler"); + } + return this._id > other._id; + }, + "<": function(other) { + if (!(other instanceof Handler)) { + throw new Error("Can compare only Handler with Handler"); + } + return this._id < other._id; + } +}); + +module.exports = Handler; diff --git a/libjs/wDispatcher/logger.js b/libjs/wDispatcher/logger.js new file mode 100644 index 0000000..d191625 --- /dev/null +++ b/libjs/wDispatcher/logger.js @@ -0,0 +1,18 @@ +"use strict"; +var DefaultHandler = require("./defaulthandler"); + +var Logger = DefaultHandler.inherit({ + "className": "Logger", + "constructor": function() { + DefaultHandler.fn.constructor.call(this); + }, + "call": function(event) { + console.log("Event went to default handler"); + console.log("Destination: " + event.getDestination().toString()); + console.log("Data: " + event.getData().toString()); + + return false; + } +}); + +module.exports = Logger; diff --git a/libjs/wDispatcher/parentreporter.js b/libjs/wDispatcher/parentreporter.js new file mode 100644 index 0000000..9eec1ef --- /dev/null +++ b/libjs/wDispatcher/parentreporter.js @@ -0,0 +1,55 @@ +"use strict"; + +var DefaultHandler = require("./defaulthandler"); +var Handler = require("./handler"); + +var Address = require("../wType/address"); +var AbstractMap = require("../wContainer/abstractmap"); + +var HandlersMap = AbstractMap.template(Address, Handler); + +var ParentReporter = DefaultHandler.inherit({ + "className": "ParentReporter", + "constructor": function() { + DefaultHandler.fn.constructor.call(this); + + this._handlers = new HandlersMap(false); + }, + "destructor": function() { + this._handlers.destructor(); + + DefaultHandler.fn.destructor.call(this); + }, + "call": function(ev) { + var addr = ev.getDestination(); + var result = []; + + var itr = this._handlers.begin(); + var end = this._handlers.end(); + for (; !itr["=="](end); itr["++"]()) { + var pair = itr["*"](); + if (addr.begins(pair.first)) { + result[pair.first.size()] = pair.second; //it's a dirty javascript trick + } //I need the longest of matching, and that's how I'm gonna get it + } + if (result.length) { + result[result.length - 1].pass(ev); + return true; + } else { + return false; + } + }, + "registerParent": function(parentAddr, handler) { + this._handlers.insert(parentAddr, handler); + }, + "unregisterParent": function(parentAddr) { + var itr = this._handlers.find(parentAddr); + if (!itr["=="](this._handlers.end())) { + this._handlers.erase(itr); + } else { + throw new Error("An attempt to unregister unregistered parent in ParentReporter"); + } + } +}); + +module.exports = ParentReporter; diff --git a/libjs/wModel/CMakeLists.txt b/libjs/wModel/CMakeLists.txt new file mode 100644 index 0000000..b06a09c --- /dev/null +++ b/libjs/wModel/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(model.js model.js) +configure_file(globalControls.js globalControls.js) +configure_file(link.js link.js) +configure_file(list.js list.js) +configure_file(page.js page.js) +configure_file(pageStorage.js pageStorage.js) +configure_file(panesList.js panesList.js) +configure_file(string.js string.js) +configure_file(theme.js theme.js) +configure_file(themeStorage.js themeStorage.js) +configure_file(vocabulary.js vocabulary.js) +configure_file(attributes.js attributes.js) +configure_file(image.js image.js) + +add_subdirectory(proxy) diff --git a/libjs/wModel/attributes.js b/libjs/wModel/attributes.js new file mode 100644 index 0000000..d252027 --- /dev/null +++ b/libjs/wModel/attributes.js @@ -0,0 +1,44 @@ +"use strict"; + +var ModelVocabulary = require("./vocabulary"); +var Vocabulary = require("../wType/vocabulary"); +var String = require("../wType/string"); +var Uint64 = require("../wType/uint64"); + +var Attributes = ModelVocabulary.inherit({ + "className": "Attributes", + "constructor": function(addr) { + ModelVocabulary.fn.constructor.call(this, addr); + + this._attributes = global.Object.create(null); + }, + "addAttribute": function(key, model) { + var old = this._attributes[key]; + if (old) { + throw new Error("Attribute with key " + key + " already exists"); + } + this._attributes[key] = model; + this.addModel(model); + + var vc = new Vocabulary(); + vc.insert("name", new String(key)); + vc.insert("address", model.getAddress()); + vc.insert("type", new Uint64(model.getType())); + this.insert(key, vc); + }, + "removeAttribute": function(key) { + var model = this._attributes[key]; + if (!model) { + throw new Error("An attempt to access non existing Attribute"); + } + delete this._attributes[key]; + this.erase(key); + this.removeModel(model); + model.destructor(); + }, + "setAttribute": function(key, value) { + this._attributes[key].set(value); + } +}); + +module.exports = Attributes; diff --git a/libjs/wModel/globalControls.js b/libjs/wModel/globalControls.js new file mode 100644 index 0000000..f5f931d --- /dev/null +++ b/libjs/wModel/globalControls.js @@ -0,0 +1,60 @@ +"use strict"; + +var List = require("./list"); +var Model = require("./model"); +var ModelString = require("./string"); +var ModelLink = require("./link"); +var ThemeStorage = require("./themeStorage"); +var Vocabulary = require("../wType/vocabulary"); +var Address = require("../wType/address"); +var String = require("../wType/string"); + +var GlobalControls = List.inherit({ + "className": "GlobalControls", + "constructor": function(address) { + List.fn.constructor.call(this, address); + + this._initModels() + }, + "addModel": function(name, model) { + List.fn.addModel.call(this, model); + + var vc = new Vocabulary(); + vc.insert("type", new String(model.className)); + vc.insert("address", model.getAddress()); + vc.insert("name", new String(name)); + + this.push(vc); + }, + "addModelAsLink": function(name, model) { + var vc = new Vocabulary(); + vc.insert("type", new String(model.className)); + vc.insert("address", model.getAddress()); + vc.insert("name", new String(name)); + + this.push(vc); + }, + "_initModels": function() { + var navigationPanel = this._np = new List(this._address["+"](new Address(["navigationPanel"]))); + + navigationPanel.addProperty("backgroundColor", "primaryColor"); + this.addModel("navigationPanel", navigationPanel); + + var ts = new ThemeStorage(this._address["+"](new Address(["themes"]))); + this.addModel("themes", ts); + }, + "addNav": function(name, address) { + var vc = new Vocabulary(); + + var model = new ModelLink(this._np._address["+"](new Address(["" + this._np._data.length()])), name, address); + model.label.addProperty("fontSize", "largeFontSize"); + model.label.addProperty("fontFamily", "largeFont"); + model.label.addProperty("color", "primaryFontColor"); + this._np.addModel(model); + + vc.insert("address", model.getAddress()); + this._np.push(vc); + } +}); + +module.exports = GlobalControls; diff --git a/libjs/wModel/image.js b/libjs/wModel/image.js new file mode 100644 index 0000000..1b95e12 --- /dev/null +++ b/libjs/wModel/image.js @@ -0,0 +1,47 @@ +"use strict"; + +var Model = require("./model"); +var Uint64 = require("../wType/uint64"); +var Address = require("../wType/address"); +var Vocabulary = require("../wType/vocabulary"); + +var ModelImage = Model.inherit({ + "className": "Image", + "constructor": function(address, uint64) { + Model.fn.constructor.call(this, address); + + this._data = uint64; + + this.addHandler("get"); + }, + "destructor": function() { + this._data.destructor(); + + Model.fn.destructor.call(this); + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + }, + "_h_get": function(ev) { + var vc = new Vocabulary(); + + vc.insert("data", this._data.clone()); + this.response(vc, "get", ev); + }, + "set": function(uint64) { + this._data.destructor(); + + this._data = uint64; + + if (this._registered) { + var vc = new Vocabulary(); + + vc.insert("data", this._data.clone()); + this.broadcast(vc, "get"); + } + } +}); + +module.exports = ModelImage; diff --git a/libjs/wModel/link.js b/libjs/wModel/link.js new file mode 100644 index 0000000..8fcdc4c --- /dev/null +++ b/libjs/wModel/link.js @@ -0,0 +1,44 @@ +"use strict"; + +var Model = require("./model"); +var ModelString = require("./string") + +var String = require("../wType/string"); + +var Vocabulary = require("../wType/vocabulary"); +var Address = require("../wType/address"); + +var Link = Model.inherit({ + "className": "Link", + "constructor": function(addr, text, tAddress) { + Model.fn.constructor.call(this, addr); + + this.addHandler("get"); + + this._targetAddress = tAddress; + + var hop = new Address(["label"]); + this.label = new ModelString(addr["+"](hop), text); + this.addModel(this.label); + + hop.destructor(); + }, + "destructor": function() { + this._targetAddress.destructor(); + + Model.fn.destructor.call(this); + }, + "_h_get": function(ev) { + var vc = new Vocabulary(); + + vc.insert("targetAddress", this._targetAddress.clone()); + this.response(vc, "get", ev); + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + } +}); + +module.exports = Link; diff --git a/libjs/wModel/list.js b/libjs/wModel/list.js new file mode 100644 index 0000000..7db1bdc --- /dev/null +++ b/libjs/wModel/list.js @@ -0,0 +1,51 @@ +"use strict"; + +var Model = require("./model"); +var Vector = require("../wType/vector"); +var Vocabulary = require("../wType/vocabulary"); +var Object = require("../wType/object") + +var List = Model.inherit({ + "className": "List", + "constructor": function(address) { + Model.fn.constructor.call(this, address); + + this._data = new Vector(); + + this.addHandler("get"); + }, + "destructor": function() { + this._data.destructor(); + + Model.fn.destructor.call(this); + }, + "clear": function() { + this._data.clear(); + + this.broadcast(new Vocabulary(), "clear"); + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + }, + "_h_get": function(ev) { + var vc = new Vocabulary(); + + vc.insert("data", this._data.clone()); + this.response(vc, "get", ev); + }, + "push": function(obj) { + if (!(obj instanceof Object)) { + throw new Error("An attempt to push into list unserializable value"); + } + this._data.push(obj); + + var vc = new Vocabulary(); + vc.insert("data", obj.clone()); + + this.broadcast(vc, "push"); + } +}); + +module.exports = List; diff --git a/libjs/wModel/model.js b/libjs/wModel/model.js new file mode 100644 index 0000000..d5f68a1 --- /dev/null +++ b/libjs/wModel/model.js @@ -0,0 +1,316 @@ +"use strict"; +var Subscribable = require("../utils/subscribable"); +var AbstcractMap = require("../wContainer/abstractmap"); +var AbstractOrder = require("../wContainer/abstractorder"); +var Address = require("../wType/address"); +var Uint64 = require("../wType/uint64"); +var Event = require("../wType/event"); +var Vector = require("../wType/vector"); +var Vocabulary = require("../wType/vocabulary"); +var String = require("../wType/string"); +var Handler = require("../wDispatcher/handler"); + +var Model = Subscribable.inherit({ + "className": "Model", + "constructor": function(address) { + Subscribable.fn.constructor.call(this); + + var SMap = AbstcractMap.template(Uint64, Model.addressOrder); + + this._registered = false; + this._subscribers = new SMap(false); + this._handlers = []; + this._models = []; + this._props = new Vector(); + this._address = address; + this._subscribersCount = 0; + + this.addHandler("subscribe"); + this.addHandler("unsubscribe"); + }, + "destructor": function() { + var i; + if (this._registered) { + this.unregister(); + } + + this._subscribers.destructor(); + + for (i = 0; i < this._models.length; ++i) { + this._models[i].destructor(); + } + + for (i = 0; i < this._handlers.length; ++i) { + this._handlers[i].destructor(); + } + this._props.destructor(); + + Subscribable.fn.destructor.call(this); + }, + "addHandler": function(name) { + if (!(this["_h_" + name] instanceof Function)) { + throw new Error("An attempt to create handler without a handling method"); + } + + var handler = new Handler(this._address["+"](new Address([name])), this, this["_h_" + name]); + + this._addHandler(handler); + }, + "_addHandler": function(handler) { + this._handlers.push(handler); + if (this._registered) { + this._dp.registerHandler(handler); + } + }, + "addModel": function(model) { + if (!(model instanceof Model)) { + throw new Error("An attempt to add not a model into " + this.className); + } + this._models.push(model); + model.on("serviceMessage", this._onModelServiceMessage, this); + if (this._registered) { + model.register(this._dp, this._server); + } + }, + "addProperty": function(property, key) { + var vc = new Vocabulary(); + vc.insert("property", new String(property)); + vc.insert("key", new String(key)); + + this._props.push(vc); + + if (this._registered) { + var nvc = new Vocabulary(); + nvc.insert("properties", this._props.clone()); + this.broadcast(nvc, "properties"); + } + }, + "broadcast": function(vc, handler) { + var itr = this._subscribers.begin(); + var end = this._subscribers.end(); + vc.insert("source", this._address.clone()); + + for (;!itr["=="](end); itr["++"]()) { + var obj = itr["*"](); + var order = obj.second; + var socket = this._server.getConnection(obj.first); + var oItr = order.begin(); + var oEnd = order.end(); + for (;!oItr["=="](oEnd); oItr["++"]()) { + var addr = oItr["*"]()["+"](new Address([handler])); + var ev = new Event(addr, vc.clone()); + ev.setSenderId(socket.getId().clone()); + socket.send(ev); + ev.destructor(); + } + } + vc.destructor(); + }, + "getAddress": function() { + return this._address.clone(); + }, + "getType": function() { + var type = Model.ModelType[this.className]; + if (type === undefined) { + throw new Error("Undefined ModelType"); + } + return type; + }, + "_h_subscribe": function(ev) { + var id = ev.getSenderId(); + var source = ev.getData().at("source"); + + var itr = this._subscribers.find(id); + var ord; + if (itr["=="](this._subscribers.end())) { + ord = new Model.addressOrder(true); + var socket = this._server.getConnection(id); + socket.one("disconnected", this._onSocketDisconnected, this); + this._subscribers.insert(id.clone(), ord); + } else { + ord = itr["*"]().second; + var oItr = ord.find(source); + if (!oItr["=="](ord.end())) { + this.trigger("serviceMessage", "id: " + id.toString() + ", " + + "source: " + source.toString() + " " + + "is trying to subscribe on model " + this._address.toString() + " " + + "but it's already subscribed", 1); + return; + } + } + + ord.push_back(source.clone()); + ++this._subscribersCount; + this.trigger("serviceMessage", this._address.toString() + " has now " + this._subscribersCount + " subscribers", 0); + + var nvc = new Vocabulary(); + nvc.insert("properties", this._props.clone()); + + this.response(nvc, "properties", ev); + }, + "_h_unsubscribe": function(ev) { + var id = ev.getSenderId(); + var source = ev.getData().at("source"); + + var itr = this._subscribers.find(id); + + if (itr["=="](this._subscribers.end())) { + this.trigger("serviceMessage", "id: " + id.toString() + ", " + + "source: " + source.toString() + " " + + "is trying to unsubscribe from model " + this._address.toString() + " " + + "but even this id is not registered in subscribers map", 1 + ); + return + } + var ord = itr["*"]().second; + var oItr = ord.find(source); + if (oItr["=="](ord.end())) { + this.trigger("serviceMessage", "id: " + id.toString() + ", " + + "source: " + source.toString() + " " + + "is trying to unsubscribe from model " + this._address.toString() + " " + + "but such address is not subscribed to this model", 1 + ); + return + } + ord.erase(oItr["*"]()); + if (ord.size() === 0) { + var socket = this._server.getConnection(itr["*"]().first); + socket.off("disconnected", this._onSocketDisconnected, this); + this._subscribers.erase(itr); + ord.destructor(); + } + + --this._subscribersCount; + this.trigger("serviceMessage", this._address.toString() + " has now " + this._subscribersCount + " subscribers", 0); + }, + "_onModelServiceMessage": function(msg, severity) { + this.trigger("serviceMessage", msg, severity); + }, + "_onSocketDisconnected": function(ev, socket) { + var id = socket.getId(); + var itr = this._subscribers.find(id); + + if (itr["=="](this._subscribers.end())) { + this.trigger("serviceMessage", "id: " + id.toString() + ", " + + "after socket disconnected trying to remove subscriptions from model " + + "but id haven't been found in subscribers map", 1); + return + } + var ord = itr["*"]().second; + this._subscribersCount -= ord.size(); + this._subscribers.erase(itr); + ord.destructor(); + this.trigger("serviceMessage", this._address.toString() + " has now " + this._subscribersCount + " subscribers", 0); + }, + "register": function(dp, server) { + if (this._registered) { + throw new Error("Model " + this._address.toString() + " is already registered"); + } + this._dp = dp; + this._server = server; + var i; + + for (i = 0; i < this._models.length; ++i) { + this._models[i].register(dp, server); + } + + for (i = 0; i < this._handlers.length; ++i) { + dp.registerHandler(this._handlers[i]); + } + this._registered = true; + }, + "_removeHandler": function(handler) { + var index = this._handlers.indexOf(handler); + if (index === -1) { + throw new Error("An attempt to remove non existing handler"); + } + this._handlers.splice(index, 1); + if (this._registered) { + this._dp.unregisterHandler(handler); + } + }, + "removeModel": function(model) { + if (!(model instanceof Model)) { + throw new Error("An attempt to remove not a model from " + this.className); + } + var index = this._models.indexOf(model); + if (index === -1) { + throw new Error("An attempt to remove non existing model from " + this.className); + } + this._models.splice(index, 1); + if (this._registered) { + model.unregister(this._dp, this._server); + } + }, + "response": function(vc, handler, src) { + if (!this._registered) { + throw new Error("An attempt to send a message from unregistered model " + this._address.toString()); + } + var source = src.getData().at("source").clone(); + var id = src.getSenderId().clone(); + var addr = source["+"](new Address([handler])); + vc.insert("source", this._address.clone()); + + var ev = new Event(addr, vc); + ev.setSenderId(id); + var socket = this._server.getConnection(id); + socket.send(ev); + ev.destructor(); + }, + "unregister": function() { + if (!this._registered) { + throw new Error("Model " + this._address.toString() + " is not registered"); + } + var i; + + for (i = 0; i < this._models.length; ++i) { + this._models[i].unregister(); + } + + for (i = 0; i < this._handlers.length; ++i) { + this._dp.unregisterHandler(this._handlers[i]); + } + + var itr = this._subscribers.begin(); + var end = this._subscribers.end(); + + for (;!itr["=="](end); itr["++"]()) { + var socket = this._server.getConnection(itr["*"]().first); + var ord = itr["*"]().second; + ord.destructor(); + socket.off("disconnected", this._onSocketDisconnected, this); + } + this._subscribers.clear(); + this._subscribersCount = 0; + + delete this._dp; + delete this._server; + + this._registered = false; + } +}); + +Model.getModelTypeId = function(model) { + return this.ModelType[model.className]; +} + +Model.addressOrder = AbstractOrder.template(Address); +Model.ModelType = { + String: 0, + List: 1, + Vocabulary: 2, + Image: 3, + Model: 4, + + Attributes: 50, + + GlobalControls: 100, + Link: 101, + Page: 102, + PageStorage: 103, + PanesList: 104, + Theme: 105, + ThemeStorage: 106 +}; + +module.exports = Model; diff --git a/libjs/wModel/page.js b/libjs/wModel/page.js new file mode 100644 index 0000000..01e32c3 --- /dev/null +++ b/libjs/wModel/page.js @@ -0,0 +1,163 @@ +"use strict"; + +var Model = require("./model"); +var AbstractMap = require("../wContainer/abstractmap"); +var Vocabulary = require("../wType/vocabulary"); +var Vector = require("../wType/vector"); +var Address = require("../wType/address"); +var String = require("../wType/string"); +var UInt64 = require("../wType/uint64"); + +var ContentMap = AbstractMap.template(Address, Vocabulary); + +var Page = Model.inherit({ + "className": "Page", + "constructor": function(address, name) { + Model.fn.constructor.call(this, address); + + this._data = new ContentMap(true); + this.name = name.toLowerCase(); + this.urlAddress = [this.name]; + this._hasParentReporter = false; + this._pr = undefined; + this._childPages = {}; + + this.addHandler("get"); + this.addHandler("ping"); + + this.addProperty("backgroundColor", "mainColor"); + this.addProperty("color", "mainFontColor"); + }, + "destructor": function() { + this._data.destructor(); + + Model.fn.destructor.call(this); + }, + "addItem": function(model, row, col, rowspan, colspan, aligment, viewType, viewOptions) { + Model.fn.addModel.call(this, model); + + var vc = new Vocabulary(); + viewOptions = viewOptions || new Vocabulary(); + viewType = viewType || new UInt64(Page.getModelTypeId(model)) + + vc.insert("type", new UInt64(Page.getModelTypeId(model))); + vc.insert("row", new UInt64(row || 0)); + vc.insert("col", new UInt64(col || 0)); + vc.insert("rowspan", new UInt64(rowspan || 1)); + vc.insert("colspan", new UInt64(colspan || 1)); + vc.insert("aligment", new UInt64(aligment || Page.Aligment.LeftTop)); + vc.insert("viewOptions", viewOptions); + vc.insert("viewType", viewType); + + this._data.insert(model.getAddress(), vc); + + var evc = vc.clone(); + evc.insert("address", model.getAddress()); + + this.broadcast(evc, "addItem"); + }, + "addPage": function(page) { + this.addModel(page); + var addr = this.urlAddress.slice(); + addr.push(page.name); + page.setUrlAddress(addr) + page.on("newPage", this._onNewPage, this); + page.on("removePage", this._onRemovePage, this); + this._childPages[page.name] = page; + + this.trigger("newPage", page); + }, + "clear": function() { + this._data.clear(); + + this.broadcast(new Vocabulary(), "clear"); + }, + "getChildPage": function(name) { + return this._childPages[name]; + }, + "_h_get": function(ev) { + var data = new Vocabulary(); + + var vector = new Vector(); + var itr = this._data.begin(); + var end = this._data.end(); + + for (; !itr["=="](end); itr["++"]()) { + var pair = itr["*"](); + var vc = pair.second.clone(); + + vc.insert("address", pair.first.clone()); + vector.push(vc); + } + + data.insert("name", new String(this.name)); + data.insert("data", vector); + this.response(data, "get", ev); + }, + "_h_ping": function(ev) { + this.trigger("serviceMessage", "page " + this._address.toString() + " got pinged", 0); + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + }, + "_onNewPage": function(page) { + this.trigger("newPage", page); + }, + "_onRemovePage": function(page) { + this.trigger("removePage", page); + }, + "removeItem": function(model) { + Model.fn.removeModel.call(this, model); + var addr = model.getAddress(); + + var itr = this._data.find(addr); + this._data.erase(itr); + + var vc = new Vocabulary(); + vc.insert("address", addr); + + this.broadcast(vc, "removeItem"); + }, + "removePage": function(page) { + Model.fn.removeModel.call(this, page); + + delete this._childPages[page.name]; + page.off("newPage", this._onNewPage, this); + page.off("removePage", this._onRemovePage, this); + + this.trigger("removePage", page); + }, + "setParentReporter": function(pr) { + if (this._hasParentReporter) { + throw new Error("An attempt to set parent reporter to page while another one already exists"); + } + this._pr = pr; + this._hasParentReporter = true; + }, + "setUrlAddress": function(address) { + this.urlAddress = address; + }, + "unsetParentReporter": function() { + if (!this._hasParentReporter) { + throw new Error("An attempt to unset parent reporter from page which doesn't have one"); + } + delete this._pr; + this._hasParentReporter = false; + } +}); + +Page.Aligment = { + "LeftTop": 1, + "LeftCenter": 4, + "LeftBottom": 7, + "CenterTop": 2, + "CenterCenter": 5, + "CenterBottom": 8, + "RightTop": 3, + "RightCenter": 6, + "RightBottom": 9 +}; + +module.exports = Page; diff --git a/libjs/wModel/pageStorage.js b/libjs/wModel/pageStorage.js new file mode 100644 index 0000000..0055fe4 --- /dev/null +++ b/libjs/wModel/pageStorage.js @@ -0,0 +1,124 @@ +"use strict"; + +var Model = require("./model"); +var Page = require("./page"); +var ModelString = require("./string"); +var Vocabulary = require("../wType/vocabulary"); +var Address = require("../wType/address"); +var String = require("../wType/string"); +var Event = require("../wType/event"); +var AbstractMap = require("../wContainer/abstractmap"); + +var AddressMap = AbstractMap.template(Address, String); + +var PageStorage = Model.inherit({ + "className": "PageStorage", + "constructor": function(addr, rootPage, pr) { + Model.fn.constructor.call(this, addr); + + this._urls = {}; + this._specialPages = {}; + this._rMap = new AddressMap(true); + this._root = rootPage; + this._pr = pr; + + this._initRootPage(); + this._initNotFoundPage(); + + this.addHandler("getPageAddress"); + this.addHandler("getPageName"); + }, + "destructor": function() { + this._rMap.destructor(); + + Model.fn.destructor.call(this); + }, + "getPageByUrl": function(url) { + var addr = url.split("/"); + var page = this._root; + for (var i = 0; i < addr.length; ++i) { + if (addr[i] !== "") { + page = page.getChildPage(addr[i]); + if (page === undefined) { + return this._specialPages.notFound; + } + } + } + return page; + }, + "hasPage": function(name) { + return this.getPageByUrl(name) !== this._specialPages.notFound + }, + "_h_getPageAddress": function(ev) { + var data = ev.getData(); + + var page = this.getPageByUrl(data.at("url").valueOf()); + + var vc = new Vocabulary(); + if (page) { + vc.insert("address", page.getAddress()); + } else { + vc.insert("address", this._specialPages.notFound.getAddress()); + } + + this.response(vc, "pageAddress", ev); + }, + "_h_getPageName": function(ev) { + var data = ev.getData(); + var address = data.at("address"); + + var evp = new Event(address["+"](new Address(["ping"]))); + this._dp.pass(evp); + evp.destructor(); + + var itr = this._rMap.find(address); + var vc = new Vocabulary(); + if (itr["=="](this._rMap.end())) { + vc.insert("name", new String("notFound")); + } else { + vc.insert("name", itr["*"]().second.clone()); + } + this.response(vc, "pageName", ev); + }, + "_initNotFoundPage": function() { + var nf = new Page(this._address["+"](new Address(["special", "notFound"])), "Not found"); + this._specialPages["notFound"] = nf; + this.addModel(nf); + var msg = new ModelString(nf._address["+"](new Address(["errorMessage"])), "Error: page not found"); + nf.addItem(msg, 0, 0, 1, 1); + }, + "_initRootPage": function() { + this.addModel(this._root); + + this._rMap.insert(this._root.getAddress(), new String("/")); + this._root.on("newPage", this._onNewPage, this); + this._root.on("removePage", this._onRemovePage, this); + }, + "_onNewPage": function(page) { + var addr = page.urlAddress.join("/").slice(this._root.name.length); + this.trigger("serviceMessage", "Adding new page: " + addr); + this.trigger("serviceMessage", "Page address: " + page.getAddress().toString()); + + if (this._urls[addr]) { + throw new Error("An attempt to add page with an existing url"); + } + this._urls[addr] = page; + this._rMap.insert(page.getAddress(), new String(addr)); + page.setParentReporter(this._pr); + }, + "_onRemovePage": function(page) { + var addr = page.urlAddress.join("/").slice(this._root.name.length); + this.trigger("serviceMessage", "Removing page: " + addr); + this.trigger("serviceMessage", "Page address: " + page.getAddress().toString()); + + if (!this._urls[addr]) { + throw new Error("An attempt to remove a non existing page"); + } + delete this._urls[addr]; + var itr = this._rMap.find(page.getAddress()); + this._rMap.erase(itr); + page.unsetParentReporter(); + } +}); + +module.exports = PageStorage; diff --git a/libjs/wModel/panesList.js b/libjs/wModel/panesList.js new file mode 100644 index 0000000..bfaf123 --- /dev/null +++ b/libjs/wModel/panesList.js @@ -0,0 +1,21 @@ +"use strict"; + +var List = require("./list"); +var Vocabulary = require("../wType/vocabulary"); + +var PanesList = List.inherit({ + "className": "PanesList", + "constructor": function(address) { + List.fn.constructor.call(this, address); + }, + "addItem": function(model) { + List.fn.addModel.call(this, model); + + var vc = new Vocabulary(); + vc.insert("address", model.getAddress()); + + this.push(vc); + } +}); + +module.exports = PanesList; diff --git a/libjs/wModel/proxy/CMakeLists.txt b/libjs/wModel/proxy/CMakeLists.txt new file mode 100644 index 0000000..77e91b5 --- /dev/null +++ b/libjs/wModel/proxy/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(proxy.js proxy.js) +configure_file(list.js list.js) +configure_file(vocabulary.js vocabulary.js) +configure_file(catalogue.js catalogue.js) diff --git a/libjs/wModel/proxy/catalogue.js b/libjs/wModel/proxy/catalogue.js new file mode 100644 index 0000000..c79627a --- /dev/null +++ b/libjs/wModel/proxy/catalogue.js @@ -0,0 +1,54 @@ +"use strict"; + +var Proxy = require("./proxy"); +var Vocabulary = require("../../wType/vocabulary"); +var Vector = require("../../wType/vector"); +var Ctrl = require("../../wController/catalogue"); + +var Catalogue = Proxy.inherit({ + "className": "List", //no, it is not a typo, this is the way data structure here suppose to look like + "constructor": function(address, ctrlAddr, ctrlOpts, socket) { + var controller = new Ctrl(ctrlAddr, ctrlOpts); + Proxy.fn.constructor.call(this, address, controller, socket); + + this.controller.on("data", this._onRemoteData, this); + this.controller.on("addElement", this._onRemoteAddElement, this); + //this.controller.on("removeElement", this._onRemoteRemoveElement, this); //not supported yet + }, + "_getAllData": function() { + var vec = new Vector(); + + var itr = this.controller.data.begin(); + var end = this.controller.data.end(); + + for (; !itr["=="](end); itr["++"]()) { + vec.push(itr["*"]().clone()); + } + + return vec; + }, + "_h_subscribe": function(ev) { + Proxy.fn._h_subscribe.call(this, ev); + + if (this.ready) { + this._h_get(ev); + } + }, + "_onRemoteData": function() { + this.setReady(true); + + var vc = new Vocabulary(); + vc.insert("data", this._getAllData()); + this.broadcast(vc, "get") + }, + "_onRemoteAddElement": function(obj, before) { + if (this.ready) { //only end appends are supported now + var vc = new Vocabulary(); + vc.insert("data", obj.clone()); + + this.broadcast(vc, "push"); + } + } +}); + +module.exports = Catalogue; diff --git a/libjs/wModel/proxy/list.js b/libjs/wModel/proxy/list.js new file mode 100644 index 0000000..4f9937d --- /dev/null +++ b/libjs/wModel/proxy/list.js @@ -0,0 +1,46 @@ +"use strict"; + +var Proxy = require("./proxy"); +var Vocabulary = require("../../wType/vocabulary"); +var Ctrl = require("../../wController/list"); + +var List = Proxy.inherit({ + "className": "List", + "constructor": function(address, controllerAddress, socket) { + var controller = new Ctrl(controllerAddress); + Proxy.fn.constructor.call(this, address, controller, socket); + + this.controller.on("data", this._onRemoteData, this); + this.controller.on("clear", this._onRemoteClear, this); + this.controller.on("newElement", this._onRemoteNewElement, this); + }, + "_h_subscribe": function(ev) { + Proxy.fn._h_subscribe.call(this, ev); + + if (this.ready) { + this._h_get(ev); + } + }, + "_onRemoteClear": function() { + if (this.ready) { + this.broadcast(new Vocabulary(), "clear"); + } + }, + "_onRemoteData": function() { + this.setReady(true); + + var vc = new Vocabulary(); + vc.insert("data", this.controller.data.clone()) + this.broadcast(vc, "get") + }, + "_onRemoteNewElement": function(obj) { + if (this.ready) { + var vc = new Vocabulary(); + vc.insert("data", obj.clone()); + + this.broadcast(vc, "push"); + } + } +}); + +module.exports = List; diff --git a/libjs/wModel/proxy/proxy.js b/libjs/wModel/proxy/proxy.js new file mode 100644 index 0000000..64e4f27 --- /dev/null +++ b/libjs/wModel/proxy/proxy.js @@ -0,0 +1,180 @@ +"use strict"; + +var Model = require("../model"); +var Handler = require("../../wDispatcher/handler"); +var Vocabulary = require("../../wType/vocabulary"); +var Address = require("../../wType/address"); + +var config = require("../../../config/config.json"); + +var Proxy = Model.inherit({ + "className": "Proxy", + "constructor": function(address, controller, socket) { //les't pretend - this class is abstract + Model.fn.constructor.call(this, address); + + this._socket = socket; + this.ready = false; + this.controller = controller; + this.childrenPossible = false; + this._destroyOnLastUnsubscription = false; + this._destructionTimeout = undefined; + this._childClass = undefined; + this._waitingEvents = []; + + this.addHandler("get"); + this.reporterHandler = new Handler(this._address["+"](new Address(["subscribeMember"])), this, this._h_subscribeMember); + + this._uncyclic.push(function() { + controller.destructor(); + }); + }, + "destructor": function() { + if (this._destructionTimeout) { + clearTimeout(this._destructionTimeout); + } + for (var i = 0; i < this._waitingEvents.length; ++i) { + this._waitingEvents[i].destructor(); + } + this.reporterHandler.destructor(); + Model.fn.destructor.call(this); + }, + "checkSubscribersAndDestroy": function() { + if (this._subscribersCount === 0 && this._destructionTimeout === undefined) { + this.trigger("serviceMessage", this._address.toString() + " has no more subscribers, destroying model"); + this._destructionTimeout = setTimeout(this.trigger.bind(this, "destroyMe"), config.modelDestructionTimeout); + } + }, + "dispatchWaitingEvents": function() { + for (var i = 0; i < this._waitingEvents.length; ++i) { + var ev = this._waitingEvents[i]; + var cmd = "_h_" + ev.getDestination().back().toString(); + this[cmd](ev); + ev.destructor(); + } + this._waitingEvents = []; + }, + "_getAllData": function() { + return this.controller.data.clone(); + }, + "_h_get": function(ev) { + if (this.ready) { + var vc = new Vocabulary(); + + vc.insert("data", this._getAllData()); + this.response(vc, "get", ev); + } else { + this._waitingEvents.push(ev.clone()); + } + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + if (this._destructionTimeout) { + clearTimeout(this._destructionTimeout); + this._destructionTimeout = undefined; + } + }, + "_h_unsubscribe": function(ev) { + Model.fn._h_unsubscribe.call(this, ev); + + if (this._destroyOnLastUnsubscription) { + this.checkSubscribersAndDestroy(); + } + }, + "_h_subscribeMember": function(ev) { + if (!this.childrenPossible) { + return; + } + var dest = ev.getDestination(); + var lastHops = dest["<<"](this._address.length()); + + if (lastHops.length() === 2) { + var command = lastHops.back().toString(); + var id = lastHops[">>"](1); + if (command === "subscribe" || command === "get") { + var child = new this._childClass(this._address["+"](id), this.controller._pairAddress["+"](id), this._socket); + this.addModel(child); + child._destroyOnLastUnsubscription = true; + child["_h_" + command](ev); + if (command === "get") { + child.on("ready", child.checkSubscribersAndDestroy, child); + } + child.subscribe(); + child.on("destroyMe", this.destroyChild.bind(this, child)); //to remove model if it has no subscribers + } else { + this.trigger("serviceMessage", "Proxy model got a strange event: " + ev.toString(), 1); + } + } else { + this.trigger("serviceMessage", "Proxy model got a strange event: " + ev.toString(), 1); + } + }, + "_onSocketDisconnected": function(ev, socket) { + Model.fn._onSocketDisconnected.call(this, ev, socket); + + if (this._destroyOnLastUnsubscription) { + this.checkSubscribersAndDestroy(); + } + }, + "register": function(dp, server) { + Model.fn.register.call(this, dp, server); + + this.controller.register(dp, this._socket); + }, + "unregister": function() { + Model.fn.unregister.call(this); + + if (this.controller._subscribed) { + this.controller.unsubscribe(); + } + this.controller.unregister(); + }, + "setChildrenClass": function(Class) { + if (this.childrenPossible) { + this.trigger("serviceMessage", "An attempt to set another class for children in Proxy", 1); + } + if (!Class instanceof Proxy) { + this.trigger("serviceMessage", "An attempt to set not inherited chidren class from Proxy to a Proxy", 2); + } + this.childrenPossible = true; + this._childClass = Class; + }, + "setReady": function(bool) { + bool = !!bool; + if (bool !== this.ready) { + this.ready = bool; + if (bool) { + this.dispatchWaitingEvents(); + this.trigger("ready"); + } else { + //todo do I realy need to trigger smth here? + } + } + }, + "subscribe": function() { + this.controller.subscribe(); + }, + "destroyChild": function(child) { + this.removeModel(child); + child.destructor(); + }, + "unsetChildrenClass": function() { + if (!this.childrenPossible) { + this.trigger("serviceMessage", "An attempt to unset children class in Proxy, which don't have it", 1); + } else { + delete this._childClass; + this.childrenPossible = false; + } + }, + "unsubscribe": function() { + this.controller.unsubscribe(); + this.setReady(false); + } +}); + +Proxy.onChildReady = function(ev) { + this._h_get(ev); + this.checkSubscribersAndDestroy(); +} + +module.exports = Proxy; + diff --git a/libjs/wModel/proxy/vocabulary.js b/libjs/wModel/proxy/vocabulary.js new file mode 100644 index 0000000..4600782 --- /dev/null +++ b/libjs/wModel/proxy/vocabulary.js @@ -0,0 +1,43 @@ +"use strict"; + +var Proxy = require("./proxy"); +var Vocabulary = require("../../wType/vocabulary"); +var Ctrl = require("../../wController/vocabulary"); + +var MVocabulary = Proxy.inherit({ + "className": "Vocabulary", + "constructor": function(address, controllerAddress, socket) { + var controller = new Ctrl(controllerAddress); + Proxy.fn.constructor.call(this, address, controller, socket); + + this.controller.on("data", this._onRemoteData, this); + this.controller.on("clear", this._onRemoteClear, this); + this.controller.on("change", this._onRemoteChange, this); + }, + "_h_subscribe": function(ev) { + Proxy.fn._h_subscribe.call(this, ev); + + if (this.ready) { + this._h_get(ev); + } + }, + "_onRemoteClear": function() { + if (this.ready) { + this.broadcast(new Vocabulary(), "clear"); + } + }, + "_onRemoteData": function() { + this.setReady(true); + + var vc = new Vocabulary(); + vc.insert("data", this._getAllData()); + this.broadcast(vc, "get") + }, + "_onRemoteChange": function(data) { + if (this.ready) { + this.broadcast(data, "change"); + } + } +}); + +module.exports = MVocabulary; diff --git a/libjs/wModel/string.js b/libjs/wModel/string.js new file mode 100644 index 0000000..678296d --- /dev/null +++ b/libjs/wModel/string.js @@ -0,0 +1,47 @@ +"use strict"; + +var Model = require("./model"); +var String = require("../wType/string"); +var Address = require("../wType/address"); +var Vocabulary = require("../wType/vocabulary"); + +var ModelString = Model.inherit({ + "className": "String", + "constructor": function(address, string) { + Model.fn.constructor.call(this, address); + + this._data = new String(string); + + this.addHandler("get"); + }, + "destructor": function() { + this._data.destructor(); + + Model.fn.destructor.call(this); + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + }, + "_h_get": function(ev) { + var vc = new Vocabulary(); + + vc.insert("data", this._data.clone()); + this.response(vc, "get", ev); + }, + "set": function(value) { + this._data.destructor(); + + this._data = new String(value.toString()); + + if (this._registered) { + var vc = new Vocabulary(); + + vc.insert("data", this._data.clone()); + this.broadcast(vc, "get"); + } + } +}); + +module.exports = ModelString; diff --git a/libjs/wModel/theme.js b/libjs/wModel/theme.js new file mode 100644 index 0000000..61d16a6 --- /dev/null +++ b/libjs/wModel/theme.js @@ -0,0 +1,70 @@ +"use strict"; + +var Model = require("./model"); +var Vocabulary = require("../wType/vocabulary"); +var String = require("../wType/string"); +var Uint64 = require("../wType/uint64"); + +var Theme = Model.inherit({ + "className": "Theme", + "constructor": function(address, name, theme) { + Model.fn.constructor.call(this, address); + + this._themeName = name; + var result = {}; + W.extend(result, Theme.default, theme); + + var data = new Vocabulary(); + + for (var key in result) { + if (result.hasOwnProperty(key)) { + var type = typeof result[key]; + switch (type) { + case "number": + data.insert(key, new Uint64(result[key])); + break; + default: + data.insert(key, new String(result[key])); + break; + } + } + } + + this._data = data; + + this.addHandler("get"); + }, + "getName": function() { + return this._themeName; + }, + "_h_get": function(ev) { + var vc = new Vocabulary(); + + vc.insert("data", this._data.clone()); + vc.insert("name", new String(this._themeName)); + this.response(vc, "get", ev); + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + } +}); + +Theme.default = { + mainColor: "#ffffff", + mainFontColor: "#222222", + primaryColor: "#0000ff", + primaryFontColor: "#ffffff", + secondaryColor: "#dddddd", + secondaryFontColor: "#222222", + + smallFont: "Liberation", + smallFontSize: "12px", + casualFont: "Liberation", + casualFontSize: "16px", + largeFont: "Liberation", + largeFontSize: "20px" +} + +module.exports = Theme; diff --git a/libjs/wModel/themeStorage.js b/libjs/wModel/themeStorage.js new file mode 100644 index 0000000..3f84cfd --- /dev/null +++ b/libjs/wModel/themeStorage.js @@ -0,0 +1,54 @@ +"use strict" + +var Model = require("./model"); + +var Vocabulary = require("../wType/vocabulary"); +var Address = require("../wType/address"); +var String = require("../wType/string"); +var Theme = require("./theme") + +var ThemeStorage = Model.inherit({ + "className": "ThemeStorage", + "constructor": function(address) { + Model.fn.constructor.call(this, address); + + this._dtn = "budgie"; + this._data = new Vocabulary(); + + this.addHandler("get"); + this._initThemes(); + }, + "destructor": function() { + this._data.destructor(); + + Model.fn.destructor.call(this); + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + }, + "_h_get": function(ev) { + var vc = new Vocabulary(); + + vc.insert("data", this._data.clone()); + vc.insert("default", new String(this._dtn)); + this.response(vc, "get", ev); + }, + "_initThemes": function() { + var budgie = new Theme(this._address["+"](new Address(["budgie"])), "budgie"); + this.insert(budgie); + }, + "insert": function(theme) { + this.addModel(theme); + this._data.insert(theme.getName(), theme.getAddress()); + + var vc = new Vocabulary(); + vc.insert("name", new String(theme.getName())); + vc.insert("address", theme.getAddress()); + + this.broadcast(vc, "insertion"); + } +}); + +module.exports = ThemeStorage; diff --git a/libjs/wModel/vocabulary.js b/libjs/wModel/vocabulary.js new file mode 100644 index 0000000..34f281c --- /dev/null +++ b/libjs/wModel/vocabulary.js @@ -0,0 +1,79 @@ +"use strict"; + +var Model = require("./model"); +var Vector = require("../wType/vector"); +var Vocabulary = require("../wType/vocabulary"); +var Object = require("../wType/object") +var String = require("../wType/string"); + +var ModelVocabulary = Model.inherit({ + "className": "Vocabulary", + "constructor": function(address) { + Model.fn.constructor.call(this, address); + + this._data = new Vocabulary(); + + this.addHandler("get"); + }, + "destructor": function() { + this._data.destructor(); + + Model.fn.destructor.call(this); + }, + "clear": function() { + this._data.clear(); + + if (this._regestered) { + this.broadcast(new Vocabulary(), "clear"); + } + }, + "erase": function(key) { + this._data.erase(key); + + if (this._registered) { + var vc = new Vocabulary(); + var insert = new Vocabulary(); + var erase = new Vector(); + + erase.push(new String(key)); + vc.insert("insert", insert); + vc.insert("erase", erase); + + this.broadcast(vc, "change"); + } + }, + "_h_subscribe": function(ev) { + Model.fn._h_subscribe.call(this, ev); + + this._h_get(ev); + }, + "_h_get": function(ev) { + var vc = new Vocabulary(); + + vc.insert("data", this._data.clone()); + this.response(vc, "get", ev); + }, + "insert": function(key, value) { + if (this._registered) { + var vc = new Vocabulary(); + var insert = new Vocabulary(); + var erase = new Vector(); + + if (this._data.has(key)) { + erase.push(new String(key)); + } + this._data.insert(key, value); + insert.insert(key, value.clone()); + + vc.insert("insert", insert); + vc.insert("erase", erase); + + this.broadcast(vc, "change"); + + } else { + this._data.insert(key, value); + } + } +}); + +module.exports = ModelVocabulary; diff --git a/libjs/wTest/CMakeLists.txt b/libjs/wTest/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/libjs/wTest/abstractlist.js b/libjs/wTest/abstractlist.js new file mode 100644 index 0000000..677db73 --- /dev/null +++ b/libjs/wTest/abstractlist.js @@ -0,0 +1,136 @@ +"use strict"; +var WTest = require("./test"); +var AbstractList = require("../wContainer/abstractlist"); + +var String = require("../wType/string"); + +var TAbsctractList = WTest.inherit({ + "className": "TAbsctractList", + "constructor": function() { + WTest.fn.constructor.call(this, "AbstractList"); + + var List = AbstractList.template(String); + this._list = new List(); + + this._actions.push(this.testBeginEnd); + this._actions.push(this.testSize); + this._actions.push(this.testIterators); + this._actions.push(this.testErasing); + this._actions.push(this.testClear); + this._actions.push(this.testInsertion); + }, + "destructor": function() { + this._list.destructor(); + + WTest.fn.destructor.call(this); + }, + "testBeginEnd": function() { + if (!this._list.begin()["=="](this._list.end())) { + throw new Error("problem with empty list"); + } + }, + "testSize": function() { + this._list.push_back(new String("h")); + + if (this._list.size() !== 1) { + throw new Error("problem with size"); + } + }, + "testInsertion": function() { + var str1 = new String("one"); + this._list.insert(str1, this._list.end()); + + if (!this._list.begin()["*"]()["=="](str1)) { + throw new Error("Problem with insertion to an empty list"); + } + + var str2 = new String("two"); + this._list.insert(str2, this._list.begin()); + + if (!this._list.begin()["*"]()["=="](str2)) { + throw new Error("Problem with insertion to the beginning of the list"); + } + + var itr = this._list.begin(); + itr["++"](); + var str3 = new String("oneAndAHalf"); + this._list.insert(str3, itr); + + itr["--"](); + if (!itr["*"]()["=="](str3)) { + throw new Error("Problem with insertion to the middle of the list"); + } + + var arr = [str2, str3, str1]; + var itr1 = this._list.begin(); + for (var i = 0; i < arr.length; ++i, itr1["++"]()) { + if (!itr1["*"]()["=="](arr[i])) { + throw new Error("Problem with the order of elements in list after insertion"); + } + } + }, + "testIterators": function() { + var beg = this._list.begin(); + var end = this._list.end(); + + beg["++"](); + end["--"](); + + if (!beg["=="](this._list.end())) { + throw new Error("problem with iterator incrementation"); + } + if (!end["=="](this._list.begin())) { + throw new Error("problem with iterator decrementation"); + } + + this._list.pop_back(); + + if (!this._list.begin()["=="](this._list.end())) { + throw new Error("problem with empty list"); + } + }, + "testErasing": function() { + this._list.push_back(new String("h")); + this._list.push_back(new String("e")); + this._list.push_back(new String("l")); + this._list.push_back(new String("l")); + this._list.push_back(new String("o")); + this._list.push_back(new String(",")); + this._list.push_back(new String(" ")); + this._list.push_back(new String("w")); + this._list.push_back(new String("w")); + + var itr = this._list.end(); + itr["--"](); + + this._list.push_back(new String("o")); + this._list.push_back(new String("r")); + this._list.push_back(new String("l")); + this._list.push_back(new String("d")); + this._list.push_back(new String("!")); + + this._list.erase(itr); + + var beg = this._list.begin(); + var end = this._list.end(); + + var str = new String(); + + for (; !beg["=="](end); beg["++"]()) { + str["+="](beg["*"]()); + } + + if (str.toString() !== "hello, world!") { + throw new Error("Error push back and erasing"); + } + }, + "testClear": function() { + this._list.clear(); + + if (!this._list.begin()["=="](this._list.end())) { + throw new Error("problem with empty list"); + } + } +}); + +module.exports = TAbsctractList; diff --git a/libjs/wTest/abstractmap.js b/libjs/wTest/abstractmap.js new file mode 100644 index 0000000..c7cddfc --- /dev/null +++ b/libjs/wTest/abstractmap.js @@ -0,0 +1,78 @@ +"use strict"; +var WTest = require("./test"); +var AbstractMap = require("../wContainer/abstractmap"); + +var String = require("../wType/string"); +var Address = require("../wType/address"); + +var TAbsctractMap = WTest.inherit({ + "className": "TAbsctractMap", + "constructor": function() { + WTest.fn.constructor.call(this, "AbstractMap"); + + var Map = AbstractMap.template(Address, String); + this._map = new Map(); + + this._actions.push(this.testEnd); + this._actions.push(this.testSize); + this._actions.push(this.testIterators); + this._actions.push(this.testClear); + }, + "destructor": function() { + this._map.destructor(); + + WTest.fn.destructor.call(this); + }, + "testEnd": function() { + var noEl = this._map.find(new Address(["addr1", "hop1"])); + if (!noEl["=="](this._map.end())) { + throw new Error("problem with end!"); + } + }, + "testSize": function() { + this._map.insert(new Address(["addr1", "hop1"]), new String("hello")); + this._map.insert(new Address(["addr2", "hop2"]), new String("world")); + this._map.insert(new Address(["addr2", "/hop2"]), new String("world2")); + this._map.insert(new Address(["addr2", "/hop2", "hop4"]), new String("world3")); + + if (this._map.size() !== 4) { + throw new Error("problem with insertion!"); + } + }, + "testIterators": function() { + var itr = this._map.find(new Address(["addr1", "hop1"])); + + if (itr["=="](this._map.end())) { + throw new Error("problem with finding!"); + } + + if (itr["*"]().second.toString() !== "hello") { + throw new Error("wrong element found"); + } + itr["++"](); + if (itr["*"]().second.toString() !== "world2") { + throw new Error("iterator dereferenced into wrong element after incrementetion"); + } + + this._map.erase(itr); + itr = this._map.find(new Address(["addr2", "/hop2"])); + + if (!itr["=="](this._map.end())) { + throw new Error("problem with erasing!"); + } + + itr = this._map.find(new Address(["addr2", "/hop2", "hop4"])); + if (itr["=="](this._map.end()) || itr["*"]().second.toString() !== "world3") { + throw new Error("Something is wrong with finding"); + } + }, + "testClear": function() { + this._map.clear(); + + if (this._map.size() > 0) { + throw new Error("problem with clearing!"); + } + } +}); + +module.exports = TAbsctractMap; diff --git a/libjs/wTest/abstractorder.js b/libjs/wTest/abstractorder.js new file mode 100644 index 0000000..ee1bd35 --- /dev/null +++ b/libjs/wTest/abstractorder.js @@ -0,0 +1,93 @@ +"use strict"; +var WTest = require("./test"); +var AbstractOrder = require("../wContainer/abstractorder"); +var Address = require("../wType/address"); + +var TAbstractOrder = WTest.inherit({ + "className": "TAbstractOrder", + "constructor": function() { + WTest.fn.constructor.call(this, "AbstractOrder"); + + var Order = AbstractOrder.template(Address); + this._order = new Order(); + + this._actions.push(this.testSize); + this._actions.push(this.testIterators); + this._actions.push(this.testEmpty); + this._actions.push(this.testPushBackFind); + }, + "destructor": function() { + this._order.destructor(); + + WTest.fn.destructor.call(this); + }, + "testSize": function() { + var addr1 = new Address(["hop1", "hop2"]); + this._order.push_back(addr1); + + if (this._order.size() !== 1) { + throw new Error("problem with size"); + } + }, + "testIterators": function() { + var addr1 = new Address(["hop1", "hop2"]); + + var begin = this._order.begin(); + var itr = begin.clone(); + + if (!begin["*"]()["=="](addr1)) { + throw new Error("problem with iterator"); + } + + itr["++"](); + if (!itr["=="](this._order.end())) { + throw new Error("problem with iterator, end"); + } + + if (!this._order.find(addr1)["=="](begin)) { + throw new Error("problem with finding"); + } + + this._order.erase(addr1); + + if (addr1.toString() !== new Address(["hop1", "hop2"]).toString()) { + throw new Error("key have been destroyed afrer eresing element"); + } + }, + "testEmpty": function() { + if (!this._order.begin()["=="](this._order.end())) { + throw new Error("error: problem with empty order"); + } + }, + "testPushBackFind": function() { + this._order.push_back(new Address(["hop1", "hop2"])) + this._order.push_back(new Address(["hop1", "hop3"])) + this._order.push_back(new Address(["hop1", "hop4"])) + this._order.push_back(new Address(["hop1", "hop5"])) + this._order.push_back(new Address(["hop1", "hop6"])) + this._order.push_back(new Address(["hop1", "hop7"])) + + if (this._order.size() !== 6) { + throw new Error("problem with size"); + } + + var itr = this._order.find(new Address(["hop1", "hop4"])); + var end = this._order.end(); + var arr = [ + new Address(["hop1", "hop4"]), + new Address(["hop1", "hop5"]), + new Address(["hop1", "hop6"]), + new Address(["hop1", "hop7"]) + ] + var i = 0; + + for (; !itr["=="](end); itr["++"]()) { + if (!itr["*"]()["=="](arr[i])) { + throw new Error("problem with finding element in the middle and iteration to the end"); + } + ++i; + } + } +}); + +module.exports = TAbstractOrder; \ No newline at end of file diff --git a/libjs/wTest/test.js b/libjs/wTest/test.js new file mode 100644 index 0000000..55bd378 --- /dev/null +++ b/libjs/wTest/test.js @@ -0,0 +1,29 @@ +"use strict"; + +var Class = require("../utils/subscribable"); + +var Test = Class.inherit({ + "className": "Test", + "constructor": function(name) { + Class.fn.constructor.call(this); + + this._name = name; + this._actions = []; + }, + "run": function() { + this.trigger("start", this._name); + var succsess = this._actions.length; + for (var i = 0; i < this._actions.length; ++i) { + this.trigger("progress", this._name, i + 1, this._actions.length); + try { + this._actions[i].call(this); + } catch (e) { + this.trigger("fail", this._name, i + 1, e); + --succsess; + } + } + this.trigger("end", this._name, succsess, this._actions.length); + } +}); + +module.exports = Test; \ No newline at end of file diff --git a/libjs/wTest/uint64.js b/libjs/wTest/uint64.js new file mode 100644 index 0000000..1e3d5ca --- /dev/null +++ b/libjs/wTest/uint64.js @@ -0,0 +1,40 @@ +"use strict"; +var WTest = require("./test"); +var Uint64 = require("../wType/uint64"); + +var TUint64 = WTest.inherit({ + "className": "TUint64", + "constructor": function() { + WTest.fn.constructor.call(this, "Uint64"); + + this._actions.push(this.testEq); + this._actions.push(this.testGt); + this._actions.push(this.testLt); + }, + "testEq": function() { + var first = new Uint64(5); + var second = new Uint64(5); + + if (!first["=="](second)) { + throw new Error("problem with equals low"); + } + }, + "testGt": function() { + var first = new Uint64(5); + var second = new Uint64(4); + + if (!first[">"](second)) { + throw new Error("problem with greater low"); + } + }, + "testLt": function() { + var first = new Uint64(4); + var second = new Uint64(5); + + if (!first["<"](second)) { + throw new Error("problem with lower low"); + } + } +}); + +module.exports = TUint64; diff --git a/libjs/wType/CMakeLists.txt b/libjs/wType/CMakeLists.txt new file mode 100644 index 0000000..7fbd3d8 --- /dev/null +++ b/libjs/wType/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(string.js string.js) +configure_file(bytearray.js bytearray.js) +configure_file(object.js object.js) +configure_file(uint64.js uint64.js) +configure_file(vocabulary.js vocabulary.js) +configure_file(vector.js vector.js) +configure_file(address.js address.js) +configure_file(event.js event.js) +configure_file(boolean.js boolean.js) +configure_file(blob.js blob.js) diff --git a/libjs/wType/address.js b/libjs/wType/address.js new file mode 100644 index 0000000..dcf8d2f --- /dev/null +++ b/libjs/wType/address.js @@ -0,0 +1,228 @@ +"use strict"; +var Object = require("./object"); +var String = require("./string"); + +var Address = Object.inherit({ + "className": "Address", + "constructor": function(data) { + Object.fn.constructor.call(this); + + this._data = []; + this._parseSource(data || []); + }, + "destructor": function() { + this.clear(); + + Object.fn.destructor.call(this); + }, + "<": function(other) { + if (!(other instanceof Address)) { + throw new Error("Can compare Address only with Address"); + } + var hopMe; + var hopOt; + + for (var i = 0; i < this._data.length; ++i) { + hopMe = this._data[i]; + hopOt = other._data[i]; + + if (hopOt === undefined) { + return false; + } + + if (hopMe["<"](hopOt)) { + return true; + } + if (hopMe[">"](hopOt)) { + return false; + } + } + return this._data.length < other._data.length; + }, + ">": function(other) { + if (!(other instanceof Address)) { + throw new Error("Can compare Address only with Address"); + } + var hopMe; + var hopOt; + + for (var i = 0; i < this._data.length; ++i) { + hopMe = this._data[i]; + hopOt = other._data[i]; + + if (hopOt === undefined) { + return true; + } + + if (hopMe[">"](hopOt)) { + return true; + } + if (hopMe["<"](hopOt)) { + return false; + } + } + return this._data.length > other._data.length; + }, + "==": function(other) { + if (this.getType() !== other.getType()) { + return false; + } + + if (this._data.length !== other._data.length) { + return false; + } + + var hopMe; + var hopOt; + + for (var i = 0; i < this._data.length; ++i) { + hopMe = this._data[i]; + hopOt = other._data[i]; + if ( !(hopMe["=="](hopOt)) ) { + return false; + } + } + return true; + }, + "+": function(other) { + var res = this.clone(); + res["+="](other); + return res; + }, + "+=": function(other) { + if (other instanceof Address) { + for (var i = 0; i < other._data.length; ++i) { + this._data.push(other._data[i].clone()); + } + } else { + throw new Error("Can add to Address only Address"); + } + + return this; + }, + "<<": function(n) { + var res = new Address(); + for (var i = n; i < this._data.length; ++i) { + res._data.push(this._data[i].clone()); + } + return res; + }, + ">>": function(n) { + var res = new Address(); + for (var i = 0; i < this._data.length - n; ++i) { + res._data.push(this._data[i].clone()); + } + return res; + }, + "clear": function() { + for (var i = 0; i < this._data.length; ++i) { + this._data[i].destructor(); + } + this._data = []; + }, + "clone": function() { + var clone = new Address(); + + for (var i = 0; i < this._data.length; ++i) { + clone._data.push(this._data[i].clone()); + } + + return clone; + }, + "deserialize": function(ba) { + this.clear() + var length = ba.pop32(); + + for (var i = 0; i < length; ++i) { + var hop = new String(); + hop.deserialize(ba); + this._data.push(hop); + } + }, + "serialize": function(ba) { + ba.push32(this._data.length) + + for (var i = 0; i < this._data.length; ++i) { + this._data[i].serialize(ba); + } + }, + "length": function() { + return this._data.length; + }, + "size": function() { + var size = 4; + for (var i = 0; i < this._data.length; ++i) { + size += this._data[i].size(); + } + + return size; + }, + "toString": function() { + var str = ""; + str += "[" + + for (var i = 0; i < this._data.length; ++i) { + if (i !== 0) { + str +=", "; + } + str += this._data[i].toString(); + } + + str += "]"; + return str; + }, + "begins": function(other) { + var size = other._data.length; + if (size > this._data.length) { + return false; + } + + for (var i = 0; i < size; ++i) { + var myHop = this._data[i]; + var othHop = other._data[i]; + + if (!myHop["=="](othHop)) { + return false; + } + } + + return true; + }, + "ends": function(other) { + var size = other._data.length; + if (size > this._data.length) { + return false; + } + + for (var i = 1; i <= size; ++i) { + var myHop = this._data[this._data.length - i]; + var othHop = other._data[other._data.length - i]; + + if (!myHop["=="](othHop)) { + return false; + } + } + + return true; + }, + "back": function() { + return this._data[this._data.length - 1].clone(); + }, + "front": function() { + return this._data[0].clone(); + }, + "toArray": function() { + var arr = []; + for (var i = 0; i < this._data.length; ++i) { + arr.push(this._data[i].toString()); + } + return arr; + }, + "_parseSource": function(data) { + for (var i = 0; i < data.length; ++i) { + this._data.push(new String(data[i])); + } + } +}); + +module.exports = Address; diff --git a/libjs/wType/blob.js b/libjs/wType/blob.js new file mode 100644 index 0000000..3aa38de --- /dev/null +++ b/libjs/wType/blob.js @@ -0,0 +1,65 @@ +"use strict"; + +var Object = require("./object"); + +var Blob = Object.inherit({ + "className": "Blob", + "constructor": function(/*ArrayBuffer*/data) { + Object.fn.constructor.call(this); + + data = data || new ArrayBuffer(0); + this._data = data; + }, + "==": function(other) { + if (this.getType() !== other.getType()) { + return false; + } + + return this.size() == other.size(); //TODO let's pretend one shall never wish to compare blobs) + }, + "base64": function() { + var arr = new Uint8Array(this._data); + var bin = ""; + for (var i = 0; i < arr.length; ++i) { + bin += String.fromCharCode(arr[i]); + } + + return btoa(bin); + }, + "clone": function() { + var clone = new Blob(this._data.slice(0)); + + return clone; + }, + "deserialize": function(ba) { + var length = ba.pop32(); + this._data = new ArrayBuffer(length); + var view = new Uint8Array(this._data); + + for (var i = 0; i < length; ++i) { + view[i] = ba.pop8(); + } + }, + "length": function() { + return this._data.byteLength; + }, + "serialize": function(ba) { + ba.push32(this._data.byteLength); + var view = new Uint8Array(this._data); + + for (var i = 0; i < view.length; ++i) { + ba.push8(view[i]); + } + }, + "size": function() { + return this._data.byteLength + 4; + }, + "toString": function() { + return "File <" + this._data.byteLength + ">"; + }, + "valueOf": function() { + return this._data; + } +}); + +module.exports = Blob; diff --git a/libjs/wType/boolean.js b/libjs/wType/boolean.js new file mode 100644 index 0000000..1cc959c --- /dev/null +++ b/libjs/wType/boolean.js @@ -0,0 +1,62 @@ +"use strict"; +var Object = require("./object"); + +var Boolean = Object.inherit({ + "className": "Boolean", + "constructor": function(bool) { + Object.fn.constructor.call(this); + + this._data = bool === true; + }, + "<": function(other) { + if (!(other instanceof Boolean)) { + throw new Error("Can compare Boolean only with Boolean"); + } + return this._data < other._data; + }, + ">": function(other) { + if (!(other instanceof Boolean)) { + throw new Error("Can compare Boolean only with Boolean"); + } + return this._data > other._data; + }, + "==": function(other) { + if (this.getType() !== other.getType()) { + return false; + } + return this._data === other._data; + }, + "clone": function() { + return new Boolean(this._data); + }, + "deserialize": function(ba) { + var int = ba.pop8(); + + if (int === 253) { + this._data = true; + } else { + this._data = false; + } + }, + "length": function() { + return 1; + }, + "size": function() { + return 1; + }, + "serialize": function(ba) { + if (this._data) { + ba.push8(253); + } else { + ba.push8(0); + } + }, + "toString": function() { + return this._data.toString(); + }, + "valueOf": function() { + return this._data; + } +}); + +module.exports = Boolean; diff --git a/libjs/wType/bytearray.js b/libjs/wType/bytearray.js new file mode 100644 index 0000000..6bb6b14 --- /dev/null +++ b/libjs/wType/bytearray.js @@ -0,0 +1,106 @@ +"use strict"; +var Class = require("../utils/class"); + +var ByteArray = Class.inherit({ + "className": "ByteArray", + "constructor": function(size) { + Class.fn.constructor.call(this); + + this._referenceMode = false; + this._data = new Uint8Array(size); + this._shiftBegin = 0; + this._shiftEnd = 0; + }, + "_checkReference": function() { + if (this._referenceMode) { + var buffer = new ArrayBuffer(this._data.length - this._shiftBegin); + var newData = new Uint8Array(buffer); + newData.set(this._data, this._shiftBegin); + this._data = newData; + this._shiftBegin = 0; + this._referenceMode = false; + } + }, + "fill": function(/*Uint8Array*/arr, /*Number*/size, /*[Number]*/shift) { + this._checkReference(); + shift = shift || 0; + + if (this._shiftEnd === 0 && (this._data.length <= size - shift)) { + this._referenceMode = true; + this._data = arr.subarray(shift, this._data.length + shift); + this._shiftEnd = this._data.length; + shift += this._shiftEnd; + } else { + while (!this.filled() && shift < size) { + this._data[this._shiftEnd] = arr[shift]; + ++shift; + ++this._shiftEnd; + } + } + + return shift; + }, + "filled": function() { + return this._data.length === this._shiftEnd; + }, + "size": function() { + return this._shiftEnd - this._shiftBegin; + }, + "maxSize": function() { + return this._data.length; + }, + "push8": function(int) { + this._checkReference(); + + this._data[this._shiftEnd] = int; + ++this._shiftEnd; + }, + "push16": function(int) { + var h = (int >> 8) & 0xff; + var l = int & 0xff; + + this.push8(h); + this.push8(l); + }, + "push32": function(int) { + var hh = (int >> 24) & 0xff; + var hl = (int >> 16) & 0xff; + var lh = (int >> 8) & 0xff; + var ll = int & 0xff; + + this.push8(hh); + this.push8(hl); + this.push8(lh); + this.push8(ll); + }, + "push64": function(int) { + + }, + "pop8": function(int) { + var ret = this._data[this._shiftBegin]; + ++this._shiftBegin; + return ret; + }, + "pop16": function(int) { + var ret = (this.pop8() << 8); + ret = ret | this.pop8(); + + return ret; + }, + "pop32": function(int) { + var ret = this.pop8() << 24; + ret = ret | (this.pop8() << 16); + ret = ret | (this.pop8() << 8); + ret = ret | this.pop8(); + + return ret; + }, + "pop64": function(int) { + + }, + "data": function() { + return this._data.subarray(this._shiftBegin, this._shiftEnd); + } +}); + +module.exports = ByteArray; diff --git a/libjs/wType/event.js b/libjs/wType/event.js new file mode 100644 index 0000000..0f9e95b --- /dev/null +++ b/libjs/wType/event.js @@ -0,0 +1,121 @@ +"use strict"; +var Object = require("./object"); +var Uint64 = require("./uint64"); +var Address = require("./address"); +var Boolean = require("./boolean"); + +var Event = Object.inherit({ + "className": "Event", + "constructor": function(addr, object, isSystem) { + Object.fn.constructor.call(this); + + if (!(object instanceof Object) && object !== undefined) { + throw new Error("Wrong arguments to construct Event"); + } + if (!(addr instanceof Address) && addr !== undefined) { + throw new Error("Wrong arguments to construct Event"); + } + + this._destination = addr !== undefined ? addr : new Address(); + this._data = object !== undefined ? object : new Object(); + this._senderId = new Uint64(); + this._system = new Boolean(isSystem); + }, + "destructor": function() { + this.clear(); + + Object.fn.destructor.call(this); + }, + "==": function(other) { + if (this.getType() !== other.getType()) { + return false; + } + return this._destination["=="](other._destination) && + this._system["=="](other._system) && + this._senderId["=="](other._senderId) && + this._data["=="](other._data) + }, + "clear": function() { + this._system.destructor(); + this._destination.destructor(); + this._senderId.destructor(); + this._data.destructor(); + }, + "clone": function() { + var clone = new Event(); + + clone._destination = this._destination.clone(); + clone._data = this._data.clone(); + clone._senderId = this._senderId.clone(); + clone._system = this._system.clone(); + + return clone; + }, + "deserialize": function(ba) { + this._system = new Boolean(); + this._system.deserialize(ba); + + if (!this.isSystem()) { + this._destination = new Address(); + this._destination.deserialize(ba); + + this._senderId = new Uint64(); + this._senderId.deserialize(ba); + } + + this._data = Object.fromByteArray(ba); + }, + "getData": function() { + return this._data; + }, + "getDestination": function() { + return this._destination; + }, + "getSenderId": function() { + return this._senderId; + }, + "isSystem": function() { + return this._system.valueOf(); + }, + "length": function() { + return 2 + this._destination.length() + this._data.length(); + }, + "serialize": function(ba) { + this._system.serialize(ba); + + if (!this.isSystem()) { + this._destination.serialize(ba); + this._senderId.serialize(ba); + } + + ba.push8(this._data.getType()); + this._data.serialize(ba); + }, + "setSenderId": function(id) { + if (!(id instanceof Uint64)) { + throw new Error("Can't set id, which is not Uint64"); + } + this._senderId = id; + }, + "size": function() { + var size = this._system.size() + this._data.size() + 1 + if (!this.isSystem()) { + size += this._senderId.size() + this._destination.size(); + } + return size; + }, + "toString": function() { + var str = "{"; + + str += "system: " + this._system.toString(); + str += " destination: " + this._destination.toString(); + str += " sender: " + this._senderId.toString(); + str += " data: " + this._data.toString(); + + str += "}"; + + return str; + } +}); + +module.exports = Event; diff --git a/libjs/wType/factory.js b/libjs/wType/factory.js new file mode 100644 index 0000000..b4097ae --- /dev/null +++ b/libjs/wType/factory.js @@ -0,0 +1,42 @@ +"use strict"; + +var Object = require("./object"); +var types = { + "String" : require("./string"), + "Vocabulary": require("./vocabulary"), + "Uint64" : require("./uint64"), + "Address" : require("./address"), + "Boolean" : require("./boolean"), + "Event" : require("./event"), + "Vector" : require("./vector"), + "Blob" : require("./blob") +} + +var storage = global.Object.create(null); + +for (var name in types) { + if (types.hasOwnProperty(name)) { + var typeId = Object.objectType[name]; + if (typeId === undefined) { + throw new Error("wType initialization error - can't find type id for type " + name); + } + storage[typeId] = types[name]; + } +} + +function create(/*ByteArray*/ba) { + var type = ba.pop8(); + var Type = storage[type]; + + if (Type === undefined) { + throw new Error("Unsupported data type found during deserialization: " + type); + } + + var obj = new Type(); + obj.deserialize(ba); + + return obj; +} + +Object.fromByteArray = create; +module.exports = create; diff --git a/libjs/wType/object.js b/libjs/wType/object.js new file mode 100644 index 0000000..de47a58 --- /dev/null +++ b/libjs/wType/object.js @@ -0,0 +1,70 @@ +"use strict"; +var Class = require("../utils/class"); + +var Object = Class.inherit({ + "className": "Object", + "constructor": function() { + Class.fn.constructor.call(this); + }, + "<": function(other) { + throw new Error(this.className + " has no reimplemented method \"<\""); + }, + ">": function(other) { + throw new Error(this.className + " has no reimplemented method \">\""); + }, + "==": function(other) { + throw new Error(this.className + " has no reimplemented method \"==\""); + }, + "clone": function() { + throw new Error(this.className + " has no reimplemented method \"clone\""); + }, + "getType": function() { + var type = Object.objectType[this.className]; + + if (type === undefined) { + throw new Error("Undefined type of " + this.className); + } + + return type; + }, + "length": function() { + throw new Error(this.className + " has no reimplemented method \"length\""); + }, + "size": function() { + throw new Error(this.className + " has no reimplemented method \"size\""); + }, + "toString": function() { + throw new Error(this.className + " has no reimplemented method \"toString\""); + }, + "valueOf": function() { + throw new Error(this.className + " has no reimplemented method \"valueOf\""); + } +}); + +Object.objectType = { + "String" : 0, + "Vocabulary": 1, + "Uint64" : 2, + "Address" : 3, + "Boolean" : 4, + "Event" : 5, + "Vector" : 6, + "Blob" : 7 +}; + +Object.reverseObjectType = { + 0 : "String", + 1 : "Vocabulary", + 2 : "Uint64", + 3 : "Address", + 4 : "Boolean", + 5 : "Event", + 6 : "Vector", + 7 : "Blob" +} + +Object.fromByteArray = function() { + throw new Error("Initialization error. Object.fromByteArray is not implemented, it implements in factory.js"); +} + +module.exports = Object; diff --git a/libjs/wType/string.js b/libjs/wType/string.js new file mode 100644 index 0000000..d395fbc --- /dev/null +++ b/libjs/wType/string.js @@ -0,0 +1,84 @@ +"use strict"; +var Object = require("./object"); + +var String = Object.inherit({ + "className": "String", + "constructor": function(source) { + Object.fn.constructor.call(this); + + this._data = ""; + this._parseSource(source || ""); + }, + "destructor": function () { + this.clear(); + + Object.fn.destructor.call(this); + }, + "<": function(other) { + if (!(other instanceof String)) { + throw new Error("Can compare String only with String"); + } + return this._data < other._data; + }, + ">": function(other) { + if (!(other instanceof String)) { + throw new Error("Can compare String only with String"); + } + return this._data > other._data; + }, + "==": function(other) { + if (this.getType() !== other.getType()) { + return false; + } + + return this._data === other._data; + }, + "+=": function(str) { + this._data += str.toString(); + }, + "clear": function() { + this._data = ""; + }, + "clone": function() { + var clone = new String(this._data); + + return clone; + }, + "deserialize": function(ba) { + this.clear(); + var size = ba.pop32(); + + for (var i = 0; i < size; ++i) { + var cc = ba.pop16(); + this._data += global.String.fromCharCode(cc); + } + }, + "length": function() { + return this._data.length; + }, + "serialize": function(ba) { + ba.push32(this._data.length); + + for (var i = 0; i < this._data.length; ++i) { + var code = this._data.charCodeAt(i); + ba.push16(code); + } + }, + "size": function() { + return this._data.length * 2 + 4; + }, + "toString": function() { + return this._data; + }, + "valueOf": function() { + return this.toString(); + }, + "_parseSource": function(source) { + if (typeof source !== "string") { + throw new Error("Wrong argument to construct String"); + } + this._data = source; + } +}); + +module.exports = String; diff --git a/libjs/wType/uint64.js b/libjs/wType/uint64.js new file mode 100644 index 0000000..ae74698 --- /dev/null +++ b/libjs/wType/uint64.js @@ -0,0 +1,95 @@ +"use strict"; +var Object = require("./object"); + +var Uint64 = Object.inherit({ + "className": "Uint64", + "constructor": function(int) { + Object.fn.constructor.call(this); + + this._h = 0; + this._l = 0; + + this._parseSource(int || 0); + }, + "<": function(other) { + if (!(other instanceof Uint64)) { + throw new Error("Can compare Uint64 only with Uint64"); + } + if (this._h < other._h) { + return true; + } else if(this._h === other._h) { + return this._l < other._l; + } else { + return false; + } + }, + ">": function(other) { + if (!(other instanceof Uint64)) { + throw new Error("Can compare Uint64 only with Uint64"); + } + if (this._h > other._h) { + return true; + } else if(this._h === other._h) { + return this._l > other._l; + } else { + return false; + } + }, + "==": function(other) { + if (this.getType() !== other.getType()) { + return false; + } + return (this._h == other._h) && (this._l == other._l); + }, + "++": function() { + ++this._l; + if (this._l === 4294967296) { + this._l = 0; + ++this._h; + } + }, + "clone": function() { + var clone = new Uint64(); + clone._l = this._l; + clone._h = this._h; + + return clone; + }, + "deserialize": function(ba) { + this._h = ba.pop32(); + this._l = ba.pop32(); + }, + "length": function() { + return 1; + }, + "serialize": function(ba) { + ba.push32(this._h); + ba.push32(this._l); + }, + "size": function() { + return 8; + }, + "toString": function() { + if (this._h !== 0) { + console.log(this._h); + console.log(this._l); + throw new Error("Don't know yet how to show uint64 in javascript"); + } + return this._l.toString(); + }, + "valueOf": function() { + if (this._h !== 0) { + throw new Error("Don't know yet how to show uint64 in javascript"); + } + return this._l; + }, + "_parseSource": function(int) { + if (parseInt(int) !== int) { + throw new Error("Wrong argument to construct Uint64"); + } + + this._l = int & 0xffffffff; + } +}); + +module.exports = Uint64; diff --git a/libjs/wType/vector.js b/libjs/wType/vector.js new file mode 100644 index 0000000..bff3173 --- /dev/null +++ b/libjs/wType/vector.js @@ -0,0 +1,112 @@ +"use strict"; +var Object = require("./object"); + +var Vector = Object.inherit({ + "className": "Vector", + "constructor": function() { + Object.fn.constructor.call(this); + + this._data = []; + }, + "destructor": function() { + this.clear(); + + Object.fn.destructor.call(this); + }, + "==": function(other) { + if (this.getType() !== other.getType()) { + return false; + } + + if (this._data.length !== other._data.length) { + return false; + } + + var hopMe; + var hopOt; + + for (var i = 0; i < this._data.length; ++i) { + hopMe = this._data[i]; + hopOt = other._data[i]; + if ( !(hopMe["=="](hopOt)) ) { + return false; + } + } + return true; + }, + "at": function(index) { + return this._data[index]; + }, + "clear": function() { + for (var i = 0; i < this._data.length; ++i) { + this._data[i].destructor(); + } + + this._data = []; + }, + "clone": function() { + var clone = new Vector(); + + for (var i = 0; i < this._data.length; ++i) { + clone.push(this._data[i].clone()); + } + + return clone; + }, + "deserialize": function(ba) { + this.clear(); + + var length = ba.pop32(); + + for (var i = 0; i < length; ++i) { + var value = Object.fromByteArray(ba); + this.push(value); + } + }, + "length": function() { + return this._data.length; + }, + "push": function(value) { + if (!(value instanceof Object)) { + throw new Error("An attempt to insert not a W::Object into a vector"); + } + this._data.push(value); + }, + "serialize": function(ba) { + ba.push32(this._data.length); + + for (var i = 0; i < this._data.length; ++i) { + var el = this._data[i]; + ba.push8(el.getType()); + el.serialize(ba); + } + }, + "size": function() { + var size = 4; + + for (var i = 0; i < this._data.length; ++i) { + size += this._data[i].size() + 1; + } + + return size; + }, + "toString": function() { + var str = "["; + + var ft = true; + + for (var i = 0; i < this._data.length; ++i) { + if (ft) { + ft = false; + } else { + str += ", "; + } + str += this._data[i].toString(); + + } + str += "]"; + return str; + } +}); + +module.exports = Vector; diff --git a/libjs/wType/vocabulary.js b/libjs/wType/vocabulary.js new file mode 100644 index 0000000..429979b --- /dev/null +++ b/libjs/wType/vocabulary.js @@ -0,0 +1,155 @@ +"use strict"; +var Object = require("./object"); +var String = require("./string"); + +var Vocabulary = Object.inherit({ + "className": "Vocabulary", + "constructor": function() { + Object.fn.constructor.call(this); + + this._data = global.Object.create(null); + this._length = 0; + }, + "destructor": function() { + this.clear(); + + Object.fn.destructor.call(this); + }, + "==": function(other) { + if (this.getType() !== other.getType()) { + return false; + } + + if (this._length !== other._length) { + return false; + } + + var keysMe = this.getKeys(); + var key; + var mValue; + var oValue; + + for (var i = 0; i < this._length; ++i) { + key = keysMe[i]; + oValue = other._data[key]; + if (oValue === undefined) { + return false; + } + mValue = this._data[key]; + if (!oValue["=="](mValue)) { + return false; + } + } + return true; + }, + "at": function(str) { + return this._data[str]; + }, + "clear": function() { + for (var key in this._data) { + this._data[key].destructor(); + } + + this._data = global.Object.create(null); + this._length = 0; + }, + "clone": function() { + var clone = new Vocabulary(); + + for (var key in this._data) { + clone._data[key] = this._data[key].clone(); + } + clone._length = this._length; + + return clone; + }, + "deserialize": function(ba) { + this.clear(); + + this._length = ba.pop32() + + for (var i = 0; i < this._length; ++i) { + var key = new String(); + key.deserialize(ba); + + var value = Object.fromByteArray(ba); + this._data[key.toString()] = value; + } + }, + "erase": function(key) { + var value = this._data[key]; + if (value === undefined) { + throw new Error("An attempt to erase not existing object from vocabulary"); + } + value.destructor(); + delete this._data[key]; + --this._length; + }, + "getKeys": function() { + return global.Object.keys(this._data); + }, + "has": function(key) { + return this._data[key] instanceof Object; + }, + "insert": function(key, value) { + if (!(value instanceof Object)) { + throw new Error("An attempt to insert not a W::Object into vocabulary"); + } + var oldValue = this._data[key]; + if (oldValue !== undefined) { + oldValue.destructor(); + --this._length; + } + this._data[key] = value + + ++this._length; + }, + "length": function() { + return this._length; + }, + "serialize": function(ba) { + ba.push32(this._length); + + for (var key in this._data) { + var sKey = new String(key); + var value = this._data[key]; + + sKey.serialize(ba); + ba.push8(value.getType()); + value.serialize(ba); + } + }, + "size": function() { + var size = 4; + + for (var key in this._data) { + var sKey = new String(key); + var value = this._data[key]; + + size += sKey.size(); + size += value.size() + 1; + } + + return size; + }, + "toString": function() { + var str = "{"; + + var ft = true; + + for (var key in this._data) { + if (ft) { + ft = false; + } else { + str += ", "; + } + str += key + ": "; + str += this._data[key].toString(); + + } + str += "}"; + return str; + } +}); + +module.exports = Vocabulary; diff --git a/lorgar/CMakeLists.txt b/lorgar/CMakeLists.txt new file mode 100644 index 0000000..29a4d30 --- /dev/null +++ b/lorgar/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 2.8.12) +project(lorgar) + +add_subdirectory(css) +add_subdirectory(lib) +add_subdirectory(test) +add_subdirectory(core) +add_subdirectory(views) + +configure_file(index.html index.html) +configure_file(favicon.ico favicon.ico COPYONLY) +configure_file(main.js main.js) diff --git a/lorgar/core/CMakeLists.txt b/lorgar/core/CMakeLists.txt new file mode 100644 index 0000000..57baab4 --- /dev/null +++ b/lorgar/core/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(lorgar.js lorgar.js) \ No newline at end of file diff --git a/lorgar/core/lorgar.js b/lorgar/core/lorgar.js new file mode 100644 index 0000000..bc19b87 --- /dev/null +++ b/lorgar/core/lorgar.js @@ -0,0 +1,289 @@ +"use strict"; +(function lorgar_js() { + var moduleName = "core/lorgar"; + + var defineArray = []; + defineArray.push("lib/utils/class"); + defineArray.push("lib/wSocket/socket"); + defineArray.push("lib/wDispatcher/dispatcher"); + defineArray.push("lib/wDispatcher/handler"); + defineArray.push("lib/wDispatcher/logger"); + + defineArray.push("lib/wType/event"); + defineArray.push("lib/wType/address"); + defineArray.push("lib/wType/vocabulary"); + defineArray.push("lib/wType/string"); + + defineArray.push("lib/wController/globalControls"); + defineArray.push("lib/wController/pageStorage"); + defineArray.push("lib/wController/page"); + defineArray.push("lib/wController/localModel"); + + defineArray.push("views/view"); + defineArray.push("views/layout"); + defineArray.push("views/gridLayout"); + defineArray.push("views/page"); + defineArray.push("views/mainLayout"); + + define(moduleName, defineArray, function lorgar_module() { + var Class = require("lib/utils/class"); + var Socket = require("lib/wSocket/socket"); + var Dispatcher = require("lib/wDispatcher/dispatcher"); + var Handler = require("lib/wDispatcher/handler"); + var Logger = require("lib/wDispatcher/logger"); + + var Event = require("lib/wType/event"); + var Address = require("lib/wType/address"); + var Vocabulary = require("lib/wType/vocabulary"); + var String = require("lib/wType/string"); + + var GlobalControls = require("lib/wController/globalControls"); + var PageStorage = require("lib/wController/pageStorage"); + var PageController = require("lib/wController/page"); + var LocalModel = require("lib/wController/localModel"); + + var View = require("views/view"); + var Layout = require("views/layout"); + var GridLayout = require("views/gridLayout"); + var Page = require("views/page"); + var MainLayout = require("views/mainLayout"); + + var Lorgar = Class.inherit({ + "className": "Lorgar", + "constructor": function() { + Class.fn.constructor.call(this); + + this._currentPageCtl = undefined; + this._nodes = Object.create(null); + + this._initDispatcher(); + + this._prepareNode("Magnus", "localhost", 8081); + this._prepareNode("Corax", "localhost", 8080); + + this._initModels(); + this._initViews(); + + this.connectNode("Magnus"); + this.connectNode("Corax"); + window.onpopstate = this._onHistoryPopState.bind(this) + }, + "destructor": function() { + window.onpopstate = undefined; + if (this._currentPageCtl) { + this._currentPage.destructor(); + this._currentPageCtl.destructor(); + } + + this._gc.destructor(); + this._ps.destructor(); + this._mainColorHelper.destructor(); + this._emptyHelper.destructor(); + + this._body.destructor(); + + this.coraxSocket.close(); + this.dispatcher.unregisterDefaultHandler(this._logger); + + this._logger.destructor(); + this.dispatcher.destructor(); + //this.magnusSocket.destructor(); + //this.coraxSocket.destructor(); + + Class.fn.destructor.call(this); + }, + "changePage": function(addr) { + if (this._currentPageCtl && this._currentPageCtl.getPairAddress()["=="](addr)) { + return; + } + this._ps.getPageName(addr); + this._initPageController(addr.clone()); + }, + "connectNode": function(name) { + var node = this._nodes[name]; + if (node === undefined) { + throw new Error("An attempt to connect not prepared node " + name); + } + + node.socket.open(node.address, node.port); + }, + "_initCoraxSocket": function() { + this.coraxSocket = new Socket("Lorgar"); + this.coraxSocket.on("connected", this._coraxSocketConnected, this); + this.coraxSocket.on("disconnected", this._coraxSocketDisconnected, this); + this.coraxSocket.on("error", this._coraxSocketError, this); + this.coraxSocket.on("message", this.dispatcher.pass, this.dispatcher); + }, + "_initDispatcher": function() { + this.dispatcher = new Dispatcher(); + this._logger = new Logger(); + this.dispatcher.registerDefaultHandler(this._logger); + }, + "_initModels": function() { + this._gc = new GlobalControls(new Address(["magnus", "gc"])); + this._ps = new PageStorage(new Address(["magnus", "ps"])); + + this._mainColorHelper = new LocalModel({backgroundColor: "mainColor"}); + this._emptyHelper = new LocalModel(); + + this._gc.on("themeSelected", this.setTheme, this); + + this._gc.register(this.dispatcher, this._nodes.Magnus.socket); + this._ps.register(this.dispatcher, this._nodes.Magnus.socket); + + this._ps.on("pageName", this._onPageName, this); + }, + "_initPageController": function(addr) { + if (this._currentPageCtl) { + this._currentPage.destructor(); + this._currentPageCtl.destructor(); + } + this._currentPageCtl = new PageController(addr); + this._currentPageCtl.register(this.dispatcher, this._nodes.Magnus.socket); + this._currentPage = new Page(this._currentPageCtl); + this._currentPageCtl.subscribe(); + this._mainLayout.append(this._currentPage, 1, 1, 1, 1); + }, + "_initViews": function() { + this._body = new Layout(this._emptyHelper); + this._mainLayout = new MainLayout(this._gc); + + document.body.innerHTML = ""; + document.body.appendChild(this._body._e); + window.addEventListener("resize",this._onWindowResize.bind(this) ,false); + + this._body.setSize(document.body.offsetWidth, document.body.offsetHeight); + this._body.append(this._mainLayout); + var spacerL = new View(this._mainColorHelper, { + maxWidth: 50 + }); + var spacerR = new View(this._mainColorHelper, { + maxWidth: 50 + }); + this._mainLayout.append(spacerL, 1, 0, 1, 1); + this._mainLayout.append(spacerR, 1, 2, 1, 1); + }, + "_onHistoryPopState": function(e) { + this._initPageController(new Address(e.state.address)); + }, + "_onPageName": function(name) { + window.history.pushState({ + address: this._currentPageCtl.getPairAddress().toArray() + }, "", name); + }, + "_onSocketConnected": function(name) { + console.log(name + " socket connected"); + var node = this._nodes[name]; + node.connected = true; + + for (var id in node.foreigns) { + if (node.foreigns[id].subscribed) { + node.foreigns[id].controller.subscribe(); + } + } + + if (name === "Magnus") { + this._gc.subscribe(); + + if (!this._currentPageCtl) { + this._ps.getPageAddress(location.pathname); + this._ps.one("pageAddress", this._initPageController, this); + } + } + }, + "_onSocketDisconnected": function(name) { + console.log(name + " socket disconnected"); + var node = this._nodes[name]; + node.connected = false; + + for (var id in node.foreigns) { + if (node.foreigns[id].subscribed) { + node.foreigns[id].controller._onSocketDisconnected; + } + } + }, + "_onSocketError": function(name) { + console.log(name + " socket error: "); + console.log(e); + }, + "_onWindowResize": function() { + this._body.setSize(document.body.offsetWidth, document.body.offsetHeight); + }, + "_prepareNode": function(name, address, port) { + if (this._nodes[name]) { + throw new Error("An attempt to prepeare node " + name + " for the second time"); + } + var obj = Object.create(null); + obj.name = name; + obj.address = address; + obj.port = port; + obj.socket = new Socket("Lorgar"); + obj.connected = false; + obj.foreigns = Object.create(null); + + obj.socket.on("connected", this._onSocketConnected.bind(this, name)); + obj.socket.on("disconnected", this._onSocketDisconnected.bind(this, name)); + obj.socket.on("error", this._onSocketError.bind(this, name)); + obj.socket.on("message", this.dispatcher.pass, this.dispatcher); + + this._nodes[name] = obj; + }, + "registerForeignController": function(node, controller) { + var node = this._nodes[node]; + if (node === undefined) { + throw new Error("An attempt to register controller to an unknown node " + node); + } + + if (node.foreigns[controller.id] !== undefined) { + throw new Error("An attempt to register a controller under node " + node + " for a second time"); + } + var obj = Object.create(null); + obj.controller = controller; + obj.subscribed = false; + node.foreigns[controller.id] = obj; + controller.register(this.dispatcher, node.socket); + }, + "setTheme": function(theme) { + View.setTheme(theme); + }, + "subscribeForeignController": function(node, controller) { + var node = this._nodes[node]; + if (node === undefined) { + throw new Error("An attempt to subscribe a controller to an unknown node " + node); + } + + if (node.foreigns[controller.id] === undefined) { + throw new Error("An attempt to subscribe not registered controller to node " + node); + } + node.foreigns[controller.id].subscribed = true; + controller.subscribe(); + }, + "unregisterForeignController": function(node, controller) { + var node = this._nodes[node]; + if (node === undefined) { + throw new Error("An attempt to unregister a controller from an unknown node " + node); + } + + if (node.foreigns[controller.id] === undefined) { + throw new Error("An attempt to unregister not registered controller from node " + node); + } + delete node.foreigns[controller.id]; + controller.unregister(); + }, + "unsubscribeForeignController": function(node, controller) { + var node = this._nodes[node]; + if (node === undefined) { + throw new Error("An attempt to unsubscribe a controller from an unknown node " + node); + } + + if (node.foreigns[controller.id] === undefined) { + throw new Error("An attempt to unsubscribe not registered controller from node " + node); + } + node.foreigns[controller.id].subscribed = false; + controller.unsubscribe(); + } + }); + + return Lorgar; + }); +})(); diff --git a/lorgar/css/CMakeLists.txt b/lorgar/css/CMakeLists.txt new file mode 100644 index 0000000..2887846 --- /dev/null +++ b/lorgar/css/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(main.css main.css) +configure_file(Liberation.ttf Liberation.ttf COPYONLY) diff --git a/lorgar/css/Liberation.ttf b/lorgar/css/Liberation.ttf new file mode 100644 index 0000000..626dd93 Binary files /dev/null and b/lorgar/css/Liberation.ttf differ diff --git a/lorgar/css/main.css b/lorgar/css/main.css new file mode 100644 index 0000000..4a92774 --- /dev/null +++ b/lorgar/css/main.css @@ -0,0 +1,47 @@ +@font-face { + font-family: Liberation; + src: url(Liberation.ttf); +} + +body { + margin: 0; + width: 100%; + height: 100%; + position: absolute; + overflow: hidden; + box-sizing: border-box; +} + +html { + width: 100%; + height: 100%; +} + +.non-selectable, +.non-selectable * { + -webkit-user-select: none; + -ms-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.hoverable { + +} + +.hoverable:hover { + background-image: linear-gradient(to bottom, rgba(0, 0, 0, .2) 0%, rgba(0, 0, 0, .2) 100%); +} + +.draggable { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: -grab; +} + +div.dragging , +div.dragging .draggable { + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} diff --git a/lorgar/favicon.ico b/lorgar/favicon.ico new file mode 100644 index 0000000..5a6127b Binary files /dev/null and b/lorgar/favicon.ico differ diff --git a/lorgar/index.html b/lorgar/index.html new file mode 100644 index 0000000..7b5c210 --- /dev/null +++ b/lorgar/index.html @@ -0,0 +1,13 @@ + + + + + RadioW + + + + + +

I am

+ + \ No newline at end of file diff --git a/lorgar/lib/CMakeLists.txt b/lorgar/lib/CMakeLists.txt new file mode 100644 index 0000000..b10b75c --- /dev/null +++ b/lorgar/lib/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_subdirectory(requirejs) +add_subdirectory(wSocket) +add_subdirectory(bintrees) +add_subdirectory(wContainer) +add_subdirectory(utils) +add_subdirectory(wType) +add_subdirectory(wDispatcher) +add_subdirectory(wTest) +add_subdirectory(wController) +add_subdirectory(fonts) diff --git a/lorgar/lib/bintrees/CMakeLists.txt b/lorgar/lib/bintrees/CMakeLists.txt new file mode 100644 index 0000000..346394a --- /dev/null +++ b/lorgar/lib/bintrees/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(index.js index.js) \ No newline at end of file diff --git a/lorgar/lib/bintrees/index.js b/lorgar/lib/bintrees/index.js new file mode 100644 index 0000000..f5eec49 --- /dev/null +++ b/lorgar/lib/bintrees/index.js @@ -0,0 +1,478 @@ +"use strict"; +(function rbtree_js() { + + var moduleName = "lib/bintrees/index"; + + var defineArray = []; + + define(moduleName, defineArray, function rbtree_module() { + var require = function(name) { + var fn = require.m[name]; + if (fn.mod) { + return fn.mod.exports; + } + + var mod = fn.mod = { exports: {} }; + fn(mod, mod.exports); + return mod.exports; + }; + + require.m = {}; + require.m['./treebase'] = function(module, exports) { + + function TreeBase() {} + + // removes all nodes from the tree + TreeBase.prototype.clear = function() { + this._root = null; + this.size = 0; + }; + + // returns node data if found, null otherwise + TreeBase.prototype.find = function(data) { + var res = this._root; + + while(res !== null) { + var c = this._comparator(data, res.data); + if(c === 0) { + return res.data; + } + else { + res = res.get_child(c > 0); + } + } + + return null; + }; + + // returns iterator to node if found, null otherwise + TreeBase.prototype.findIter = function(data) { + var res = this._root; + var iter = this.iterator(); + + while(res !== null) { + var c = this._comparator(data, res.data); + if(c === 0) { + iter._cursor = res; + return iter; + } + else { + iter._ancestors.push(res); + res = res.get_child(c > 0); + } + } + + return null; + }; + + // Returns an iterator to the tree node at or immediately after the item + TreeBase.prototype.lowerBound = function(item) { + var cur = this._root; + var iter = this.iterator(); + var cmp = this._comparator; + + while(cur !== null) { + var c = cmp(item, cur.data); + if(c === 0) { + iter._cursor = cur; + return iter; + } + iter._ancestors.push(cur); + cur = cur.get_child(c > 0); + } + + for(var i=iter._ancestors.length - 1; i >= 0; --i) { + cur = iter._ancestors[i]; + if(cmp(item, cur.data) < 0) { + iter._cursor = cur; + iter._ancestors.length = i; + return iter; + } + } + + iter._ancestors.length = 0; + return iter; + }; + + // Returns an iterator to the tree node immediately after the item + TreeBase.prototype.upperBound = function(item) { + var iter = this.lowerBound(item); + var cmp = this._comparator; + + while(iter.data() !== null && cmp(iter.data(), item) === 0) { + iter.next(); + } + + return iter; + }; + + // returns null if tree is empty + TreeBase.prototype.min = function() { + var res = this._root; + if(res === null) { + return null; + } + + while(res.left !== null) { + res = res.left; + } + + return res.data; + }; + + // returns null if tree is empty + TreeBase.prototype.max = function() { + var res = this._root; + if(res === null) { + return null; + } + + while(res.right !== null) { + res = res.right; + } + + return res.data; + }; + + // returns a null iterator + // call next() or prev() to point to an element + TreeBase.prototype.iterator = function() { + return new Iterator(this); + }; + + // calls cb on each node's data, in order + TreeBase.prototype.each = function(cb) { + var it=this.iterator(), data; + while((data = it.next()) !== null) { + cb(data); + } + }; + + // calls cb on each node's data, in reverse order + TreeBase.prototype.reach = function(cb) { + var it=this.iterator(), data; + while((data = it.prev()) !== null) { + cb(data); + } + }; + + + function Iterator(tree) { + this._tree = tree; + this._ancestors = []; + this._cursor = null; + } + + Iterator.prototype.data = function() { + return this._cursor !== null ? this._cursor.data : null; + }; + + // if null-iterator, returns first node + // otherwise, returns next node + Iterator.prototype.next = function() { + if(this._cursor === null) { + var root = this._tree._root; + if(root !== null) { + this._minNode(root); + } + } + else { + if(this._cursor.right === null) { + // no greater node in subtree, go up to parent + // if coming from a right child, continue up the stack + var save; + do { + save = this._cursor; + if(this._ancestors.length) { + this._cursor = this._ancestors.pop(); + } + else { + this._cursor = null; + break; + } + } while(this._cursor.right === save); + } + else { + // get the next node from the subtree + this._ancestors.push(this._cursor); + this._minNode(this._cursor.right); + } + } + return this._cursor !== null ? this._cursor.data : null; + }; + + // if null-iterator, returns last node + // otherwise, returns previous node + Iterator.prototype.prev = function() { + if(this._cursor === null) { + var root = this._tree._root; + if(root !== null) { + this._maxNode(root); + } + } + else { + if(this._cursor.left === null) { + var save; + do { + save = this._cursor; + if(this._ancestors.length) { + this._cursor = this._ancestors.pop(); + } + else { + this._cursor = null; + break; + } + } while(this._cursor.left === save); + } + else { + this._ancestors.push(this._cursor); + this._maxNode(this._cursor.left); + } + } + return this._cursor !== null ? this._cursor.data : null; + }; + + Iterator.prototype._minNode = function(start) { + while(start.left !== null) { + this._ancestors.push(start); + start = start.left; + } + this._cursor = start; + }; + + Iterator.prototype._maxNode = function(start) { + while(start.right !== null) { + this._ancestors.push(start); + start = start.right; + } + this._cursor = start; + }; + + module.exports = TreeBase; + + }; + require.m['__main__'] = function(module, exports) { + + var TreeBase = require('./treebase'); + + function Node(data) { + this.data = data; + this.left = null; + this.right = null; + this.red = true; + } + + Node.prototype.get_child = function(dir) { + return dir ? this.right : this.left; + }; + + Node.prototype.set_child = function(dir, val) { + if(dir) { + this.right = val; + } + else { + this.left = val; + } + }; + + function RBTree(comparator) { + this._root = null; + this._comparator = comparator; + this.size = 0; + } + + RBTree.prototype = new TreeBase(); + + // returns true if inserted, false if duplicate + RBTree.prototype.insert = function(data) { + var ret = false; + + if(this._root === null) { + // empty tree + this._root = new Node(data); + ret = true; + this.size++; + } + else { + var head = new Node(undefined); // fake tree root + + var dir = 0; + var last = 0; + + // setup + var gp = null; // grandparent + var ggp = head; // grand-grand-parent + var p = null; // parent + var node = this._root; + ggp.right = this._root; + + // search down + while(true) { + if(node === null) { + // insert new node at the bottom + node = new Node(data); + p.set_child(dir, node); + ret = true; + this.size++; + } + else if(is_red(node.left) && is_red(node.right)) { + // color flip + node.red = true; + node.left.red = false; + node.right.red = false; + } + + // fix red violation + if(is_red(node) && is_red(p)) { + var dir2 = ggp.right === gp; + + if(node === p.get_child(last)) { + ggp.set_child(dir2, single_rotate(gp, !last)); + } + else { + ggp.set_child(dir2, double_rotate(gp, !last)); + } + } + + var cmp = this._comparator(node.data, data); + + // stop if found + if(cmp === 0) { + break; + } + + last = dir; + dir = cmp < 0; + + // update helpers + if(gp !== null) { + ggp = gp; + } + gp = p; + p = node; + node = node.get_child(dir); + } + + // update root + this._root = head.right; + } + + // make root black + this._root.red = false; + + return ret; + }; + + // returns true if removed, false if not found + RBTree.prototype.remove = function(data) { + if(this._root === null) { + return false; + } + + var head = new Node(undefined); // fake tree root + var node = head; + node.right = this._root; + var p = null; // parent + var gp = null; // grand parent + var found = null; // found item + var dir = 1; + + while(node.get_child(dir) !== null) { + var last = dir; + + // update helpers + gp = p; + p = node; + node = node.get_child(dir); + + var cmp = this._comparator(data, node.data); + + dir = cmp > 0; + + // save found node + if(cmp === 0) { + found = node; + } + + // push the red node down + if(!is_red(node) && !is_red(node.get_child(dir))) { + if(is_red(node.get_child(!dir))) { + var sr = single_rotate(node, dir); + p.set_child(last, sr); + p = sr; + } + else if(!is_red(node.get_child(!dir))) { + var sibling = p.get_child(!last); + if(sibling !== null) { + if(!is_red(sibling.get_child(!last)) && !is_red(sibling.get_child(last))) { + // color flip + p.red = false; + sibling.red = true; + node.red = true; + } + else { + var dir2 = gp.right === p; + + if(is_red(sibling.get_child(last))) { + gp.set_child(dir2, double_rotate(p, last)); + } + else if(is_red(sibling.get_child(!last))) { + gp.set_child(dir2, single_rotate(p, last)); + } + + // ensure correct coloring + var gpc = gp.get_child(dir2); + gpc.red = true; + node.red = true; + gpc.left.red = false; + gpc.right.red = false; + } + } + } + } + } + + // replace and remove if found + if(found !== null) { + found.data = node.data; + p.set_child(p.right === node, node.get_child(node.left === null)); + this.size--; + } + + // update root and make it black + this._root = head.right; + if(this._root !== null) { + this._root.red = false; + } + + return found !== null; + }; + + function is_red(node) { + return node !== null && node.red; + } + + function single_rotate(root, dir) { + var save = root.get_child(!dir); + + root.set_child(!dir, save.get_child(dir)); + save.set_child(dir, root); + + root.red = true; + save.red = false; + + return save; + } + + function double_rotate(root, dir) { + root.set_child(!dir, single_rotate(root.get_child(!dir), !dir)); + return single_rotate(root, dir); + } + + module.exports = RBTree; + }; + return { + RBTree: require('__main__') + }; + }); +})(); diff --git a/lorgar/lib/fonts/CMakeLists.txt b/lorgar/lib/fonts/CMakeLists.txt new file mode 100644 index 0000000..dbbe0f6 --- /dev/null +++ b/lorgar/lib/fonts/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(Liberation.js Liberation.js) diff --git a/lorgar/lib/fonts/Liberation.js b/lorgar/lib/fonts/Liberation.js new file mode 100644 index 0000000..eace970 --- /dev/null +++ b/lorgar/lib/fonts/Liberation.js @@ -0,0 +1,94 @@ +(function() { + var moduleName = "lib/fonts/Liberation"; + + define(moduleName, [], function() { + return { + "ascent": 1854, + "descent": -434, + "lineGap": 67, + "unitsPerEm": 2048, + "advanceWidthArray": [ + 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, + 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, + 569, 569, 727, 1139, 1139, 1821, 1366, 391, 682, 682, 797, 1196, 569, 682, 569, 569, + 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139, 569, 569, 1196, 1196, 1196, 1139, + 2079, 1366, 1366, 1479, 1479, 1366, 1251, 1593, 1479, 569, 1024, 1366, 1139, 1706, 1479, 1593, + 1366, 1593, 1479, 1366, 1251, 1479, 1366, 1933, 1366, 1366, 1251, 569, 569, 569, 961, 1139, + 682, 1139, 1139, 1024, 1139, 1139, 569, 1139, 1139, 455, 455, 1024, 455, 1706, 1139, 1139, + 1139, 1139, 682, 1024, 569, 1139, 1024, 1479, 1024, 1024, 1024, 684, 532, 684, 1196, 1536, + 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, + 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, 1536, + 569, 682, 1139, 1139, 1139, 1139, 532, 1139, 682, 1509, 758, 1139, 1196, 682, 1509, 1131, + 819, 1124, 682, 682, 682, 1180, 1100, 682, 682, 682, 748, 1139, 1708, 1708, 1708, 1251, + 1366, 1366, 1366, 1366, 1366, 1366, 2048, 1479, 1366, 1366, 1366, 1366, 569, 569, 569, 569, + 1479, 1479, 1593, 1593, 1593, 1593, 1593, 1196, 1593, 1479, 1479, 1479, 1479, 1366, 1366, 1251, + 1139, 1139, 1139, 1139, 1139, 1139, 1821, 1024, 1139, 1139, 1139, 1139, 569, 569, 569, 569, + 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1124, 1251, 1139, 1139, 1139, 1139, 1024, 1139, 1024, + 1366, 1139, 1366, 1139, 1366, 1139, 1479, 1024, 1479, 1024, 1479, 1024, 1479, 1024, 1479, 1259, + 1479, 1139, 1366, 1139, 1366, 1139, 1366, 1139, 1366, 1139, 1366, 1139, 1593, 1139, 1593, 1139, + 1593, 1139, 1593, 1139, 1479, 1139, 1479, 1139, 569, 569, 569, 569, 569, 569, 569, 455, + 569, 569, 1505, 909, 1024, 455, 1366, 1024, 1024, 1139, 455, 1139, 455, 1139, 597, 1139, + 684, 1139, 455, 1479, 1139, 1479, 1139, 1479, 1139, 1237, 1481, 1139, 1593, 1139, 1593, 1139, + 1593, 1139, 2048, 1933, 1479, 682, 1479, 682, 1479, 682, 1366, 1024, 1366, 1024, 1366, 1024, + 1366, 1024, 1251, 569, 1251, 768, 1251, 569, 1479, 1139, 1479, 1139, 1479, 1139, 1479, 1139, + 1479, 1139, 1479, 1139, 1933, 1479, 1366, 1024, 1366, 1251, 1024, 1251, 1024, 1251, 1024, 455, + 1139, 1553, 1344, 1139, 1344, 1139, 1479, 1479, 1024, 1479, 1658, 1344, 1139, 1140, 1366, 1541, + 1237, 1251, 1139, 1593, 1278, 1804, 455, 569, 1366, 1024, 455, 1024, 1824, 1479, 1139, 1593, + 1756, 1343, 1778, 1367, 1545, 1139, 1366, 1366, 1024, 1266, 779, 569, 1251, 569, 1251, 1749, + 1371, 1531, 1479, 1582, 1024, 1251, 1024, 1251, 1251, 1116, 1116, 1139, 1139, 939, 997, 1139, + 532, 846, 1196, 569, 2730, 2503, 2148, 2175, 1706, 924, 2503, 1934, 1579, 1366, 1139, 569, + 455, 1593, 1139, 1479, 1139, 1479, 1139, 1479, 1139, 1479, 1139, 1479, 1139, 1139, 1366, 1139, + 1366, 1139, 2048, 1821, 1593, 1139, 1593, 1139, 1366, 1024, 1593, 1139, 1593, 1139, 1251, 1116, + 455, 2730, 2503, 2148, 1593, 1139, 2118, 1266, 1479, 1139, 1366, 1139, 2048, 1821, 1593, 1251, + 1366, 1139, 1366, 1139, 1366, 1139, 1366, 1139, 569, 569, 569, 569, 1593, 1139, 1593, 1139, + 1479, 682, 1479, 682, 1479, 1139, 1479, 1139, 1366, 1024, 1251, 569, 1116, 894, 1479, 1139, + 1446, 1396, 1238, 1158, 1251, 1024, 1366, 1139, 1366, 1139, 1593, 1139, 1593, 1139, 1593, 1139, + 1593, 1139, 1366, 1024, 715, 1402, 752, 455, 1816, 1816, 1366, 1479, 1024, 1139, 1251, 1024, + 1024, 1189, 928, 1366, 1479, 1368, 1366, 1139, 1024, 455, 1510, 1139, 1479, 682, 1366, 1024, + 1139, 1139, 1139, 1139, 1024, 1024, 1139, 1139, 1139, 1139, 1513, 939, 939, 1293, 1039, 569, + 1139, 1139, 1144, 1026, 1263, 1139, 1139, 1139, 455, 455, 729, 670, 622, 455, 1171, 1706, + 1706, 1706, 1139, 1139, 1132, 1139, 1619, 1599, 1126, 682, 682, 682, 682, 682, 682, 682, + 1109, 1109, 1024, 455, 532, 455, 715, 569, 569, 1139, 1164, 1120, 1024, 1479, 1024, 1064, + 1024, 1108, 1116, 1116, 1024, 1024, 1024, 1024, 1593, 1088, 1039, 1144, 1131, 814, 1024, 827, + 1139, 1024, 1024, 1975, 1856, 2059, 1459, 879, 1472, 1564, 1354, 1295, 994, 1080, 1407, 1407, + 785, 785, 326, 491, 491, 491, 746, 985, 657, 391, 727, 455, 455, 455, 682, 682, + 714, 714, 1196, 1196, 1196, 1196, 682, 682, 682, 682, 682, 682, 682, 682, 682, 682, + 569, 569, 682, 682, 682, 682, 682, 682, 682, 682, 682, 682, 682, 682, 682, 682, + 660, 322, 696, 672, 714, 784, 784, 784, 784, 784, 682, 682, 682, 682, 682, 682, + 682, 682, 682, 682, 682, 682, 682, 682, 569, 682, 682, 682, 682, 814, 814, 682, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1536, 1536, 1536, 1536, 682, 682, 1536, 1536, 1536, 1536, 682, 1024, 1024, 1024, 569, 1536, + 1536, 1536, 1536, 1536, 682, 682, 1367, 569, 1606, 1716, 786, 1536, 1586, 1536, 1752, 1541, + 455, 1366, 1366, 1128, 1368, 1366, 1251, 1479, 1593, 569, 1366, 1368, 1706, 1479, 1331, 1593, + 1479, 1366, 1536, 1266, 1251, 1366, 1634, 1366, 1711, 1531, 569, 1366, 1184, 913, 1139, 455, + 1120, 1184, 1178, 1024, 1140, 913, 903, 1139, 1139, 455, 1024, 1024, 1180, 1024, 917, 1139, + 1413, 1165, 987, 1264, 809, 1120, 1328, 1075, 1460, 1599, 455, 1120, 1139, 1120, 1599, 1536, + 1178, 1120, 1582, 1962, 1582, 1147, 1599, 1231, 1593, 1139, 1479, 1024, 1251, 827, 1279, 1084, + 1549, 1181, 1824, 1706, 1381, 1139, 1380, 1024, 1366, 1366, 1248, 1221, 1509, 1134, 950, 839, + 1231, 1173, 1024, 455, 1593, 905, 905, 1366, 1139, 1479, 1706, 1408, 1165, 1479, 1479, 1479, + 1366, 1367, 1771, 1109, 1472, 1366, 569, 569, 1024, 2165, 2069, 1749, 1193, 1472, 1301, 1472, + 1366, 1344, 1366, 1109, 1387, 1366, 1891, 1237, 1472, 1472, 1193, 1344, 1706, 1479, 1593, 1472, + 1366, 1479, 1251, 1301, 1557, 1366, 1515, 1365, 1877, 1920, 1621, 1813, 1344, 1472, 2069, 1479, + 1139, 1173, 1088, 747, 1195, 1139, 1370, 939, 1144, 1144, 896, 1195, 1408, 1131, 1139, 1109, + 1139, 1024, 938, 1024, 1685, 1024, 1173, 1067, 1643, 1685, 1280, 1472, 1067, 1045, 1536, 1109, + 1139, 1139, 1139, 747, 1045, 1024, 455, 569, 455, 1856, 1664, 1139, 896, 1144, 1024, 1131, + 2740, 1278, 1593, 1255, 1945, 1461, 1368, 1024, 1838, 1424, 1697, 1403, 2157, 1776, 1237, 939, + 1631, 1410, 1593, 1139, 1645, 1292, 1645, 1292, 2200, 1836, 1706, 1254, 2439, 1744, 2740, 1278, + 1479, 1024, 1031, 0, 0, 0, 0, 0, 0, 0, 1472, 1144, 1344, 1067, 1366, 1139, + 1001, 842, 1109, 747, 1373, 1124, 1891, 1370, 1237, 939, 1193, 896, 1193, 896, 1193, 896, + 1519, 1097, 1479, 1131, 1801, 1327, 2328, 1782, 1542, 1067, 1479, 1024, 1251, 938, 1139, 1024, + 1139, 1024, 1366, 1024, 1895, 1415, 1365, 1067, 1365, 1067, 1365, 1139, 1764, 1364, 1764, 1364, + 569, 1891, 1370, 1367, 1128, 1344, 1195, 1479, 1131, 1479, 1131, 1365, 1067, 1706, 1408, 455, + 1366, 1139, 1366, 1139, 2048, 1821, 1366, 1139, 1541, 1139, 1541, 1139, 1891, 1370, 1237, 939, + 1237, 1116, 1472, 1144, 1472, 1144, 1593, 1139, 1593, 1139, 1593, 1139, 1472, 1045, 1301, 1024, + 1301, 1024, 1301, 1024, 1365, 1067, 1109, 747, 1813, 1472, 1109, 747, 1366, 1024, 1366, 1024 + ] + } + }); +})(); diff --git a/lorgar/lib/requirejs/CMakeLists.txt b/lorgar/lib/requirejs/CMakeLists.txt new file mode 100644 index 0000000..f2bcf13 --- /dev/null +++ b/lorgar/lib/requirejs/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(require.js require.js) \ No newline at end of file diff --git a/lorgar/lib/requirejs/require.js b/lorgar/lib/requirejs/require.js new file mode 100644 index 0000000..fbaf690 --- /dev/null +++ b/lorgar/lib/requirejs/require.js @@ -0,0 +1,37 @@ +/* + RequireJS 2.1.22 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved. + Available via the MIT or new BSD license. + see: http://github.com/jrburke/requirejs for details +*/ +var requirejs,require,define; +(function(ha){function L(b){return"[object Function]"===R.call(b)}function M(b){return"[object Array]"===R.call(b)}function x(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(L(l)){try{f=h.execCb(c,l,b,f)}catch(d){a=d}this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports: +this.usingExports&&(f=this.exports));if(a){if(this.events.error&&this.map.isDefine||k.onError!==ia)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",B(this.error=a);if("undefined"!==typeof console&&console.error)console.error(a);else k.onError(a)}}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,k.onResourceLoad)){var e=[];x(this.depMaps,function(a){e.push(a.normalizedMap||a)});k.onResourceLoad(h, +this.map,e)}D(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}},callPlugin:function(){var a=this.map,b=a.id,d=q(a.prefix);this.depMaps.push(d);v(d,"defined",y(this,function(f){var l,d,e=g(ga,this.map.id),N=this.map.name,p=this.map.parentMap?this.map.parentMap.name:null,r=h.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(N=f.normalize(N,function(a){return c(a, +p,!0)})||""),d=q(a.prefix+"!"+N,this.map.parentMap),v(d,"defined",y(this,function(a){this.map.normalizedMap=d;this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),f=g(t,d.id)){this.depMaps.push(d);if(this.events.error)f.on("error",y(this,function(a){this.emit("error",a)}));f.enable()}}else e?(this.map.url=h.nameToUrl(e),this.load()):(l=y(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=y(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b]; +E(t,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&D(a.map.id)});B(a)}),l.fromText=y(this,function(f,c){var d=a.name,e=q(d),N=T;c&&(f=c);N&&(T=!1);u(e);w(m.config,b)&&(m.config[d]=m.config[b]);try{k.exec(f)}catch(g){return B(G("fromtexteval","fromText eval for "+b+" failed: "+g,g,[b]))}N&&(T=!0);this.depMaps.push(e);h.completeLoad(d);r([d],l)}),f.load(a.name,r,l,m))}));h.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){aa[this.map.id]=this;this.enabling=this.enabled=!0;x(this.depMaps, +y(this,function(a,b){var c,f;if("string"===typeof a){a=q(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=g(S,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;v(a,"defined",y(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?v(a,"error",y(this,this.errback)):this.events.error&&v(a,"error",y(this,function(a){this.emit("error",a)}))}c=a.id;f=t[c];w(S,c)||!f||f.enabled||h.enable(a,this)}));E(this.pluginMaps,y(this,function(a){var b= +g(t,a.id);b&&!b.enabled&&h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};h={config:m,contextName:b,registry:t,defined:r,urlFetched:X,defQueue:H,defQueueMap:{},Module:ea,makeModuleMap:q,nextTick:k.nextTick,onError:B,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.shim,c={paths:!0, +bundles:!0,config:!0,map:!0};E(a,function(a,b){c[b]?(m[b]||(m[b]={}),Z(m[b],a,!0,!0)):m[b]=a});a.bundles&&E(a.bundles,function(a,b){x(a,function(a){a!==b&&(ga[a]=b)})});a.shim&&(E(a.shim,function(a,c){M(a)&&(a={deps:a});!a.exports&&!a.init||a.exportsFn||(a.exportsFn=h.makeShimExports(a));b[c]=a}),m.shim=b);a.packages&&x(a.packages,function(a){var b;a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(m.paths[b]=a.location);m.pkgs[b]=a.name+"/"+(a.main||"main").replace(na,"").replace(V,"")});E(t, +function(a,b){a.inited||a.map.unnormalized||(a.map=q(b,null,!0))});(a.deps||a.callback)&&h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ha,arguments));return b||a.exports&&ja(a.exports)}},makeRequire:function(a,n){function e(c,d,g){var m,p;n.enableBuildCallback&&d&&L(d)&&(d.__requireJsBuild=!0);if("string"===typeof c){if(L(d))return B(G("requireargs","Invalid require call"),g);if(a&&w(S,c))return S[c](t[a.id]);if(k.get)return k.get(h, +c,a,e);m=q(c,a,!1,!0);m=m.id;return w(r,m)?r[m]:B(G("notloaded",'Module name "'+m+'" has not been loaded yet for context: '+b+(a?"":". Use require([])")))}Q();h.nextTick(function(){Q();p=u(q(null,a));p.skipMap=n.skipMap;p.init(c,d,g,{enabled:!0});I()});return e}n=n||{};Z(e,{isBrowser:F,toUrl:function(b){var d,e=b.lastIndexOf("."),n=b.split("/")[0];-1!==e&&("."!==n&&".."!==n||1e.attachEvent.toString().indexOf("[native code")||da?(e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)):(T=!0,e.attachEvent("onreadystatechange",b.onScriptLoad));e.src=d;Q=e;I?D.insertBefore(e,I):D.appendChild(e);Q=null;return e}if(ka)try{importScripts(d),b.completeLoad(c)}catch(q){b.onError(G("importscripts","importScripts failed for "+ +c+" at "+d,q,[c]))}};F&&!v.skipDataMain&&Y(document.getElementsByTagName("script"),function(b){D||(D=b.parentNode);if(P=b.getAttribute("data-main"))return u=P,v.baseUrl||(J=u.split("/"),u=J.pop(),U=J.length?J.join("/")+"/":"./",v.baseUrl=U),u=u.replace(V,""),k.jsExtRegExp.test(u)&&(u=P),v.deps=v.deps?v.deps.concat(u):[u],!0});define=function(b,c,d){var g,e;"string"!==typeof b&&(d=c,c=b,b=null);M(c)||(d=c,c=null);!c&&L(d)&&(c=[],d.length&&(d.toString().replace(qa,"").replace(ra,function(b,d){c.push(d)}), +c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));T&&(g=Q||pa())&&(b||(b=g.getAttribute("data-requiremodule")),e=K[g.getAttribute("data-requirecontext")]);e?(e.defQueue.push([b,c,d]),e.defQueueMap[b]=!0):W.push([b,c,d])};define.amd={jQuery:!0};k.exec=function(b){return eval(b)};k(v)}})(this); \ No newline at end of file diff --git a/lorgar/lib/utils/CMakeLists.txt b/lorgar/lib/utils/CMakeLists.txt new file mode 100644 index 0000000..b06c48f --- /dev/null +++ b/lorgar/lib/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(utils/class.js lib/utils/class ${LORGAR_DIR} browser) +add_jslib(utils/subscribable.js lib/utils/subscribable ${LORGAR_DIR} browser) +add_jslib(utils/globalMethods.js lib/utils/globalMethods ${LORGAR_DIR} browser) \ No newline at end of file diff --git a/lorgar/lib/wContainer/CMakeLists.txt b/lorgar/lib/wContainer/CMakeLists.txt new file mode 100644 index 0000000..a8d2aab --- /dev/null +++ b/lorgar/lib/wContainer/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wContainer/abstractmap.js lib/wContainer/abstractmap ${LORGAR_DIR} browser) +add_jslib(wContainer/abstractlist.js lib/wContainer/abstractlist ${LORGAR_DIR} browser) +add_jslib(wContainer/abstractorder.js lib/wContainer/abstractorder ${LORGAR_DIR} browser) +add_jslib(wContainer/abstractpair.js lib/wContainer/abstractpair ${LORGAR_DIR} browser) \ No newline at end of file diff --git a/lorgar/lib/wController/CMakeLists.txt b/lorgar/lib/wController/CMakeLists.txt new file mode 100644 index 0000000..79ca778 --- /dev/null +++ b/lorgar/lib/wController/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wController/controller.js lib/wController/controller ${LORGAR_DIR} browser) +add_jslib(wController/globalControls.js lib/wController/globalControls ${LORGAR_DIR} browser) +add_jslib(wController/link.js lib/wController/link ${LORGAR_DIR} browser) +add_jslib(wController/list.js lib/wController/list ${LORGAR_DIR} browser) +add_jslib(wController/navigationPanel.js lib/wController/navigationPanel ${LORGAR_DIR} browser) +add_jslib(wController/page.js lib/wController/page ${LORGAR_DIR} browser) +add_jslib(wController/pageStorage.js lib/wController/pageStorage ${LORGAR_DIR} browser) +add_jslib(wController/panesList.js lib/wController/panesList ${LORGAR_DIR} browser) +add_jslib(wController/string.js lib/wController/string ${LORGAR_DIR} browser) +add_jslib(wController/theme.js lib/wController/theme ${LORGAR_DIR} browser) +add_jslib(wController/themeSelecter.js lib/wController/themeSelecter ${LORGAR_DIR} browser) +add_jslib(wController/vocabulary.js lib/wController/vocabulary ${LORGAR_DIR} browser) +add_jslib(wController/attributes.js lib/wController/attributes ${LORGAR_DIR} browser) +add_jslib(wController/localModel.js lib/wController/localModel ${LORGAR_DIR} browser) +add_jslib(wController/imagePane.js lib/wController/imagePane ${LORGAR_DIR} browser) +add_jslib(wController/file/file.js lib/wController/file/file ${LORGAR_DIR} browser) +add_jslib(wController/image.js lib/wController/image ${LORGAR_DIR} browser) diff --git a/lorgar/lib/wDispatcher/CMakeLists.txt b/lorgar/lib/wDispatcher/CMakeLists.txt new file mode 100644 index 0000000..68b6fb1 --- /dev/null +++ b/lorgar/lib/wDispatcher/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wDispatcher/dispatcher.js lib/wDispatcher/dispatcher ${LORGAR_DIR} browser) +add_jslib(wDispatcher/defaulthandler.js lib/wDispatcher/defaulthandler ${LORGAR_DIR} browser) +add_jslib(wDispatcher/handler.js lib/wDispatcher/handler ${LORGAR_DIR} browser) +add_jslib(wDispatcher/logger.js lib/wDispatcher/logger ${LORGAR_DIR} browser) \ No newline at end of file diff --git a/lorgar/lib/wSocket/CMakeLists.txt b/lorgar/lib/wSocket/CMakeLists.txt new file mode 100644 index 0000000..d3d985d --- /dev/null +++ b/lorgar/lib/wSocket/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(socket.js socket.js) \ No newline at end of file diff --git a/lorgar/lib/wSocket/socket.js b/lorgar/lib/wSocket/socket.js new file mode 100644 index 0000000..5808867 --- /dev/null +++ b/lorgar/lib/wSocket/socket.js @@ -0,0 +1,203 @@ +"use strict"; +(function socket_js() { + var moduleName = "lib/wSocket/socket" + + var defineArray = []; + defineArray.push("lib/utils/subscribable"); + defineArray.push("lib/wType/event"); + defineArray.push("lib/wType/bytearray"); + defineArray.push("lib/wType/string"); + defineArray.push("lib/wType/vocabulary"); + defineArray.push("lib/wType/uint64"); + defineArray.push("lib/wType/address"); + defineArray.push("lib/wType/factory"); + + define(moduleName, defineArray, function socket_module() { + var Subscribable = require("lib/utils/subscribable"); + var Event = require("lib/wType/event"); + var ByteArray = require("lib/wType/bytearray"); + var String = require("lib/wType/string"); + var Vocabulary = require("lib/wType/vocabulary"); + var Uint64 = require("lib/wType/uint64"); + var Address = require("lib/wType/address"); + var factory = require("lib/wType/factory"); + + var Socket = Subscribable.inherit({ + "className": "Socket", + "constructor": function(name) { + if (!name) { + throw new Error("Can't construct a socket without a name"); + } + Subscribable.fn.constructor.call(this); + + this._state = DISCONNECTED; + this._dState = SIZE; + this._name = name instanceof String ? name : new String(name); + this._remoteName = new String(); + this._id = new Uint64(0); + this._socket = undefined; + this._helperBuffer = new ByteArray(4); + + this._initProxy(); + }, + "destructor": function() { + this.close(); + if (this._state === DISCONNECTING) { + this.on("disconnected", function() { + Subscribable.fn.destructor.call(this); + }) + } else { + Subscribable.fn.destructor.call(this); + } + }, + "close": function() { + if ((this._state !== DISCONNECTED) && (this._state !== DISCONNECTING)) { + this._state = DISCONNECTING; + this._socket.close(); + } + }, + "_emitEvent": function(ev) { + this.trigger("message", ev); + ev.destructor(); + }, + "getId": function() { + return this._id; + }, + "isOpened": function() { + return this._state === CONNECTED; + }, + "open": function(addr, port) { + if (this._state === DISCONNECTED) { + this._state = CONNECTING; + this._socket = new WebSocket("ws://"+ addr + ":" + port); + this._socket.binaryType = "arraybuffer"; + + this._socket.onclose = this._proxy.onSocketClose; + this._socket.onerror = this._proxy.onSocketError; + this._socket.onmessage = this._proxy.onSocketMessage + } + }, + "send": function(ev) { + var size = ev.size(); + var ba = new ByteArray(size + 5); + ba.push32(size); + ba.push8(ev.getType()); + ev.serialize(ba); + + this._socket.send(ba.data().buffer); + }, + "_initProxy": function() { + this._proxy = { + onSocketClose: this._onSocketClose.bind(this), + onSocketError: this._onSocketError.bind(this), + onSocketMessage: this._onSocketMessage.bind(this) + } + }, + "_onEvent": function(ev) { + if (ev.isSystem()) { + var cmd = ev._data.at("command").toString(); + + switch(cmd) { + case "setId": + this._setId(ev._data.at("id")); + this._setRemoteName(); + break; + case "setName": + this._setName(ev._data.at("name")); + this._state = CONNECTED; + this.trigger("connected"); + break; + default: + throw new Error("Unknown system command: " + cmd); + } + ev.destructor(); + } else { + setTimeout(this._emitEvent.bind(this, ev), 5); //TODO event queue + } + }, + "_onSocketClose": function(ev) { + this._state = DISCONNECTED; + + this._id.destructor(); + this._id = new Uint64(0); + + this._remoteName.destructor(); + this._remoteName = new String(); + + console.log(ev); + this.trigger("disconnected", ev); + }, + "_onSocketError": function(err) { + this.trigger("error", err); + }, + "_onSocketMessage": function(mes) { + var raw = new Uint8Array(mes.data); + var i = 0; + + while (i < raw.length) { + switch (this._dState) { + case SIZE: + i = this._helperBuffer.fill(raw, raw.length, i); + + if (this._helperBuffer.filled()) { + var size = this._helperBuffer.pop32(); + this._helperBuffer.destructor(); + this._helperBuffer = new ByteArray(size + 1); + this._dState = BODY; + } + break; + case BODY: + i = this._helperBuffer.fill(raw, raw.length, i); + + if (this._helperBuffer.filled()) { + var ev = factory(this._helperBuffer); + this._onEvent(ev); + this._helperBuffer.destructor(); + this._helperBuffer = new ByteArray(4); + this._dState = SIZE; + } + break; + } + } + }, + "_setId": function(id) { + if (this._state === CONNECTING) { + this._id.destructor(); + this._id = id.clone(); + } else { + throw new Error("An attempt to set id in unexpected time"); + } + }, + "_setName": function(name) { + if ((this._state === CONNECTING) && (this._id.valueOf() !== 0)) { + this._remoteName.destructor(); + this._remoteName = name.clone(); + } else { + throw new Error("An attempt to set name in unexpected time"); + } + }, + "_setRemoteName": function() { + var vc = new Vocabulary(); + vc.insert("command", new String("setName")); + vc.insert("name", this._name.clone()); + vc.insert("yourName", this._remoteName.clone()); + + var ev = new Event(new Address(), vc, true); + ev.setSenderId(this._id.clone()); + this.send(ev); + + ev.destructor(); + } + }); + + return Socket; + }); + + var DISCONNECTED = 111; + var DISCONNECTING = 110; + var CONNECTING = 101; + var CONNECTED = 100; + + var SIZE = 11; + var BODY = 10; +})(); diff --git a/lorgar/lib/wTest/CMakeLists.txt b/lorgar/lib/wTest/CMakeLists.txt new file mode 100644 index 0000000..eefe30f --- /dev/null +++ b/lorgar/lib/wTest/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wTest/test.js lib/wTest/test ${LORGAR_DIR} browser) +add_jslib(wTest/abstractmap.js lib/wTest/abstractmap ${LORGAR_DIR} browser) +add_jslib(wTest/abstractlist.js lib/wTest/abstractlist ${LORGAR_DIR} browser) +add_jslib(wTest/abstractorder.js lib/wTest/abstractorder ${LORGAR_DIR} browser) \ No newline at end of file diff --git a/lorgar/lib/wType/CMakeLists.txt b/lorgar/lib/wType/CMakeLists.txt new file mode 100644 index 0000000..a5718a9 --- /dev/null +++ b/lorgar/lib/wType/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wType/address.js lib/wType/address ${LORGAR_DIR} browser) +add_jslib(wType/boolean.js lib/wType/boolean ${LORGAR_DIR} browser) +add_jslib(wType/bytearray.js lib/wType/bytearray ${LORGAR_DIR} browser) +add_jslib(wType/event.js lib/wType/event ${LORGAR_DIR} browser) +add_jslib(wType/object.js lib/wType/object ${LORGAR_DIR} browser) +add_jslib(wType/string.js lib/wType/string ${LORGAR_DIR} browser) +add_jslib(wType/uint64.js lib/wType/uint64 ${LORGAR_DIR} browser) +add_jslib(wType/vector.js lib/wType/vector ${LORGAR_DIR} browser) +add_jslib(wType/vocabulary.js lib/wType/vocabulary ${LORGAR_DIR} browser) +add_jslib(wType/blob.js lib/wType/blob ${LORGAR_DIR} browser) +add_jslib(wType/factory.js lib/wType/factory ${LORGAR_DIR} browser) diff --git a/lorgar/main.js b/lorgar/main.js new file mode 100644 index 0000000..286a597 --- /dev/null +++ b/lorgar/main.js @@ -0,0 +1,47 @@ +"use strict"; +(function main_js() { + requirejs.onError = function(e) { + throw e; + } + + var defineArray = []; + defineArray.push("test/test"); + defineArray.push("core/lorgar"); + defineArray.push("lib/utils/globalMethods"); + + require(defineArray, function main_module() { + require("lib/utils/globalMethods"); + + var Test = require("test/test"); + var Lorgar = require("core/lorgar"); + var Controller = require("lib/wController/controller"); + var View = require("views/view"); + + var waiter = { + views: false, + controllers: false, + check: function(key) { + this[key] = true; + this.launch() + }, + launch: function() { + if (this.views && this.controllers) { + window.lorgar = new Lorgar(); + + window.registerForeignController = window.lorgar.registerForeignController.bind(window.lorgar); + window.unregisterForeignController = window.lorgar.unregisterForeignController.bind(window.lorgar); + window.subscribeForeignController = window.lorgar.subscribeForeignController.bind(window.lorgar); + window.unsubscribeForeignController = window.lorgar.unsubscribeForeignController.bind(window.lorgar); + } + } + } + + Controller.initialize(["String", "List", "Vocabulary", "Page", "PanesList", "Link", "Image"], waiter.check.bind(waiter, "controllers")); + View.initialize(["Label", "Page", "PanesList", "Nav", "Image"], waiter.check.bind(waiter, "views")); + + var test = new Test(); + test.run(); + + }); + +})(); diff --git a/lorgar/test/CMakeLists.txt b/lorgar/test/CMakeLists.txt new file mode 100644 index 0000000..d55da71 --- /dev/null +++ b/lorgar/test/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(test.js test.js) \ No newline at end of file diff --git a/lorgar/test/test.js b/lorgar/test/test.js new file mode 100644 index 0000000..93b059a --- /dev/null +++ b/lorgar/test/test.js @@ -0,0 +1,73 @@ +"use strict"; +(function test_js() { + var moduleName = "test/test"; + + var defineArray = []; + defineArray.push("lib/utils/class"); + defineArray.push("lib/wTest/abstractmap"); + defineArray.push("lib/wTest/abstractlist"); + defineArray.push("lib/wTest/abstractorder"); + + define(moduleName, defineArray, function test_module() { + var Class = require("lib/utils/class"); + var TAbstractMap = require("lib/wTest/abstractmap"); + var TAbstractList = require("lib/wTest/abstractlist"); + var TAbstractOrder = require("lib/wTest/abstractorder"); + + var Test = Class.inherit({ + "className": "Test", + "constructor": function() { + Class.fn.constructor.call(this); + + this._s = 0; + this._t = 0; + this._tests = []; + + this.addTest(new TAbstractList()); + this.addTest(new TAbstractMap()); + this.addTest(new TAbstractOrder()); + }, + "destructor": function() { + for (var i = 0; i < this._tests.length; ++i) { + this._tests[i].destructor(); + } + + Class.fn.destructor.call(this); + }, + "addTest": function(test) { + test.on("start", this._onStart, this); + test.on("end", this._onEnd, this); + test.on("progress", this._onProgress, this); + test.on("fail", this._onFail, this); + + this._tests.push(test); + }, + "run": function() { + console.info("Starting tests"); + for (var i = 0; i < this._tests.length; ++i) { + this._tests[i].run(); + } + console.info("Testing complete. " + this._s + "/" + this._t); + }, + "_onStart": function(name) { + console.info("Testing " + name); + }, + "_onEnd": function(name, s, t) { + console.info("Finished " + name + ". " + s + "/" + t); + + this._s += s; + this._t += t; + }, + "_onProgress": function(name, current, total) { + + }, + "_onFail": function(name, current, error) { + console.warn("Test failed! Action " + current + "."); + console.warn("Error message:" + error.message); + console.warn("Error stack: \n" + error.stack); + } + }); + + return Test; + }); +})(); diff --git a/lorgar/views/CMakeLists.txt b/lorgar/views/CMakeLists.txt new file mode 100644 index 0000000..c656448 --- /dev/null +++ b/lorgar/views/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(label.js label.js) +configure_file(view.js view.js) +configure_file(layout.js layout.js) +configure_file(gridLayout.js gridLayout.js) +configure_file(navigationPanel.js navigationPanel.js) +configure_file(nav.js nav.js) +configure_file(panesList.js panesList.js) +configure_file(mainLayout.js mainLayout.js) +configure_file(page.js page.js) +configure_file(pane.js pane.js) +configure_file(image.js image.js) + +add_subdirectory(helpers) diff --git a/lorgar/views/gridLayout.js b/lorgar/views/gridLayout.js new file mode 100644 index 0000000..cb44ebb --- /dev/null +++ b/lorgar/views/gridLayout.js @@ -0,0 +1,436 @@ +"use strict"; +(function gridLayout_js() { + var moduleName = "views/gridLayout"; + + var defineArray = []; + defineArray.push("views/view"); + defineArray.push("views/layout"); + + define(moduleName, defineArray, function gridLayout_module() { + var View = require("views/view"); + var Layout = require("views/layout"); + + var GridLayout = Layout.inherit({ + "className": "GridLayout", + "constructor": function(controller, options) { + var base = { + + }; + W.extend(base, options); + Layout.fn.constructor.call(this, controller, base); + + this._lay = [[[]]]; + this._cols = [{}]; + this._rows = [{}]; + }, + "append": function(child, row, col, rowSpan, colSpan, aligment) { + aligment = aligment || 5; + this._addChild(child, aligment); + + rowSpan = rowSpan || 1; + colSpan = colSpan || 1; + + var tRow = row + rowSpan; + var tCol = col + colSpan; + + while (this._lay.length < tRow) { + this._lay.push([]); + + } + for (var i = 0; i < this._lay.length; ++i) { + while (this._lay[i].length < tCol) { + this._lay[i].push([]); + } + } + var obj = { + child: child, + colspan: colSpan, + rowspan: rowSpan, + a: aligment + } + + for (i = row; i < tRow; ++i) { + for (var j = col; j < tCol; ++j) { + this._lay[i][j].push(obj); + } + } + + this._recountLimits(); + if (this._w !== undefined && this._h !== undefined) { + this.refreshLay(); + } + }, + "_cleanupLay": function() { + var i; + var rowsC = false; + var colsC = false; + while (!rowsC) { + for (i = 0; i < this._lay[this._lay.length - 1].length; ++i) { + if (this._lay[this._lay.length - 1][i].length) { + rowsC = true; + break; + } + } + if (!rowsC) { + this._lay.pop() + rowsC = !this._lay.length; + colsC = !this._lay.length; + } + } + while (!colsC) { + for (i = 0; i < this._lay.length; ++i) { + if (this._lay[i][this._lay[i].length - 1].length) { + colsC = true; + break; + } + } + if (!colsC) { + for (i = 0; i < this._lay.length; ++i) { + this._lay[i].pop(); + } + } + } + }, + "clear": function() { + Layout.fn.clear.call(this); + + this._lay = [[[]]]; + this._cols = [{}]; + this._rows = [{}]; + }, + "_onChildChangeLimits": function() { + var notification = this._recountLimits(); + if (!notification) { + this.refreshLay(); + } + }, + "_positionElements": function() { + var shiftW = 0; + var shiftH = 0; + + var positioned = []; + + for (var i = 0; i < this._lay.length; ++i) { + shiftW = 0; + for (var j = 0; j < this._lay[i].length; ++j) { + for (var k = 0; k < this._lay[i][j].length; ++k) { + var e = this._lay[i][j][k]; + var child = e.child; + if (positioned.indexOf(child) === -1) { + var tWidth = 0; + var tHeight = 0; + var s; + for (s = 0; s < e.colspan; ++s) { + tWidth += this._cols[j + s].cur; + } + for (s = 0; s < e.rowspan; ++s) { + tHeight += this._rows[i + s].cur; + } + child.setSize(tWidth, tHeight); + + switch (e.a) { + case Layout.Aligment.LeftTop: + child.setTop(shiftH); + child.setLeft(shiftW); + break; + case Layout.Aligment.LeftCenter: + child.setTop(shiftH + (tHeight - child._h) / 2); + child.setLeft(shiftW); + break; + case Layout.Aligment.LeftBottom: + child.setTop(shiftH + (tHeight - child._h)); + child.setLeft(shiftW); + break; + case Layout.Aligment.CenterTop: + child.setTop(shiftH); + child.setLeft(shiftW + (tWidth - child._w) / 2); + break; + case Layout.Aligment.CenterCenter: + child.setTop(shiftH + (tHeight - child._h) / 2); + child.setLeft(shiftW + (tWidth - child._w) / 2); + break; + case Layout.Aligment.CenterBottom: + child.setTop(shiftH + (tHeight - child._h)); + child.setLeft((tWidth - child._w) / 2); + break; + case Layout.Aligment.RightTop: + child.setTop(shiftH); + child.setLeft(shiftW + (tWidth - child._h)); + break; + case Layout.Aligment.RightCenter: + child.setTop((tHeight - child._h) / 2); + child.setLeft(shiftW + (tWidth - child._h)); + break; + case Layout.Aligment.RightBottom: + child.setTop(shiftH + (tHeight - child._h)); + child.setLeft(shiftW + (tWidth - child._h)); + break; + + } + positioned.push(child); + } + } + shiftW += this._cols[j].cur; + } + shiftH += this._rows[i].cur; + } + }, + "_recountLimits": function() { + this._cols = []; + this._rows = []; + var i, j; + var multiCols = []; + var multiRows = []; + var proccessed = Object.create(null); + + for (i = 0; i < this._lay.length; ++i) { + while (!this._rows[i]) { + this._rows.push({}); + } + for (j = 0; j < this._lay[i].length; ++j) { + while (!this._cols[j]) { + this._cols.push({}); + } + for (var k = 0; k < this._lay[i][j].length; ++k) { + var e = this._lay[i][j][k]; + + if (proccessed[e.child._id]) { + this._cols[j].min = this._cols[j].min || 0; + this._rows[i].min = this._rows[i].min || 0; + this._cols[j].max = this._cols[j].max || 0; + this._rows[i].max = this._rows[i].max || 0; + continue; + } + + var colMinW = this._cols[j].min || 0; + var rowMinH = this._rows[i].min || 0; + var colMaxW = this._cols[j].max || Infinity; + var rowMaxH = this._rows[i].max || Infinity; + + if (e.colspan === 1) { + this._cols[j].min = Math.max(colMinW, e.child._o.minWidth); + this._cols[j].max = Math.min(colMaxW, e.child._o.maxWidth); + } else { + this._cols[j].min = colMinW; + this._cols[j].max = colMaxW; + + multiCols.push({ + p: j, + e: e + }); + } + if (e.rowspan === 1) { + this._rows[i].min = Math.max(rowMinH, e.child._o.minHeight); + this._rows[i].max = Math.min(rowMaxH, e.child._o.maxHeight); + } else { + this._rows[i].min = rowMinH; + this._rows[i].max = rowMaxH; + + multiRows.push({ + p: i, + e: e + }); + } + + proccessed[e.child._id] = true; + } + if (!this._lay[i][j].length) { + this._cols[j].min = this._cols[j].min || 0; + this._rows[i].min = this._rows[i].min || 0; + this._cols[j].max = this._cols[j].max || 0; + this._rows[i].max = this._rows[i].max || 0; + } + } + } + + for (i = 0; i < multiCols.length; ++i) { + var e = multiCols[i].e; + var pos = multiCols[i].p; + var span = e.colspan; + var target = pos + span; + var minSize = 0; + var maxSize = 0; + for (j = pos; j < target; ++j) { + minSize += this._cols[j].min; + maxSize += this._cols[j].max; + } + if (e.child._o.minWidth > minSize) { + var portion = (e.child._o.minWidth - minSize) / span; + for (j = pos; j < target; ++j) { + this._cols[j].min += portion; + } + } + if (e.child._o.maxWidth < maxSize) { + var portion = (maxSize - e.child._o.maxWidth) / span; + for (j = pos; j < target; ++j) { + this._cols[j].max -= portion; + } + } + } + + for (i = 0; i < multiRows.length; ++i) { + var e = multiRows[i].e; + var pos = multiRows[i].p; + var span = e.rowspan; + var target = pos + span; + var minSize = 0; + var maxSize = 0; + for (j = pos; j < target; ++j) { + minSize += this._rows[j].min; + maxSize += this._rows[j].max; + } + if (e.child._o.minHeight > minSize) { + var portion = (e.child._o.minHeight - minSize) / span; + for (j = pos; j < target; ++j) { + this._rows[j].min += portion; + } + } + if (e.child._o.maxHeight < maxSize) { + var portion = (maxSize - e.child._o.maxHeight) / span; + for (j = pos; j < target; ++j) { + this._rows[j].max -= portion; + } + } + } + + var minWidth = 0; + var minHeight = 0; + var maxWidth = 0; + var maxHeight = 0; + + for (i = 0; i < this._rows.length; ++i) { + minHeight += this._rows[i].min; + this._rows[i].max = Math.max(this._rows[i].max, this._rows[i].min); + maxHeight += this._rows[i].max; + } + for (i = 0; i < this._cols.length; ++i) { + minWidth += this._cols[i].min; + this._cols[i].max = Math.max(this._cols[i].max, this._cols[i].min); + maxWidth += this._cols[i].max; + } + + return this._setLimits(minWidth, minHeight, maxWidth, maxHeight); + }, + "refreshLay": function() { + var totalMaxW = 0; + var totalMaxH = 0; + var totalMinW = 0; + var totalMinH = 0; + var i; + + for (i = 0; i < this._cols.length; ++i) { + totalMaxW += this._cols[i].max; + totalMinW += this._cols[i].min + } + for (i = 0; i < this._rows.length; ++i) { + totalMaxH += this._rows[i].max; + totalMinH += this._rows[i].min; + } + + if (this._w <= totalMinW || this._w >= totalMaxW) { + var kW; + var keyW; + if (this._w <= totalMinW) { + kW = this._w / totalMinW; + keyW = "min"; + } else { + kW = this._w / totalMaxW; + keyW = "max"; + } + + for (i = 0; i < this._cols.length; ++i) { + this._cols[i].cur = this._cols[i][keyW] * kW; + } + } else { + distribute(this._w, this._cols); + } + + if (this._h <= totalMinH || this._h >= totalMaxH) { + var kH; + var keyH; + if (this._h <= totalMinH) { + kH = this._h / totalMinH; + keyH = "min"; + } else { + kH = this._h / totalMaxH; + keyH = "max"; + } + + for (i = 0; i < this._rows.length; ++i) { + this._rows[i].cur = this._rows[i][keyH] * kH; + } + } else { + distribute(this._h, this._rows); + } + this._positionElements(); + }, + "removeChild": function(child) { + Layout.fn.removeChild.call(this, child); + + for (var i = 0; i < this._lay.length; ++i) { + for (var j = 0; j < this._lay[i].length; ++j) { + for (var k = 0; k < this._lay[i][j].length; ++k) { + if (child === this._lay[i][j][k].child) { + this._lay[i][j].splice(k, 1); + } + } + } + } + + this._cleanupLay(); + this._recountLimits(); + this.refreshLay(); + }, + "setSize": function(w, h) { + View.fn.setSize.call(this, w, h); + + if (this._c.length) { + this.refreshLay(); + } + } + }); + + function distribute (size, array) { + var i, portion; + var cMax = []; + for (i = 0; i < array.length; ++i) { + array[i].cur = array[i].min; + size -= array[i].min; + + if (array[i].cur < array[i].max) { + cMax.push(array[i]); + } + } + cMax.sort(GridLayout._candidatesSortMax); + + while (cMax.length && size) { + portion = size / cMax.length; + var last = cMax[cMax.length -1]; + + if (portion >= last.max) { + size -= last.max - last.cur; + last.cur = last.max; + cMax.pop(); + } else { + for (i = 0; i < cMax.length; ++i) { + cMax[i].cur += portion; + } + size = 0; + } + } + + if (size) { + portion = size / array.length; + for (i = 0; i < array.length; ++i) { + array.cur += portion; + } + } + } + + GridLayout._candidatesSortMax = function(a, b) { + return b.max - a.max; + } + + return GridLayout; + }); +})(); diff --git a/lorgar/views/helpers/CMakeLists.txt b/lorgar/views/helpers/CMakeLists.txt new file mode 100644 index 0000000..13302bb --- /dev/null +++ b/lorgar/views/helpers/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(scrollable.js scrollable.js) +configure_file(draggable.js draggable.js) \ No newline at end of file diff --git a/lorgar/views/helpers/draggable.js b/lorgar/views/helpers/draggable.js new file mode 100644 index 0000000..173b4ad --- /dev/null +++ b/lorgar/views/helpers/draggable.js @@ -0,0 +1,148 @@ +"use strict"; +(function draggable_js() { + var moduleName = "views/helpers/draggable"; + + var defineArray = []; + defineArray.push("lib/utils/subscribable"); + + define(moduleName, defineArray, function draggable_module() { + var Subscribable = require("lib/utils/subscribable"); + + var Draggable = Subscribable.inherit({ + "className": "Draggable", + "constructor": function Draggable (view, options) { + var base = { + snapDistance: 0 + }; + W.extend(base, options); + Subscribable.fn.constructor.call(this, base); + + this._o = base; + this._e = view._e; + this._v = view; + this._v.addClass("draggable"); + + this._initProxy(); + this._dragging = false; + this._toFlush = false; + + this._x = 0; + this._y = 0; + + this._e.addEventListener("mousedown", this._proxy.onMouseDown, false); + this._e.addEventListener("touchstart", this._touch, false); + this._e.addEventListener("touchmove", this._touch, false); + this._e.addEventListener("touchend", this._touch, false); + }, + "destructor": function () { + if (this._dragging) { + window.removeEventListener("mouseup", this._proxy.onMouseUp); + window.removeEventListener("mousemove", this._proxy.onMouseMove); + } + + if (!this._v.destroying) { + this._v.removeClass("draggable"); + } + + this._e.removeEventListener("mousedown", this._proxy.onMouseDown); + this._e.removeEventListener("touchstart", this._touch); + this._e.removeEventListener("touchmove", this._touch); + this._e.removeEventListener("touchend", this._touch); + + Subscribable.fn.destructor.call(this); + }, + "_initProxy": function () { + this._proxy = { + "onMouseDown": this._onMouseDown.bind(this), + "onMouseUp": this._onMouseUp.bind(this), + "onMouseMove": this._onMouseMove.bind(this) + } + }, + "_onMouseDown": function (e) { + if (e.which === 1) { + window.addEventListener("mouseup", this._proxy.onMouseUp); + window.addEventListener("mousemove", this._proxy.onMouseMove); + lorgar._body.addClass("non-selectable"); + this._x = e.pageX; + this._y = e.pageY; + } + }, + "_onMouseMove": function (e) { + var x = e.pageX - this._x; + var y = e.pageY - this._y; + + if (this._dragging) { + this._v.trigger("drag", { + x: x, + y: y + }); + } else { + var absX = Math.abs(x); + var absY = Math.abs(y); + + if (absX > this._o.snapDistance || absY > this._o.snapDistance) { + this._dragging = true; + lorgar._body.addClass("dragging"); + this._v.trigger("dragStart", { + x: x, + y: y + }); + } + } + return false; + }, + "_onMouseUp": function (e) { + window.removeEventListener("mouseup", this._proxy.onMouseUp); + window.removeEventListener("mousemove", this._proxy.onMouseMove); + + this._x = 0; + this._y = 0; + lorgar._body.removeClass("non-selectable"); + if (this._dragging) { + e.preventDefault(); + this._dragging = false; + lorgar._body.removeClass("dragging"); + this._v.trigger("dragEnd"); + return false; + } + }, + "_touch": function (e) { + e.preventDefault(); + if (e.touches.length > 1 || (e.type == "touchend" && e.touches.length > 0)) + return; + + var type = null; + var touch = null; + var src = e.currentTarget; + switch (e.type) { + case "touchstart": + type = "mousedown"; + touch = e.changedTouches[0]; + + break; + case "touchmove": + type = "mousemove"; + touch = e.changedTouches[0]; + src = window; + break; + case "touchend": + type = "mouseup"; + touch = e.changedTouches[0]; + src = window; + break; + } + + var event = new MouseEvent(type, { + button: 0, + screenX: touch.screenX, + screenY: touch.screenY, + clientX: touch.clientX, + clientY: touch.clientY + }); + src.dispatchEvent(event); + } + }); + + return Draggable; + }); +})(); diff --git a/lorgar/views/helpers/scrollable.js b/lorgar/views/helpers/scrollable.js new file mode 100644 index 0000000..64f2262 --- /dev/null +++ b/lorgar/views/helpers/scrollable.js @@ -0,0 +1,222 @@ +"use strict"; +(function scrollable_js() { + var moduleName = "views/helpers/scrollable"; + + var defineArray = []; + defineArray.push("views/view"); + defineArray.push("lib/wController/localModel"); + + define(moduleName, defineArray, function scrollable_module() { + var View = require("views/view"); + var LocalModel = require("lib/wController/localModel"); + + var Scrollable = View.inherit({ + "className": "Scrollable", + "constructor": function Scrollable(options, layout) { + var base = { + draggable: true, + snapDistance: 5, + vertical: false, + horizontal: false, + virtual: false + }; + var lm = new LocalModel(); + W.extend(base, options); + View.fn.constructor.call(this, lm, base); + + this._l = layout; + this._l._e.appendChild(this._e); + this._horizontalBar = document.createElement("div"); + this._verticalBar = document.createElement("div"); + this._horizontalBarShown = false; + this._verticalBarShown = false; + + this._initProxy(); + this._initBars(); + + this._e.addEventListener("mousewheel", this._proxy.onMouseWheel, false); + + if (this._o.draggable) { + this.on("dragStart", this._onDragStart, this); + this.on("drag", this._onDrag, this); + this.on("dragEnd", this._onDragEnd, this); + } + + this._uncyclic.push(function() { + lm.destructor(); + }) + }, + "destructor": function() { + this._e.removeEventListener("mousewheel", this._proxy.onMouseWheel, false); + this._l._e.removeChild(this._e); + if (this._horizontalBarShown) { + this._l._e.removeChild(this._horizontalBar); + } + if (this._verticalBarShown) { + this._l._e.removeChild(this._verticalBar); + } + + View.fn.destructor.call(this); + }, + "appendChild": function(e) { + this._e.appendChild(e); + }, + "_initBars": function() { + this._horizontalBar.style.position = "absolute"; + this._horizontalBar.style.left = "0"; + this._horizontalBar.style.bottom = "0"; + this._horizontalBar.style.height = "10px"; + this._horizontalBar.style.width = "0"; + this._horizontalBar.style.zIndex = "2"; + this._horizontalBar.style.backgroundColor = Scrollable.theme.primaryColor || "#ff0000"; + + this._verticalBar.style.position = "absolute"; + this._verticalBar.style.right = "0"; + this._verticalBar.style.top = "0"; + this._verticalBar.style.height = "0"; + this._verticalBar.style.width = "10px"; + this._verticalBar.style.zIndex = "2"; + this._verticalBar.style.backgroundColor = Scrollable.theme.primaryColor || "#ff0000"; + }, + "_initProxy": function() { + this._proxy = { + onMouseWheel: this._onMouseWheel.bind(this) + }; + }, + "insertBefore": function(e, other) { + this._e.insertBefore(e, other); + }, + "maximizeMinSize": function(child) { + var width = Math.max(this._o.minWidth, child._o.minWidth); + var height = Math.max(this._o.minHeight, child._o.minHeight); + + this.setMinSize(width, height); + }, + "_onDrag": function(pos) { + var oX = this._o.horizontal ? pos.x : 0; + var oY = this._o.vertical ? pos.y : 0; + + var aX = this._sX + oX; + var aY = this._sY + oY; + + var cX = Math.max(Math.min(0, aX), this._l._w - this._w); + var cY = Math.max(Math.min(0, aY), this._l._h - this._h); + + this.setTop(cY); + this.setLeft(cX); + }, + "_onDragEnd": function(pos) { + //console.log("end") + }, + "_onDragStart": function(pos) { + this._sX = this._x; + this._sY = this._y; + //console.log("start"); + }, + "_onMouseWheel": function(e) { + var dX = this._o.horizontal ? e.deltaX : 0; + var dY = this._o.vertical ? e.deltaY : 0; + + var aX = this._x + dX; + var aY = this._y - dY; + + var cX = Math.max(Math.min(0, aX), this._l._w - this._w); + var cY = Math.max(Math.min(0, aY), this._l._h - this._h); + + this.setTop(cY); + this.setLeft(cX); + }, + "removeChild": function(e) { + this._e.removeChild(e); + }, + "_resetTheme": function() { + View.fn._resetTheme.call(this); + + this._horizontalBar.style.backgroundColor = Scrollable.theme.primaryColor || "#ff0000"; + this._verticalBar.style.backgroundColor = Scrollable.theme.primaryColor || "#ff0000"; + }, + "setLeft": function(x) { + if (this._x !== x) { + if (this._o.virtual) { + this._x = x; + this._e.style.left = "0px"; + } else { + View.fn.setLeft.call(this, x); + } + if (x === 0) { + this.trigger("scrollLeft", 0); + } else { + this.trigger("scrollLeft", -x); + } + + this._horizontalBar.style.top = this._x / this._w * this._l._w + "px"; + } + }, + "setSize": function(w, h) { + if (this._o.virtual) { + this._w = this.constrainWidth(w); + this._h = this.constrainHeight(h); + + this._e.style.width = w + "px"; + this._e.style.height = h + "px"; + + this.trigger("resize", this._w, this._h); + } else { + View.fn.setSize.call(this, w, h); + } + + if (this._w > this._l._w) { + var width = this._l._w / this._w * this._l._w; + var wOffset = this._x / this._w * this._l._w; + + this._horizontalBar.style.width = width + "px"; + this._horizontalBar.style.left = wOffset + "px"; + + if (!this._horizontalBarShown) { + this._l._e.appendChild(this._horizontalBar); + this._horizontalBarShown = true; + } + } else if (this._horizontalBarShown) { + this._l._e.removeChild(this._horizontalBar); + this._horizontalBarShown = false; + } + + if (this._h > this._l._h) { + var height = this._l._h / this._h * this._l._h; + var hOffset = -this._y / this._h * this._l._h; + + this._verticalBar.style.height = height + "px"; + this._verticalBar.style.top = hOffset + "px"; + + if (!this._verticalBarShown) { + this._l._e.appendChild(this._verticalBar); + this._verticalBarShown = true; + } + } else if (this._verticalBarShown) { + this._l._e.removeChild(this._verticalBar); + this._verticalBarShown = false; + } + }, + "setTop": function(y) { + if (this._y !== y) { + if (this._o.virtual) { + this._y = y; + this._e.style.top = "0px"; + } else { + View.fn.setTop.call(this, y); + } + if (y === 0) { + this.trigger("scrollTop", 0); + } else { + this.trigger("scrollTop", -y); + } + + + this._verticalBar.style.top = -this._y / this._h * this._l._h + "px"; + } + } + }); + + return Scrollable; + }); +})(); diff --git a/lorgar/views/image.js b/lorgar/views/image.js new file mode 100644 index 0000000..7f2dd2d --- /dev/null +++ b/lorgar/views/image.js @@ -0,0 +1,35 @@ +"use strict"; +(function() { + var moduleName = "views/image"; + + var deps = []; + deps.push("views/view"); + + define(moduleName, deps, function() { + var View = require("views/view"); + + var Image = View.inherit({ + "className": "Image", + "constructor": function(controller, options) { + var base = {}; + W.extend(base, options) + var el = document.createElement("img"); + View.fn.constructor.call(this, controller, base, el); + + controller.needData(); + }, + "destructor": function() { + this._f.dontNeedData(); + + View.fn.destructor.call(this); + }, + "_onData": function() { + if (this._f.hasData()) { + this._e.src = "data:" + this._f.getMimeType() + ";base64," + this._f.data.base64(); + } + } + }); + + return Image; + }) +})(); diff --git a/lorgar/views/label.js b/lorgar/views/label.js new file mode 100644 index 0000000..503c22f --- /dev/null +++ b/lorgar/views/label.js @@ -0,0 +1,88 @@ +"use strict"; +(function view_label_js() { + var moduleName = "views/label"; + + var defineArray = []; + defineArray.push("views/view"); + defineArray.push("lib/fonts/Liberation"); + + define(moduleName, defineArray, function view_label_module() { + var View = require("views/view"); + + var Label = View.inherit({ + "className": "Label", + "constructor": function(controller, options) { + var base = {}; + W.extend(base, options) + View.fn.constructor.call(this, controller, base); + + this._timeout = undefined; + this._recalculationScheduled = false; + + this._e.innerText = this._f.data || ""; + if (this._f.data !== "") { + this._scheduleRecalculation(); + } + }, + "destructor": function() { + if (this._recalculationScheduled) { + clearTimeout(this._timeout); + } + + View.fn.destructor.call(this); + }, + "_onAddProperty": function(key, propertyName) { + View.fn._onAddProperty.call(this, key, propertyName); + + if (sizeChangingProperties.indexOf(propertyName) !== -1) { + this._scheduleRecalculation(); + } + }, + "_onData": function() { + if (this._e.innerText !== this._f.data) { + this._e.innerText = this._f.data || ""; + this._scheduleRecalculation(); + } + }, + "_recalculateSize": function() { + var fs = parseFloat(this._e.style.fontSize) || 16; + var fontFamily = this._e.style.fontFamily || "Liberation"; + + var h = fs + 2; + var w = Label.calculateSingleString(fontFamily, fs, this._f.data || ""); + this.setConstSize(w, h); + this._recalculationScheduled = false; + }, + "_scheduleRecalculation": function() { + if (!this._recalculationScheduled) { + this._timeout = setTimeout(this._recalculateSize.bind(this), 10); + this._recalculationScheduled = true; + } + } + }); + + var sizeChangingProperties = ["fontSize", "fontFamily", "whiteSpace"]; + + Label.calculateSingleString = function(family, size, string) { + var fontStorage = Label.fonts[family] || Label.fonts["Liberation"]; + + var width = 0; + var mul = size / fontStorage.unitsPerEm; + var aw = fontStorage.advanceWidthArray; + + for (var i = 0; i < string.length; ++i) { + var letter = string.charCodeAt(i); + var l = aw[letter] || 1536; + width += (l * mul); + } + + return Math.ceil(width); + }; + + Label.fonts = { + Liberation: require("lib/fonts/Liberation") + }; + + return Label; + }); +})(); diff --git a/lorgar/views/layout.js b/lorgar/views/layout.js new file mode 100644 index 0000000..c54feba --- /dev/null +++ b/lorgar/views/layout.js @@ -0,0 +1,242 @@ +"use strict"; +(function layout_js() { + var moduleName = "views/layout"; + + var defineArray = []; + defineArray.push("views/view"); + defineArray.push("views/helpers/scrollable"); + + define(moduleName, defineArray, function layout_module() { + var View = require("views/view"); + var Scrollable = require("views/helpers/scrollable"); + + var Layout = View.inherit({ + "className": "Layout", + "constructor": function(controller, options) { + var base = { + scrollable: Layout.Scroll.None + }; + W.extend(base, options); + var element = document.createElement("div"); + View.fn.constructor.call(this, controller, base, element); + + this._c = []; + + if (this._o.scrollable) { + this._scr = new Scrollable({ + vertical: !!(this._o.scrollable & 1), + horizontal: !!(this._o.scrollable & 2), + virtual: !!(this._o.scrollable & 4) + }, this); + } + }, + "destructor": function() { + this._destroying = true; + this.clear(); + if (this._o.scrollable) { + this._scr.destructor(); + } + + View.fn.destructor.call(this); + }, + "_addChild": function(child, aligment, index) { + aligment = aligment || 1; + var item = { + c: child, + a: aligment + } + if (index !== undefined) { + this._c.splice(index, 0, item); + } else { + this._c.push(item); + } + child.remove(); + if (this._o.scrollable) { + if (index === undefined || this._c[index + 1] === undefined) { + this._scr.appendChild(child._e); + } else { + this._scr.insertBefore(child._e, this._c[index + 1].c._e); + } + } else { + if (index === undefined || this._c[index + 1] === undefined) { + this._e.appendChild(child._e); + } else { + this._e.insertBefore(child._e, this._c[index + 1].c._e); + } + } + child._p = this; + child.on("changeLimits", this._onChildChangeLimits, this); + }, + "append": function(child, aligment, index) { + this._addChild(child, aligment, index) + if (this._o.scrollable) { + this._scr.maximizeMinSize(child); + if (this._w !== undefined && this._h !== undefined) { + this._scr.setSize(this._w, this._h); + } + } + + if (this._w !== undefined && this._h !== undefined) { + child.setSize(this._w, this._h); + } + }, + "clear": function() { + while (this._c.length) { + var c = this._c[this._c.length - 1]; + c.c.destructor(); + } + if (!this._destroying) { + if (this._o.scrollable) { + this._scr.setMinSize(0, 0); + this._scr.setSize(this._w, this._h); + } + } + }, + "_onChildChangeLimits": function(child) { + var i; + if (this._o.scrollable) { + var w = 0; + var h = 0; + for (i = 0; i < this._c.length; ++i) { + w = Math.max(this._c[i].c._o.minWidth, w); + h = Math.max(this._c[i].c._o.minHeight, h); + } + this._scr.setMinSize(w, h); + this._scr.setSize(this._w, this._h); + for (i = 0; i < this._c.length; ++i) { + this._positionElement(this._c[i]); + } + } else { + for (i = 0; i < this._c.length; ++i) { + if (this._c[i].c === child) { + break; + } + } + if (i < this._c.length) { + this._positionElement(this._c[i]); + } + } + }, + "_positionElement": function(e) { + var el = e.c; + var a = e.a; + var h = this._h; + var w = this._w; + if (this._o.scrollable) { + h = this._scr._h; + w = this._scr._w; + } + el.setSize(this._w, this._h); + + switch (a) { + case Layout.Aligment.LeftTop: + el.setTop(0); + el.setLeft(0); + break; + case Layout.Aligment.LeftCenter: + el.setTop((h - el._h) / 2); + el.setLeft(0); + break; + case Layout.Aligment.LeftBottom: + el.setTop(h - el._h); + el.setLeft(0); + break; + case Layout.Aligment.CenterTop: + el.setTop(0); + el.setLeft((w - el._w) / 2) + break; + case Layout.Aligment.CenterCenter: + el.setTop((h - el._h) / 2); + el.setLeft((w - el._w) / 2) + break; + case Layout.Aligment.CenterBottom: + el.setTop(h - el._h); + el.setLeft((w - el._w) / 2) + break; + case Layout.Aligment.RightTop: + el.setTop(0); + el.setLeft(w - el._w) + break; + case Layout.Aligment.RightCenter: + el.setTop((h - el._h) / 2); + el.setLeft(w - el._w) + break; + case Layout.Aligment.RightBottom: + el.setTop(h - el._h); + el.setLeft(w - el._w) + break; + } + }, + "removeChild": function(child) { + var i; + for (i = 0; i < this._c.length; ++i) { + if (this._c[i].c === child) { + break; + } + } + if (i !== this._c.length) { + this._removeChildByIndex(i); + } + }, + "_removeChildByIndex": function(i) { + var child = this._c[i].c; + this._c.splice(i, 1); + child._p = undefined; + + if (this._o.scrollable) { + this._scr.removeChild(child._e); + if (!this._destroying) { + var w = 0; + var h = 0; + for (var i = 0; i < this._c.length; ++i) { + w = Math.max(this._c[i].c._o.minWidth, w); + h = Math.max(this._c[i].c._o.minHeight, h); + } + this._scr.setMinSize(w, h); + this._scr.setSize(this._w, this._h); + } + } else { + this._e.removeChild(child._e); + } + + child.off("changeLimits", this._onChildChangeLimits, this); + }, + "setSize": function(w, h) { + View.fn.setSize.call(this, w, h); + + if (this._o.scrollable) { + this._scr.setSize(this._w, this._h); + } + + for (var i = 0; i < this._c.length; ++i) { + this._positionElement(this._c[i]); + } + } + }); + + Layout.Aligment = { + "LeftTop": 1, + "LeftCenter": 4, + "LeftBottom": 7, + "CenterTop": 2, + "CenterCenter": 5, + "CenterBottom": 8, + "RightTop": 3, + "RightCenter": 6, + "RightBottom": 9 + }; + + Layout.Scroll = { + "None": 0, + "Vertical": 1, + "Horizontal": 2, + "Both": 3, + "Virtual": 4, + "VirtualVertical": 5, + "VirtualHorizontal": 6, + "VirtualBoth": 7 + } + + return Layout; + }); +})(); diff --git a/lorgar/views/mainLayout.js b/lorgar/views/mainLayout.js new file mode 100644 index 0000000..8571ea9 --- /dev/null +++ b/lorgar/views/mainLayout.js @@ -0,0 +1,51 @@ +"use strict"; +(function mainLayout_js() { + var moduleName = "views/mainLayout"; + + var defineArray = []; + defineArray.push("views/gridLayout"); + defineArray.push("views/label"); + defineArray.push("views/navigationPanel"); + defineArray.push("views/layout"); + defineArray.push("lib/wController/localModel"); + + define(moduleName, defineArray, function mainLayout_module() { + var GridLayout = require("views/gridLayout"); + var ViewLabel = require("views/label"); + var ViewNavigationPanel = require("views/navigationPanel"); + var Layout = require("views/layout"); + var LocalModel = require("lib/wController/localModel"); + + var MainLayout = GridLayout.inherit({ + "className": "MainLayout", + "_onNewController": function(controller) { + GridLayout.fn._onNewController.call(this, controller); + + var view; + + switch (controller.name) { + case "version": + var lm = new LocalModel({ + backgroundColor: "secondaryColor" + }); + var lay = new Layout(lm, {maxHeight: 15}) + view = new ViewLabel(controller); + lay.append(view, Layout.Aligment.RightCenter); + this.append(lay, 2, 0, 1, 3); + break; + case "navigationPanel": + view = new ViewNavigationPanel(controller); + this.append(view, 0, 0, 1, 3); + break; + case "themes": + break; + default: + //this.trigger("serviceMessage", "Unsupported view: " + name + " (" + type + ")", 1); + break; + } + } + }); + + return MainLayout; + }); +})(); diff --git a/lorgar/views/nav.js b/lorgar/views/nav.js new file mode 100644 index 0000000..186f6e3 --- /dev/null +++ b/lorgar/views/nav.js @@ -0,0 +1,58 @@ +"use strict"; +(function nav_js() { + var moduleName = "views/nav"; + + var defineArray = []; + defineArray.push("views/layout"); + defineArray.push("views/label"); + + define(moduleName, defineArray, function nav_module() { + var Layout = require("views/layout"); + var Label = require("views/label"); + + var Nav = Layout.inherit({ + "className": "Nav", + "constructor": function(controller, options) { + var base = { + "padding": 5 + }; + W.extend(base, options); + Layout.fn.constructor.call(this, controller, base); + + this._initProxy(); + + this._label = new Label(this._f.label); + this._label.on("changeLimits", this._onChangeLimits, this); + this._onChangeLimits(); + + this.append(this._label, Layout.Aligment.CenterCenter); + this.addClass("hoverable"); + + this._e.addEventListener("click", this._proxy.onClick, false); + }, + "destructor": function() { + this._e.removeEventListener("click", this._proxy.onClick, false); + + Layout.fn.destructor.call(this); + }, + "_initProxy": function() { + this._proxy = { + onClick: this._onClick.bind(this) + } + }, + "_onChangeLimits": function() { + this._o.minWidth = this._label._o.minWidth + this._o.padding * 2; + this._o.maxWidth = this._label._o.maxWidth + this._o.padding * 2; + this._o.minHeight = this._label._o.minHeight + this._o.padding * 2; + this._o.maxHeight = this._label._o.maxHeight + this._o.padding * 2; + + this.trigger("changeLimits", this); + }, + "_onClick": function(e) { + lorgar.changePage(this._f.targetAddress); + } + }); + + return Nav; + }); +})(); diff --git a/lorgar/views/navigationPanel.js b/lorgar/views/navigationPanel.js new file mode 100644 index 0000000..07e8f56 --- /dev/null +++ b/lorgar/views/navigationPanel.js @@ -0,0 +1,50 @@ +"use strict"; +(function navigationPanel_js() { + var moduleName = "views/navigationPanel"; + + var defineArray = []; + defineArray.push("views/gridLayout"); + defineArray.push("views/nav"); + defineArray.push("views/view"); + defineArray.push("lib/wController/localModel"); + + define(moduleName, defineArray, function navigationPanel_module() { + var GridLayout = require("views/gridLayout"); + var Nav = require("views/nav"); + var View = require("views/view"); + var LocalModel = require("lib/wController/localModel"); + + var NavigationPanel = GridLayout.inherit({ + "className": "NavigationPanel", + "constructor": function(controller, options) { + var base = { + minHeight: 50, + maxHeight: 50 + }; + W.extend(base, options) + GridLayout.fn.constructor.call(this, controller, base); + + this._spacerHelper = new LocalModel(); + this._spacer = new View(this._spacerHelper); + }, + "destructor": function() { + this._spacer.destructor(); + this._spacerHelper.destructor(); + + GridLayout.fn.destructor.call(this); + }, + "clear": function() { + this._spacer.remove(); + GridLayout.fn.clear.call(this); + }, + "_onNewController": function(controller) { + this._spacer.remove(); + var nav = new Nav(controller); + this.append(nav, 0, this._c.length, 1, 1); + this.append(this._spacer, 0, this._c.length, 1, 1); + } + }); + + return NavigationPanel; + }); +})(); diff --git a/lorgar/views/page.js b/lorgar/views/page.js new file mode 100644 index 0000000..676ce33 --- /dev/null +++ b/lorgar/views/page.js @@ -0,0 +1,90 @@ +"use strict"; +(function() { + var moduleName = "views/page"; + + var defineArray = []; + defineArray.push("views/gridLayout"); + defineArray.push("lib/wType/address"); + defineArray.push("lib/wContainer/abstractmap"); + + define(moduleName, defineArray, function() { + var GridLayout = require("views/gridLayout"); + var Address = require("lib/wType/address"); + var AbstractMap = require("lib/wContainer/abstractmap"); + + var ContentMap = AbstractMap.template(Address, Object); + + var Page = GridLayout.inherit({ + "className": "Page", + "constructor": function(f, options) { + var base = {}; + + W.extend(base, options); + GridLayout.fn.constructor.call(this, f, base); + + this._map = new ContentMap(false); + + this._f.on("addItem", this._onAddItem, this); + this._f.on("removeItem", this._onRemoveItem, this); + this._f.on("clear", this.clear, this); + + var end = this._f.data.end(); + for (var itr = this._f.data.begin(); !itr["=="](end); itr["++"]()) { + var pair = itr["*"](); + this._onAddItem(pair.first, pair.second); + } + }, + "destructor": function() { + this._f.off("addItem", this._onAddItem, this); + this._f.off("removeItem", this._onRemoveItem, this); + this._f.off("clear", this.clear, this); + + this._map.destructor(); + delete this._map; + + GridLayout.fn.destructor.call(this); + }, + "clear": function() { + GridLayout.fn.clear.call(this); + + if (this._map) { + this._map.clear(); + } + }, + "_onAddItem": function(address, element) { + var view = Page.createByType(element.viewType, element.controller, element.viewOptions); + + this._map.insert(address, view); + + this.append(view, element.row, element.col, element.rowspan, element.colspan, element.aligment); + }, + "_onRemoveItem": function(address) { + var itr = this._map.find(address); + var pair = itr["*"](); + + this.removeChild(pair.second); + pair.second.destructor(); + + this._map.erase(itr); + }, + "_setLimits": function(minWidth, minHeight, maxWidth, maxHeight) { + var needToTell = false; + if (this._o.minWidth !== minWidth) { + needToTell = true; + this._o.minWidth = minWidth; + } + if (this._o.minHeight !== minHeight) { + needToTell = true; + this._o.minHeight = minHeight; + } + if (needToTell) { + this.trigger("changeLimits", this); + } + + return needToTell && this._events.changeLimits && this._events.changeLimits.length; //to see if someone actually going to listen that event + } + }); + + return Page; + }); +})() diff --git a/lorgar/views/pane.js b/lorgar/views/pane.js new file mode 100644 index 0000000..2e33e7c --- /dev/null +++ b/lorgar/views/pane.js @@ -0,0 +1,109 @@ +"use strict"; + +(function() { + var moduleName = "views/pane"; + + var defineArray = []; + defineArray.push("views/view"); + defineArray.push("views/layout"); + defineArray.push("views/label"); + defineArray.push("views/image"); + defineArray.push("lib/wController/localModel"); + + define(moduleName, defineArray, function() { + var View = require("views/view"); + var Layout = require("views/layout"); + var Label = require("views/label"); + var Image = require("views/image"); + var LM = require("lib/wController/localModel"); + + var Pane = Layout.inherit({ + "constructor": function PaneView (controller, options) { + var base = { + + }; + W.extend(base, options); + Layout.fn.constructor.call(this, controller, options); + + this._initProxy(); + this.addClass("hoverable"); + this._e.addEventListener("click", this._proxy.onClick, false); + + var lm = this._labelModel = new LM({ + fontFamily: "casualFont" + }); + + if (this._f.hasImage()) { + this._image = new Image(this._f.image); + this.append(this._image, Layout.Aligment.CenterCenter); + } + + var name = this._f.data.at("name"); + this._labelModel.setData(name || ""); + this._labelView = new Label(this._labelModel); + this._labelView.on("changeLimits", this._onLabelChangeLimits, this); + this.append(this._labelView, Layout.Aligment.CenterCenter); + + this._f.on("newElement", this._onNewElement, this); + this._f.on("removeElement", this._onRemoveElement, this); + + this._uncyclic.push(function() { + lm.destructor(); + }); + }, + "destructor": function() { + this._e.removeEventListener("click", this._proxy.onClick, false); + + Layout.fn.destructor.call(this); + }, + "_applyProperties": function() { + this._onAddProperty("secondaryColor", "background"); + + Layout.fn._applyProperties.call(this); + }, + "_initProxy": function() { + this._proxy = { + onClick: this._onClick.bind(this) + }; + }, + "_onClick": function() { + if (this._f.data.at("hasPageLink").valueOf() === true) { + this.trigger("activate", this._f.data.at("pageLink").clone()); + } + }, + "_onLabelChangeLimits": function(label) { + this.setMinSize(label._o.minWidth, this._h); + }, + "_onNewElement": function(key, value) { + switch (key) { + case "name": + this._labelModel.setData(value.toString()); + break; + case "image": + this._image = new Image(this._f.image); + this.append(this._image, Layout.Aligment.LeftTop, 0); + break; + } + }, + "_onRemoveElement": function(key) { + switch (key) { + case "name": + this._labelModel.setData(""); + break; + case "image": + this._image.destructor(); + break; + } + }, + "setSize": function(w, h) { + Layout.fn.setSize.call(this, w, h); + + if (this._f.hasImage()) { + this._image.setSize(w, h); + } + } + }); + + return Pane; + }); +})(); diff --git a/lorgar/views/panesList.js b/lorgar/views/panesList.js new file mode 100644 index 0000000..08dac91 --- /dev/null +++ b/lorgar/views/panesList.js @@ -0,0 +1,189 @@ +"use strict"; +(function panesList_js() { + var moduleName = "views/panesList"; + + var defineArray = []; + defineArray.push("views/view"); + defineArray.push("views/layout"); + defineArray.push("views/label"); + defineArray.push("lib/wController/localModel"); + defineArray.push("views/pane"); + + define(moduleName, defineArray, function panesList_module() { + var View = require("views/view"); + var Layout = require("views/layout"); + var Label = require("views/label"); + var LM = require("lib/wController/localModel"); + var Pane = require("views/pane"); + + var PanesList = Layout.inherit({ + "className": "PanesList", + "constructor": function PanesListView(controller, options) { + var base = { + nestWidth: 100, + nestHeight: 100, + verticalSpace: 10, + scrollable: Layout.Scroll.VirtualVertical + }; + W.extend(base, options); + this._ctrlInitialized = false; + Layout.fn.constructor.call(this, controller, base); + + this._scr.on("scrollTop", this._onScrollTop, this); + this._scr.on("dragStart", this._onScrollDragStart, this); + this._scr.on("dragEnd", this._onScrollDragEnd, this); + + this._hbi = Object.create(null); + this._overflown = false; + this._rows = 0; + this._cachedMinH = 0; + this._cols = 0; + this._scrolled = 0; + this._scrollShift = 0; + this._rangeUpdate = false; + this._skipPaneActivate = false; + this._proxyClearSkippingPaneActivate = this._clearSkippingPaneActivate.bind(this); + + this._f.on("removedController", this._onRemovedController, this); + this._f.on("rangeStart", this._onRangeStart, this); + this._f.on("rangeEnd", this._onRangeEnd, this); + this._f.setSubscriptionRange(0, 0); + }, + "append": function(child, index) { + var model = new LM(); + var nest = new Layout(model, { + minHeight: this._o.nestHeight, + maxHeight: this._o.nestHeight, + minWidth: this._o.nestWidth, + minWidth: this._o.nestWidth, + scrollable: Layout.Scroll.None + }); + nest._uncyclic.push(function() {model.destructor()}); + nest.append(child); + child.on("activate", this._onChildActivate, this); //todo need to remove handler on deletion + this._addChild(nest, 0, index); + + nest.setSize(this._o.nestWidth, this._o.nestHeight); + + if (this._cols && !this._rangeUpdate) { + this._positionElement(index); + if (index !== this._c.length - 1) { + this._refreshPositions(index + 1); + } + } + }, + "_clearSkippingPaneActivate": function() { + this._skipPaneActivate = false; + }, + "_onAddElement": function() { + this._recalculateRows(); + }, + "_onChildActivate": function(address) { + if (!this._skipPaneActivate) { + lorgar.changePage(address); + } + }, + "_onData": function() { + if (this._f.initialized) { + if (!this._ctrlInitialized) { + this._f.on("addElement", this._onAddElement, this); + this._ctrlInitialized = true; + } + this._recalculateRows(); + } + }, + "_onNewController": function(ctrl, index) { + var label = new Pane(ctrl); + this.append(label, index); + }, + "_onRangeEnd": function() { + this._rangeUpdate = false; + this._refreshPositions(0); + }, + "_onRangeStart": function() { + this._rangeUpdate = true; + }, + "_onRemovedController": function(ctrl, index) { + var obj = this._c[index]; + this._removeChildByIndex(index); + obj.c.destructor(); + + if (!this._rangeUpdate) { + this._refreshPositions(index); + } + }, + "_onScrollDragStart": function() { + this._skipPaneActivate = true; + }, + "_onScrollDragEnd": function() { + setTimeout(this._proxyClearSkippingPaneActivate, 1); + }, + "_onScrollTop": function(y) { + this._scrolled = y; + this._recalculateShown(); + }, + "_positionElement": function(index) { + var row = Math.floor(index / this._cols); + var col = index % this._cols; + var e = this._c[index]; + + e.c.setLeft(col * this._o.nestWidth + col * this._hSpace); + e.c.setTop(row * this._o.nestHeight + row * this._o.verticalSpace - this._scrollShift); + }, + "_recalculateRows": function() { + var rows = Math.ceil(this._f.data.length() / this._cols); + if (rows !== this._rows) { + this._rows = rows; + this._cachedMinH = (rows * this._o.nestHeight) + (rows - 1) * this._o.verticalSpace; + } + this._scr.setMinSize(this._w, Math.max(this._cachedMinH, this._h)); + }, + "_recalculateShown": function() { + var ch = this._o.nestHeight + this._o.verticalSpace; + this._scrollShift = this._scrolled % (ch); + var pr = (this._h + this._scrollShift + this._o.verticalSpace) / (ch); + var possibleRows = Math.ceil(pr); + var amount = this._cols * (possibleRows); + + var start = Math.floor(this._scrolled / (ch)) * this._cols; + var end = start + amount; + + this._f.setSubscriptionRange(start, end); + this._refreshPositions(0); + }, + "_refreshPositions": function(start) { + for (var i = start; i < this._c.length; ++i) { + this._positionElement(i); + } + }, + "_removeChildByIndex": function(i) { + var child = this._c[i].c; + this._c.splice(i, 1); + child._p = undefined; + + if (this._o.scrollable) { + this._scr.removeChild(child._e); + } else { + this._e.removeChild(child._e); + } + + child.off("changeLimits", this._onChildChangeLimits, this); + }, + "setSize": function(w, h) { + View.fn.setSize.call(this, w, h); + + this._cols = Math.floor(this._w / this._o.nestWidth); + this._hSpace = (this._w - (this._cols * this._o.nestWidth)) / (this._cols - 1); + + if (this._o.scrollable) { + this._recalculateRows(); + this._scr.setSize(this._w, this._h); + } + this._recalculateShown(); + this._refreshPositions(0); + } + }); + + return PanesList; + }); +})(); diff --git a/lorgar/views/view.js b/lorgar/views/view.js new file mode 100644 index 0000000..efe9125 --- /dev/null +++ b/lorgar/views/view.js @@ -0,0 +1,320 @@ +"use strict"; +(function view_js() { + var moduleName = "views/view"; + + var defineArray = []; + defineArray.push("lib/utils/subscribable"); + defineArray.push("views/helpers/draggable"); + + define(moduleName, defineArray, function view_module() { + var counter = 0; + var Subscribable = require("lib/utils/subscribable"); + var Draggable = require("views/helpers/draggable"); + + var View = Subscribable.inherit({ + "className": "View", + "constructor": function View (controller, options, element) { + Subscribable.fn.constructor.call(this); + this._destroying = false; + + var base = { + minWidth: 0, + minHeight: 0, + maxWidth: Infinity, + maxHeight: Infinity, + draggable: false + }; + W.extend(base, options); + + this._id = ++counter; + this._o = base; + this._f = controller; + if (element) { + this._e = element; + } else { + this._e = document.createElement("div"); + } + this._p = undefined; + this._w = undefined; + this._h = undefined; + this._x = 0; + this._y = 0; + + this._initElement(); + + if (this._o.draggable) { + this._initDraggable(); + } + + this._f.on("data", this._onData, this); + this._f.on("clearProperties", this._onClearProperties, this); + this._f.on("addProperty", this._onAddProperty, this); + this._f.on("newController", this._onNewController, this); + + for (var i = 0; i < this._f._controllers.length; ++i) { + this._onNewController(this._f._controllers[i]); + } + this._onData(this._f); + + View.collection[this._id] = this; + this._applyProperties(); + }, + "destructor": function() { + this._destroying = true; + this._f.off("data", this._onData, this); + this._f.off("clearProperties", this._onClearProperties, this); + this._f.off("addProperty", this._onAddProperty, this); + this._f.off("newController", this._onNewController, this); + + this.remove() + if (this._o.draggable) { + this._dg.destructor(); + } + + delete View.collection[this._id]; + + Subscribable.fn.destructor.call(this); + }, + "addClass": function(className) { + var arr = this._e.className.split(" "); + if (arr.indexOf(className) === -1) { + arr.push(className); + this._e.className = arr.join(" "); + } + }, + "_applyProperties": function() { + for (var i = 0; i < this._f.properties.length; ++i) { + var prop = this._f.properties[i]; + this._onAddProperty(prop.k, prop.p); + } + }, + "constrainHeight": function(h) { + h = Math.max(h, this._o.minHeight); + h = Math.min(h, this._o.maxHeight); + + return h; + }, + "constrainWidth": function(w) { + w = Math.max(w, this._o.minWidth); + w = Math.min(w, this._o.maxWidth); + + return w; + }, + "_initDraggable": function() { + this._dg = new Draggable(this, { + snapDistance: this._o.snapDistance + }); + }, + "_initElement": function() { + this._e.style.position = "absolute"; + this._e.style.top = "0"; + this._e.style.left = "0"; + this._e.style.boxSizing = "border-box"; + this._e.style.overflow = "hidden"; + this._e.id = this._id; + }, + "_onAddProperty": function(key, propertyName) { + var value = View.theme[key]; + if (value) { + this._e.style[propertyName] = value; + } + }, + "_onClearProperties": function() { +// for (var key in this._e.style) { +// if (this._e.style.hasOwnProperty(key)) { +// delete this._e.style[key]; +// } +// } + this._initElement(); + this._e.style.left = this._x + "px"; + this._e.style.top = this._y + "px"; + this._e.style.width = this._w + "px"; + this._e.style.height = this._h + "px"; + }, + "_onData": function() {}, + "_onNewController": function() {}, + "remove": function() { + if (this._p) { + this._p.removeChild(this); + } + }, + "removeClass": function(className) { + var arr = this._e.className.split(" "); + var index = arr.indexOf(className) + var toJoin = false; + while (index !== -1) { + arr.splice(index, 1); + index = arr.indexOf(className) + toJoin = true; + } + if (toJoin) { + this._e.className = arr.join(" "); + } + }, + "_resetTheme": function() { + this._onClearProperties(); + this._applyProperties(); + }, + "setConstSize": function(w, h) { + this._o.maxWidth = w; + this._o.maxHeight = h; + this._o.minWidth = w; + this._o.minHeight = h; + + if (this._w !== undefined && this._h !== undefined) { + this.setSize(this._w, this._h); + } + + this.trigger("changeLimits", this); + }, + "setLeft": function(l) { + this._x = l; + this._e.style.left = this._x + "px"; + }, + "_setLimits": function(minWidth, minHeight, maxWidth, maxHeight) { + var needToTell = false; + if (this._o.minWidth !== minWidth) { + needToTell = true; + this._o.minWidth = minWidth; + } + if (this._o.maxWidth !== maxWidth) { + needToTell = true; + this._o.maxWidth = maxWidth; + } + if (this._o.minHeight !== minHeight) { + needToTell = true; + this._o.minHeight = minHeight; + } + if (this._o.maxHeight !== maxHeight) { + needToTell = true; + this._o.maxHeight = maxHeight; + } + if (needToTell) { + this.trigger("changeLimits", this); + } + + return needToTell && this._events.changeLimits && this._events.changeLimits.length; //to see if someone actually going to listen that event + }, + "setMaxSize": function(w, h) { + this._o.maxWidth = w; + this._o.maxHeight = h; + + if (this._w !== undefined && this._h !== undefined) { + this.setSize(this._w, this._h); + } + + this.trigger("changeLimits", this); + }, + "setMinSize": function(w, h) { + this._o.minWidth = w; + this._o.minHeight = h; + + if (this._w !== undefined && this._h !== undefined) { + this.setSize(this._w, this._h); + } + + this.trigger("changeLimits", this); + }, + "setSize": function(w, h) { + this._w = this.constrainWidth(w); + this._h = this.constrainHeight(h); + + this._e.style.width = this._w + "px"; + this._e.style.height = this._h + "px"; + + this.trigger("resize", this._w, this._h); + }, + "setTop": function(t) { + this._y = t; + this._e.style.top = this._y + "px"; + }, + "trySize": function(w, h) { + return !(w < this._o.minWidth || h < this._o.minHeight || w > this._o.maxWidth || h > this._o.maxHeight) + } + }); + + View.theme = Object.create(null); + View.collection = Object.create(null); + View.setTheme = function(theme) { + for (var key in this.theme) { + delete this.theme[key]; + } + for (var key in theme) { + if (theme.hasOwnProperty(key)) { + this.theme[key] = theme[key]; + } + } + + for (var id in this.collection) { + this.collection[id]._resetTheme(); + } + } + + View.createByType = function(type, ctrl, opts) { + var typeName = this.ReversedViewType[type]; + if (typeName === undefined) { + throw new Error("Unknown ViewType: " + type); + } + var Type = this.constructors[typeName]; + if (Type === undefined) { + throw new Error("Constructor is not loaded yet, something is wrong"); + } + return new Type(ctrl, opts); + } + + View.initialize = function(rc, cb) { + var deps = []; + var types = []; + for (var key in this.ViewTypesPaths) { + if (this.ViewTypesPaths.hasOwnProperty(key)) { + if (!rc || rc.indexOf(key) !== -1) { + deps.push(this.ViewTypesPaths[key]); + types.push(key); + } + } + } + require(deps, function() { + for (var i = 0; i < types.length; ++i) { + View.constructors[types[i]] = arguments[i]; + } + cb(); + }); + } + + View.ViewType = { + Label: 0, + + Image: 3, + View: 4, + + Page: 102, + PanesList: 104 + }; + + View.ReversedViewType = { + "0": "Label", + + "3": "Image", + "4": "View", + + "101": "Nav", + "102": "Page", + "104": "PanesList" + }; + + View.ViewTypesPaths = { + Label: "views/label", + Nav: "views/nav", + Page: "views/page", + PanesList: "views/panesList", + Image: "views/image" + }; + + View.constructors = { + View: View + }; + + + return View; + }); +})(); diff --git a/magnus/CMakeLists.txt b/magnus/CMakeLists.txt new file mode 100644 index 0000000..c13dce7 --- /dev/null +++ b/magnus/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.12) +project(magnus) + +add_subdirectory(config) +add_subdirectory(lib) +add_subdirectory(middleware) +add_subdirectory(views) +add_subdirectory(test) +add_subdirectory(core) +add_subdirectory(pages) + +configure_file(package.json package.json) +configure_file(app.js app.js) + +execute_process(COMMAND npm install WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/magnus/app.js b/magnus/app.js new file mode 100644 index 0000000..389ab9c --- /dev/null +++ b/magnus/app.js @@ -0,0 +1,46 @@ +var express = require("express"); +var morgan = require("morgan"); +var favicon = require("serve-favicon"); +var Magnus = require("./core/magnus"); + +require("./lib/utils/globalMethods"); + +var config = require("./config"); +var log = require("./lib/log")(module); + +if (config.get("testing")) { + var Test = require("./test/test"); + var test = new Test() + test.run(); + test.destructor(); +} + +var app = express(); + +app.set('view engine', 'jade'); +app.set('views', __dirname + '/views'); + +app.use(favicon(__dirname + "/public/favicon.ico")); + +var httpLog = config.get("httpLog"); +if (httpLog) { + app.use(morgan('dev')); +} + +app.use(require("./middleware/reply")); + +app.use(express.static(__dirname + '/public')); + +app.use(require("./middleware/pageInMagnus")); +app.use(require("./middleware/notFound")); +app.use(require("./middleware/errorHandler")); + +var server = app.listen(config.get("webServerPort"), "127.0.0.1", function () { + + var port = server.address().port; + + log.info("Webserver is listening on port " + port); + +}); +var magnus = global.magnus = new Magnus(config); + diff --git a/magnus/config/CMakeLists.txt b/magnus/config/CMakeLists.txt new file mode 100644 index 0000000..5601421 --- /dev/null +++ b/magnus/config/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(index.js index.js) +configure_file(config.json config.json) \ No newline at end of file diff --git a/magnus/config/config.json b/magnus/config/config.json new file mode 100644 index 0000000..fea707e --- /dev/null +++ b/magnus/config/config.json @@ -0,0 +1,9 @@ +{ + "webServerPort": 3000, + "webSocketServerPort": 8081, + "version": "0.0.2", + "build": "debug", + "httpLog": false, + "testing": true, + "modelDestructionTimeout": 120000 +} diff --git a/magnus/config/index.js b/magnus/config/index.js new file mode 100644 index 0000000..b19fbb7 --- /dev/null +++ b/magnus/config/index.js @@ -0,0 +1,8 @@ +var nconf = require("nconf"); +var path = require("path"); + +nconf.argv() + .env() + .file({file: path.join(__dirname, 'config.json')}); + +module.exports = nconf; diff --git a/magnus/core/CMakeLists.txt b/magnus/core/CMakeLists.txt new file mode 100644 index 0000000..0a2d625 --- /dev/null +++ b/magnus/core/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(magnus.js magnus.js) +configure_file(commands.js commands.js) +configure_file(connector.js connector.js) diff --git a/magnus/core/commands.js b/magnus/core/commands.js new file mode 100644 index 0000000..906308e --- /dev/null +++ b/magnus/core/commands.js @@ -0,0 +1,85 @@ +"use strict"; + +var ModelVocabulary = require("../lib/wModel/vocabulary"); + +var Vocabulary = require("../lib/wType/vocabulary"); +var String = require("../lib/wType/string"); + +var Commands = ModelVocabulary.inherit({ + "className": "Commands", + "constructor": function(address) { + ModelVocabulary.fn.constructor.call(this, address); + + this._commands = global.Object.create(null); + }, + "destructor": function() { + for (var key in this._commands) { + var cmd = this._commands[key]; + if (cmd.enabled) { + this._removeHandler(cmd.handler); + } + cmd.name.destructor(); + cmd.handler.destructor(); + cmd.arguments.destructor(); + delete this._commands[key]; + } + + ModelVocabulary.fn.destructor.call(this); + }, + "addCommand": function(key, handler, args) { + if (this._commands[key]) { + throw new Error("Command with this key already exist"); + } + this._commands[key] = { + name: new String(key), + handler: handler, + arguments: args, + enabled: false + } + }, + "_disableCommand": function(cmd) { + this._removeHandler(cmd.handler); + cmd.enabled = false; + this.erase(cmd.name.toString()); + }, + "enableCommand": function(key, value) { + var cmd = this._commands[key]; + + if (!cmd) { + throw new Error("An attempt to access non existing command: " + key); + } + + if (cmd.enabled !== value) { + if (value) { + this._enableCommand(cmd); + } else { + this._disableCommand(cmd); + } + } + }, + "_enableCommand": function(cmd) { + this._addHandler(cmd.handler); + cmd.enabled = true; + + var vc = new Vocabulary(); + vc.insert("address", cmd.handler.address.clone()); + vc.insert("arguments", cmd.arguments.clone()); + + this.insert(cmd.name.toString(), vc); + }, + "removeCommand": function(name) { + var cmd = this._commands[name]; + if (cmd === undefined) { + throw new Error("An attempt to access non existing command: " + key); + } + if (cmd.enabled) { + this._disableCommand(cmd); + } + cmd.name.destructor(); + cmd.handler.destructor(); + cmd.arguments.destructor(); + delete this._commands[name]; + } +}); + +module.exports = Commands; diff --git a/magnus/core/connector.js b/magnus/core/connector.js new file mode 100644 index 0000000..59b2e61 --- /dev/null +++ b/magnus/core/connector.js @@ -0,0 +1,120 @@ +"use strict"; + +var Subscribable = require("../lib/utils/subscribable"); +var Handler = require("../lib/wDispatcher/handler"); +var String = require("../lib/wType/string"); +var Address = require("../lib/wType/address"); +var Uint64 = require("../lib/wType/uint64"); +var Object = require("../lib/wType/object"); +var Vocabulary = require("../lib/wType/vocabulary"); +var Socket = require("../lib/wSocket/socket"); + +var Connector = Subscribable.inherit({ + "className": "Connector", + "constructor": function(dp, srv, cmds) { + Subscribable.fn.constructor.call(this); + + this._dispatcher = dp; + this._server = srv; + this._commands = cmds; + this._nodes = global.Object.create(null); + this._ignoredNodes = global.Object.create(null); + + this._server.on("newConnection", this._onNewConnection, this); + this._server.on("closedConnection", this._onClosedConnection, this); + + var cn = new Address(["connect"]); + var ch = new Handler(this._commands.getAddress()["+"](cn), this, this._h_connect); + var vc = new Vocabulary(); + vc.insert("address", new Uint64(Object.objectType.String)); + vc.insert("port", new Uint64(Object.objectType.Uint64)); + this._commands.addCommand("connect", ch, vc); + this._commands.enableCommand("connect", true); + cn.destructor(); + }, + "destructor": function() { + this._server.off("newConnection", this._onNewConnection, this); + this._server.off("closedConnection", this._onClosedConnection, this); + + this._commands.removeCommand("connect"); + + for (var key in this._nodes) { + this._commands.removeCommand("disconnect" + key); + } + + Subscribable.fn.destructor.call(this); + }, + "addIgnoredNode": function(name) { + this._ignoredNodes[name] = true; + }, + "sendTo": function(key, event) { + var id = this._nodes[key]; + if (!id) { + throw new Error("An attempt to access non existing node in connector"); + } + this._server.getConnection(id).send(event); + }, + "_onNewConnection": function(socket) { + var name = socket.getRemoteName().toString(); + + if (this._ignoredNodes[name] === undefined) { + if (this._nodes[name] === undefined) { + if (this._server.getName().toString() === name) { + this.trigger("serviceMessage", "An attempt to connect node to itself, closing connection", 1); + setTimeout(this._server.closeConnection.bind(this._server, socket.getId())); + } else { + var dc = "disconnect"; + var dn = dc + name; + var dh = new Handler(this._commands.getAddress()["+"](new Address([dc, name])), this, this._h_disconnect); + this._commands.addCommand(dn, dh, new Vocabulary()); + this._commands.enableCommand(dn, true); + + this._nodes[name] = socket.getId(); + + this.trigger("serviceMessage", "New connection, id: " + socket.getId().toString(), 0); + socket.on("message", this._dispatcher.pass, this._dispatcher); + this.trigger("nodeConnected", name); + } + } else { + this.trigger("serviceMessage", "Node " + name + " tried to connect, but connection with that node is already open, closing new connection", 1); + setTimeout(this._server.closeConnection.bind(this._server, socket.getId())); + } + } else { + this.trigger("serviceMessage", "New connection, id: " + socket.getId().toString(), 0); + socket.on("message", this._dispatcher.pass, this._dispatcher); + } + }, + "_onClosedConnection": function(socket) { + this.trigger("serviceMessage", "Connection closed, id: " + socket.getId().toString()); + + var name = socket.getRemoteName().toString(); + if (this._ignoredNodes[name] === undefined) { + if (this._nodes[name]) { + this._commands.removeCommand("disconnect" + name); + delete this._nodes[name]; + this.trigger("nodeDisconnected", name); + } + } + }, + "getNodeSocket": function(key) { + var id = this._nodes[key]; + if (!id) { + throw new Error("An attempt to access non existing node in connector"); + } + return this._server.getConnection(id); + }, + "_h_connect": function(ev) { + var vc = ev.getData(); + this._server.openConnection(vc.at("address"), vc.at("port")); + }, + "_h_disconnect": function(ev) { + var addr = ev.getDestination(); + var id = this._nodes[addr.back().toString()]; + if (id) { + this._server.closeConnection(id); + } + + } +}); + +module.exports = Connector; diff --git a/magnus/core/magnus.js b/magnus/core/magnus.js new file mode 100644 index 0000000..d1f5089 --- /dev/null +++ b/magnus/core/magnus.js @@ -0,0 +1,153 @@ +"use strict"; +var Subscribable = require("../lib/utils/subscribable"); +var Socket = require("../lib/wSocket/socket"); +var Server = require("../lib/wSocket/server"); +var Address = require("../lib/wType/address"); +var String = require("../lib/wType/string"); +var Vocabulary = require("../lib/wType/vocabulary"); +var Dispatcher = require("../lib/wDispatcher/dispatcher"); +var ParentReporter = require("../lib/wDispatcher/parentreporter"); +var Logger = require("../lib/wDispatcher/logger"); +var log = require("../lib/log")(module); + +var Commands = require("./commands"); +var Connector = require("./connector"); + +var GlobalControls = require("../lib/wModel/globalControls"); +var PageStorage = require("../lib/wModel/pageStorage"); +var ModelString = require("../lib/wModel/string"); +var Attributes = require("../lib/wModel/attributes"); + +var HomePage = require("../pages/home"); +var MusicPage = require("../pages/music"); +var TestPage = require("../pages/test"); + +var Magnus = Subscribable.inherit({ + "className": "Magnus", + "constructor": function(config) { + Subscribable.fn.constructor.call(this); + + this._cfg = config; + + this._initDispatcher(); + this._initServer(); + this._initModels(); + this._initConnector(); + this._initPages(); + + var port = this._cfg.get("webSocketServerPort"); + this.server.listen(port); + + global.magnus = this; + }, + "_initConnector": function() { + this._connector = new Connector(this.dispatcher, this.server, this._commands); + + this._connector.on("serviceMessage", this._onModelServiceMessage, this); + this._connector.on("nodeConnected", this._onNodeConnected, this); + this._connector.on("nodeDisconnected", this._onNodeDisconnected, this); + + this._connector.addIgnoredNode("Lorgar"); + this._connector.addIgnoredNode("Roboute"); + }, + "_initDispatcher": function() { + this.dispatcher = new Dispatcher(); + this._logger = new Logger(); + this._pr = new ParentReporter(); + this.dispatcher.registerDefaultHandler(this._pr); + this.dispatcher.registerDefaultHandler(this._logger); + }, + "_initModels": function() { + this._commands = new Commands(new Address(["management"])); + + var version = new ModelString(new Address(["version"]), this._cfg.get("version")); + version.addProperty("backgroundColor","secondaryColor"); + version.addProperty("color", "secondaryFontColor"); + version.addProperty("fontFamily", "smallFont"); + version.addProperty("fontSize", "smallFontSize"); + + this._attributes = new Attributes(new Address(["attributes"])); + this._gc = new GlobalControls(new Address(["magnus", "gc"])); + var root = this._rootPage = new HomePage(new Address(["pages", "root"]), "root"); + this._ps = new PageStorage(new Address(["magnus", "ps"]), root, this._pr); + + this._commands.on("serviceMessage", this._onModelServiceMessage, this); + this._gc.on("serviceMessage", this._onModelServiceMessage, this); + this._ps.on("serviceMessage", this._onModelServiceMessage, this); + this._attributes.on("serviceMessage", this._onModelServiceMessage, this); + + this._attributes.addAttribute("name", new ModelString(new Address(["attributes", "name"]), "Magnus")); + this._attributes.addAttribute("connectionsAmount", new ModelString(new Address(["connectionsAmount"]), "0")); + this._attributes.addAttribute("version", version); + this._gc.addModelAsLink("version", version); + + this._commands.register(this.dispatcher, this.server); + this._gc.register(this.dispatcher, this.server); + this._ps.register(this.dispatcher, this.server); + this._attributes.register(this.dispatcher, this.server); + }, + "_initPages": function() { + this._gc.addNav("Home", this._rootPage.getAddress()); + + var music = this._musicPage = new MusicPage(new Address(["pages", "/music"]), "music"); + this._rootPage.addPage(music); + this._gc.addNav("Music", music.getAddress()); + + var test = new TestPage(new Address(["pages", "/test"]), "test"); + this._rootPage.addPage(test); + this._gc.addNav("Testing...", test.getAddress()); + }, + "_initServer": function() { + this.server = new Server("Magnus"); + this.server.on("ready", this._onServerReady, this); + this.server.on("connectionsCountChange", this._onConnectionsCountChange, this); + }, + "hasPage": function(name) { + return this._ps.hasPage(name); + }, + "_onConnectionsCountChange": function(count) { + this._attributes.setAttribute("connectionsAmount", count); + }, + "_onModelServiceMessage": function(msg, severity) { + var fn; + + switch (severity) { + case 2: + fn = log.error; + break; + case 1: + fn = log.warn; + break; + case 0: + default: + fn = log.info; + break; + } + + fn(msg); + }, + "_onNodeConnected": function(nodeName) { + switch (nodeName) { + case "Perturabo": + this._musicPage.showBandList(this._connector.getNodeSocket(nodeName)); + break; + case "Corax": + break; + } + }, + "_onNodeDisconnected": function(nodeName) { + switch (nodeName) { + case "Perturabo": + this._musicPage.showError(); + break; + case "Corax": + break; + } + }, + "_onServerReady": function() { + log.info("Magnus is listening on port " + this._cfg.get("webSocketServerPort")); + log.info("Magnus is ready"); + } +}); + +module.exports = Magnus; diff --git a/magnus/lib/CMakeLists.txt b/magnus/lib/CMakeLists.txt new file mode 100644 index 0000000..faec547 --- /dev/null +++ b/magnus/lib/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_subdirectory(wSocket) +add_subdirectory(utils) +add_subdirectory(wDispatcher) +add_subdirectory(wType) +add_subdirectory(wContainer) +add_subdirectory(wController) +add_subdirectory(wTest) +add_subdirectory(wModel) + +configure_file(log.js log.js) +configure_file(httpError.js httpError.js) diff --git a/magnus/lib/httpError.js b/magnus/lib/httpError.js new file mode 100644 index 0000000..8930528 --- /dev/null +++ b/magnus/lib/httpError.js @@ -0,0 +1,15 @@ +"use strict"; +var http = require("http"); + +class HttpError extends Error +{ + constructor(status, message) { + super(status, message); + Error.captureStackTrace(this, HttpError); + + this.status = status; + this.message = message || http.STATUS_CODES[status] || "Error"; + } +} + +module.exports = HttpError; diff --git a/magnus/lib/log.js b/magnus/lib/log.js new file mode 100644 index 0000000..51778e4 --- /dev/null +++ b/magnus/lib/log.js @@ -0,0 +1,18 @@ +var Winston = require("winston"); +var config = require("../config"); +var ENV = config.get('build'); + +function getLogger(module) { + var path = module.filename.split('/').slice(-2).join('/'); + + return new Winston.Logger({ + transports: [ + new Winston.transports.Console({ + colorize: true, + level: ENV == 'debug' ? 'debug' : 'error' + }) + ] + }); +} + +module.exports = getLogger; diff --git a/magnus/lib/utils/CMakeLists.txt b/magnus/lib/utils/CMakeLists.txt new file mode 100644 index 0000000..b63c0fc --- /dev/null +++ b/magnus/lib/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(utils/class.js lib/utils/class ${MAGNUS_DIR} node) +add_jslib(utils/subscribable.js lib/utils/subscribable ${MAGNUS_DIR} node) +add_jslib(utils/globalMethods.js lib/utils/globalMethods ${MAGNUS_DIR} node) \ No newline at end of file diff --git a/magnus/lib/wContainer/CMakeLists.txt b/magnus/lib/wContainer/CMakeLists.txt new file mode 100644 index 0000000..be30f4e --- /dev/null +++ b/magnus/lib/wContainer/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wContainer/abstractmap.js lib/wContainer/abstractmap ${MAGNUS_DIR} node) +add_jslib(wContainer/abstractlist.js lib/wContainer/abstractlist ${MAGNUS_DIR} node) +add_jslib(wContainer/abstractorder.js lib/wContainer/abstractorder ${MAGNUS_DIR} node) +add_jslib(wContainer/abstractpair.js lib/wContainer/abstractpair ${MAGNUS_DIR} node) +add_jslib(wContainer/abstractset.js lib/wContainer/abstractset ${MAGNUS_DIR} node) diff --git a/magnus/lib/wController/CMakeLists.txt b/magnus/lib/wController/CMakeLists.txt new file mode 100644 index 0000000..03ef980 --- /dev/null +++ b/magnus/lib/wController/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wController/list.js lib/wController/list ${MAGNUS_DIR} node) +add_jslib(wController/controller.js lib/wController/controller ${MAGNUS_DIR} node) +add_jslib(wController/vocabulary.js lib/wController/vocabulary ${MAGNUS_DIR} node) +add_jslib(wController/catalogue.js lib/wController/catalogue ${MAGNUS_DIR} node) diff --git a/magnus/lib/wDispatcher/CMakeLists.txt b/magnus/lib/wDispatcher/CMakeLists.txt new file mode 100644 index 0000000..3d03295 --- /dev/null +++ b/magnus/lib/wDispatcher/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wDispatcher/dispatcher.js lib/wDispatcher/dispatcher ${MAGNUS_DIR} node) +add_jslib(wDispatcher/defaulthandler.js lib/wDispatcher/defaulthandler ${MAGNUS_DIR} node) +add_jslib(wDispatcher/handler.js lib/wDispatcher/handler ${MAGNUS_DIR} node) +add_jslib(wDispatcher/logger.js lib/wDispatcher/logger ${MAGNUS_DIR} node) +add_jslib(wDispatcher/parentreporter.js lib/wDispatcher/parentreporter ${MAGNUS_DIR} node) diff --git a/magnus/lib/wModel/CMakeLists.txt b/magnus/lib/wModel/CMakeLists.txt new file mode 100644 index 0000000..01c6b6a --- /dev/null +++ b/magnus/lib/wModel/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wModel/globalControls.js lib/wModel/globalControls ${MAGNUS_DIR} node) +add_jslib(wModel/link.js lib/wModel/link ${MAGNUS_DIR} node) +add_jslib(wModel/list.js lib/wModel/list ${MAGNUS_DIR} node) +add_jslib(wModel/model.js lib/wModel/model ${MAGNUS_DIR} node) +add_jslib(wModel/page.js lib/wModel/page ${MAGNUS_DIR} node) +add_jslib(wModel/pageStorage.js lib/wModel/pageStorage ${MAGNUS_DIR} node) +add_jslib(wModel/panesList.js lib/wModel/panesList ${MAGNUS_DIR} node) +add_jslib(wModel/string.js lib/wModel/string ${MAGNUS_DIR} node) +add_jslib(wModel/theme.js lib/wModel/theme ${MAGNUS_DIR} node) +add_jslib(wModel/themeStorage.js lib/wModel/themeStorage ${MAGNUS_DIR} node) +add_jslib(wModel/vocabulary.js lib/wModel/vocabulary ${MAGNUS_DIR} node) +add_jslib(wModel/attributes.js lib/wModel/attributes ${MAGNUS_DIR} node) +add_jslib(wModel/image.js lib/wModel/image ${MAGNUS_DIR} node) + +add_subdirectory(proxy) diff --git a/magnus/lib/wModel/proxy/CMakeLists.txt b/magnus/lib/wModel/proxy/CMakeLists.txt new file mode 100644 index 0000000..2ef4f4b --- /dev/null +++ b/magnus/lib/wModel/proxy/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wModel/proxy/proxy.js lib/wModel/proxy/proxy ${MAGNUS_DIR} node) +add_jslib(wModel/proxy/list.js lib/wModel/proxy/list ${MAGNUS_DIR} node) +add_jslib(wModel/proxy/vocabulary.js lib/wModel/proxy/vocabulary ${MAGNUS_DIR} node) +add_jslib(wModel/proxy/catalogue.js lib/wModel/proxy/catalogue ${MAGNUS_DIR} node) + +configure_file(pane.js pane.js) diff --git a/magnus/lib/wModel/proxy/pane.js b/magnus/lib/wModel/proxy/pane.js new file mode 100644 index 0000000..5bc8b91 --- /dev/null +++ b/magnus/lib/wModel/proxy/pane.js @@ -0,0 +1,32 @@ +"use strict"; + +var MVocabulary = require("./vocabulary"); + +var Address = require("../../wType/address"); +var Boolean = require("../../wType/boolean"); + +var Pane = MVocabulary.inherit({ + "className": "Pane", + "constructor": function(address, controllerAddress, socket) { + MVocabulary.fn.constructor.call(this, address, controllerAddress, socket); + + if (this.constructor.pageAddress) { + this.hasPageLink = true; + + var id = address.back(); + this._pageLink = this.constructor.pageAddress["+"](new Address([id.toString()])); + } + }, + "_getAllData": function() { + var vc = this.controller.data.clone(); + + vc.insert("hasPageLink", new Boolean(this.hasPageLink)); + if (this.hasPageLink) { + vc.insert("pageLink", this._pageLink.clone()); + } + + return vc; + } +}); + +module.exports = Pane; diff --git a/magnus/lib/wSocket/CMakeLists.txt b/magnus/lib/wSocket/CMakeLists.txt new file mode 100644 index 0000000..0e45476 --- /dev/null +++ b/magnus/lib/wSocket/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(socket.js socket.js) +configure_file(server.js server.js) \ No newline at end of file diff --git a/magnus/lib/wSocket/server.js b/magnus/lib/wSocket/server.js new file mode 100644 index 0000000..f0d0ee5 --- /dev/null +++ b/magnus/lib/wSocket/server.js @@ -0,0 +1,154 @@ +"use strict"; + +var WebSocketServer = require("ws").Server; +var Socket = require("./socket"); +var Subscribable = require("../utils/subscribable"); +var AbstractMap = require("../wContainer/abstractmap"); +var AbstractSet = require("../wContainer/abstractset"); +var String = require("../wType/string"); +var Uint64 = require("../wType/uint64"); + +var Server = Subscribable.inherit({ + "className": "Server", + "constructor": function(name) { + if (!name) { + throw new Error("Can't construct a socket without a name"); + } + Subscribable.fn.constructor.call(this); + + this._lastId = new Uint64(0); + this._pool = new Server.Uint64Set(true); + this._name = name instanceof String ? name : new String(name); + this._server = undefined; + this._connections = new Server.ConnectionsMap(true); + this._listening = false; + + this._initProxy(); + }, + "destructor": function() { + if (this._listening) { + this._server.stop(); + delete this._server; + } + this._lastId.destructor(); + this._pool.destructor(); + this._name.destructor(); + this._connections.destructor(); + + Subscribable.fn.destructor.call(this); + }, + "getName": function() { + return this._name; + }, + "listen": function(port) { + if (!this._listening) { + this._listening = true; + this._server = new WebSocketServer({port: port}, this._proxy.onReady); + this._server.on("connection", this._proxy.onConnection); + } + }, + "stop": function() { + if (this._listening) { + this._listening = false; + this._server.stop(); + this._lastId = new Uint64(0); + this._connections.clear(); + this._pool.clear(); + delete this._server; + } + }, + "getConnection": function(id) { + var itr = this._connections.find(id); + if (itr["=="](this._connections.end())) { + throw new Error("Connection not found"); + } + return itr["*"]().second; + }, + "getConnectionsCount": function() { + return this._connections.size(); + }, + "openConnection": function(addr, port) { + var webSocket = new Subscribable(); + var wSocket = this._createSocket(webSocket); + wSocket._socket.destructor(); + wSocket.open(addr, port); + }, + "closeConnection": function(id) { + var itr = this._connections.find(id); + if (itr["=="](this._connections.end())) { + throw new Error("Connection not found"); + } + itr["*"]().second.close(); + }, + "_createSocket": function(socket) { + var connectionId; + if (this._pool.size() === 0) { + this._lastId["++"]() + connectionId = this._lastId.clone(); + } else { + var itr = this._pool.begin(); + connectionId = itr["*"]().clone(); + this._pool.erase(itr); + } + var wSocket = new Socket(this._name, socket, connectionId); + this._connections.insert(connectionId, wSocket); + + wSocket.on("connected", this._onSocketConnected.bind(this, wSocket)); + wSocket.on("disconnected", this._onSocketDisconnected.bind(this, wSocket)); + wSocket.on("negotiationId", this._onSocketNegotiationId.bind(this, wSocket)); + + return wSocket; + }, + "_initProxy": function() { + this._proxy = { + onConnection: this._onConnection.bind(this), + onReady: this._onReady.bind(this) + }; + }, + "_onConnection": function(socket) { + var wSocket = this._createSocket(socket); + wSocket._setRemoteId(); + }, + "_onReady": function() { + this.trigger("ready"); + }, + "_onSocketConnected": function(socket) { + this.trigger("newConnection", socket); + this.trigger("connectionCountChange", this._connections.size()); + }, + "_onSocketDisconnected": function(socket) { + var cItr = this._connections.find(socket.getId()); + this._pool.insert(socket.getId().clone()); + this.trigger("closedConnection", socket); + this.trigger("connectionCountChange", this._connections.size()); + setTimeout(this._connections.erase.bind(this._connections, cItr), 1); + }, + "_onSocketNegotiationId": function(socket, id) { + var oldId = socket.getId(); + if (id["=="](oldId)) { + socket._setRemoteName(); + } else { + var pItr = this._pool.lowerBound(id); + var newId; + if (pItr["=="](this._pool.end())) { + this._lastId["++"](); + newId = this._lastId.clone(); + } else { + newId = pItr["*"]().clone(); + this._pool.erase(pItr); + } + var itr = this._connections.find(oldId); + itr["*"]().second = undefined; //to prevent autodestruction of the socket; + this._connections.erase(itr); + this._pool.insert(oldId); + socket._id = newId; + this._connections.insert(newId.clone(), socket); + socket._setRemoteId(); + } + } +}); + +Server.ConnectionsMap = AbstractMap.template(Uint64, Socket); +Server.Uint64Set = AbstractSet.template(Uint64); + +module.exports = Server; diff --git a/magnus/lib/wSocket/socket.js b/magnus/lib/wSocket/socket.js new file mode 100644 index 0000000..c3db331 --- /dev/null +++ b/magnus/lib/wSocket/socket.js @@ -0,0 +1,217 @@ +"use strict"; + +var WebSocket = require("ws"); +var Subscribable = require("../utils/subscribable"); +var Event = require("../wType/event"); +var ByteArray = require("../wType/bytearray"); +var String = require("../wType/string"); +var Vocabulary = require("../wType/vocabulary"); +var Uint64 = require("../wType/uint64"); +var Address = require("../wType/address"); +var factory = require("../wType/factory"); + +var Socket = Subscribable.inherit({ + "className": "Socket", + "constructor": function(name, socket, id) { + if (!name) { + throw new Error("Can't construct a socket without a name"); + } + Subscribable.fn.constructor.call(this); + + this._state = DISCONNECTED; + this._dState = SIZE; + this._name = name instanceof String ? name : new String(name); + this._remoteName = new String(); + this._id = new Uint64(0); + this._serverCreated = false; + this._helperBuffer = new ByteArray(4); + + this._initProxy(); + if (socket) { + this._serverCreated = true; + this._socket = socket; + this._id.destructor(); + this._id = id.clone(); + + this._socket.on("close", this._proxy.onClose); + this._socket.on("error", this._proxy.onError); + this._socket.on("message", this._proxy.onMessage); + } + }, + "destructor": function() { + this.close(); + if (this._state === DISCONNECTING) { + var onclose = function() { + Subscribable.fn.destructor.call(this); + } + this.on("disconnected", onclose.bind(this)); + } else { + Subscribable.fn.destructor.call(this); + } + }, + "close": function() { + if ((this._state !== DISCONNECTED) && (this._state !== DISCONNECTING)) { + this._state = DISCONNECTING; + this._socket.close(); + } + }, + "getId": function() { + return this._id; + }, + "getRemoteName": function() { + return this._remoteName; + }, + "_initProxy": function() { + this._proxy = { + onClose: this._onClose.bind(this), + onError: this._onError.bind(this), + onMessage: this._onMessage.bind(this) + }; + }, + "isOpened": function() { + return this._state !== undefined && this._state === CONNECTED; + }, + "_onClose": function(ev) { + this._state = DISCONNECTED; + this.trigger("disconnected", ev, this); + }, + "_onError": function(err) { + this.trigger("error", err); + }, + "_onEvent": function(ev) { + if (ev.isSystem()) { + var cmd = ev._data.at("command").toString(); + + switch(cmd) { + case "setId": + if (this._serverCreated) { + if (this._state === CONNECTING) { + this.trigger("negotiationId", ev._data.at("id")); + } else { + throw new Error("An attempt to set id in unexpected time"); + } + } else { + this._setId(ev._data.at("id")); + this._setRemoteName(); + } + break; + case "setName": + this._setName(ev._data.at("name")); + if (!ev._data.at("yourName")["=="](this._name)) { + this._setRemoteName(); + } + this._state = CONNECTED; + this.trigger("connected"); + break; + default: + throw new Error("Unknown system command: " + cmd); + } + } else { + this.trigger("message", ev); + } + ev.destructor(); + }, + "_onMessage": function(msg) { + var raw = new Uint8Array(msg); + var i = 0; + + while (i < raw.length) { + switch (this._dState) { + case SIZE: + i = this._helperBuffer.fill(raw, raw.length, i); + + if (this._helperBuffer.filled()) { + var size = this._helperBuffer.pop32(); + this._helperBuffer.destructor(); + this._helperBuffer = new ByteArray(size + 1); + this._dState = BODY; + } + break; + case BODY: + i = this._helperBuffer.fill(raw, raw.length, i); + + if (this._helperBuffer.filled()) { + var ev = factory(this._helperBuffer); + this._onEvent(ev); + this._helperBuffer.destructor(); + this._helperBuffer = new ByteArray(4); + this._dState = SIZE; + } + break; + } + } + }, + "open": function(addr, port) { + if (this._state === DISCONNECTED) { + this._state = CONNECTING; + this._remoteName.destructor(); + this._remoteName = new String(); + this._socket = new WebSocket("ws://"+ addr + ":" + port); + + this._socket.on("close", this._proxy.onClose); + this._socket.on("error", this._proxy.onError); + this._socket.on("message", this._proxy.onMessage); + } + }, + "send": function(ev) { + var size = ev.size(); + var ba = new ByteArray(size + 5); + ba.push32(size); + ba.push8(ev.getType()); + ev.serialize(ba); + + this._socket.send(ba.data().buffer); + }, + "_setId": function(id) { + if (this._state === CONNECTING) { + this._id.destructor(); + this._id = id.clone(); + } else { + throw new Error("An attempt to set id in unexpected time"); + } + }, + "_setName": function(name) { + if ((this._state === CONNECTING) && (this._id.valueOf() !== 0)) { + this._remoteName.destructor(); + this._remoteName = name.clone(); + } else { + throw new Error("An attempt to set name in unexpected time"); + } + }, + "_setRemoteName": function() { + var vc = new Vocabulary(); + vc.insert("command", new String("setName")); + vc.insert("name", this._name.clone()); + vc.insert("yourName", this._remoteName.clone()); + + var ev = new Event(new Address(), vc, true); + ev.setSenderId(this._id.clone()); + this.send(ev); + + ev.destructor(); + }, + "_setRemoteId": function() { + if (this._state === DISCONNECTED) { + this._state = CONNECTING; + } + var vc = new Vocabulary(); + vc.insert("command", new String("setId")); + vc.insert("id", this._id.clone()); + + var ev = new Event(new Address(), vc, true); + ev.setSenderId(this._id.clone()); + this.send(ev); + + ev.destructor(); + } +}); + +var DISCONNECTED = 111; +var DISCONNECTING = 110; +var CONNECTING = 101; +var CONNECTED = 100; + +var SIZE = 1 +var BODY = 10; + +module.exports = Socket; diff --git a/magnus/lib/wTest/CMakeLists.txt b/magnus/lib/wTest/CMakeLists.txt new file mode 100644 index 0000000..0515e42 --- /dev/null +++ b/magnus/lib/wTest/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wTest/test.js lib/wTest/test ${MAGNUS_DIR} node) +add_jslib(wTest/abstractmap.js lib/wTest/abstractmap ${MAGNUS_DIR} node) +add_jslib(wTest/abstractlist.js lib/wTest/abstractlist ${MAGNUS_DIR} node) +add_jslib(wTest/abstractorder.js lib/wTest/abstractorder ${MAGNUS_DIR} node) +add_jslib(wTest/uint64.js lib/wTest/uint64 ${MAGNUS_DIR} node) diff --git a/magnus/lib/wType/CMakeLists.txt b/magnus/lib/wType/CMakeLists.txt new file mode 100644 index 0000000..fe9776e --- /dev/null +++ b/magnus/lib/wType/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_jslib(wType/address.js lib/wType/address ${MAGNUS_DIR} node) +add_jslib(wType/boolean.js lib/wType/boolean ${MAGNUS_DIR} node) +add_jslib(wType/bytearray.js lib/wType/bytearray ${MAGNUS_DIR} node) +add_jslib(wType/event.js lib/wType/event ${MAGNUS_DIR} node) +add_jslib(wType/object.js lib/wType/object ${MAGNUS_DIR} node) +add_jslib(wType/string.js lib/wType/string ${MAGNUS_DIR} node) +add_jslib(wType/uint64.js lib/wType/uint64 ${MAGNUS_DIR} node) +add_jslib(wType/vector.js lib/wType/vector ${MAGNUS_DIR} node) +add_jslib(wType/vocabulary.js lib/wType/vocabulary ${MAGNUS_DIR} node) +add_jslib(wType/blob.js lib/wType/blob ${MAGNUS_DIR} node) +add_jslib(wType/factory.js lib/wType/factory ${MAGNUS_DIR} node) diff --git a/magnus/middleware/CMakeLists.txt b/magnus/middleware/CMakeLists.txt new file mode 100644 index 0000000..9c73315 --- /dev/null +++ b/magnus/middleware/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(errorHandler.js errorHandler.js) +configure_file(reply.js reply.js) +configure_file(notFound.js notFound.js) +configure_file(pageInMagnus.js pageInMagnus.js) diff --git a/magnus/middleware/errorHandler.js b/magnus/middleware/errorHandler.js new file mode 100644 index 0000000..5a18fe4 --- /dev/null +++ b/magnus/middleware/errorHandler.js @@ -0,0 +1,36 @@ +var defaultHandler = require("errorhandler"); + +var config = require("../config"); +var log = require("../lib/log"); +var HttpError = require("../lib/httpError"); + +function errorHandler(err, req, res, next) { + if (typeof err == "number") { + err = new HttpError(err); + } + + if (err instanceof HttpError) { + sendHttpError(err, res, req); + } else { + if (config.get("build") === "debug") { + var handler = defaultHandler(); + handler(err, req, res, next); + } else { + log.error(err); + err = new HttpError(500); + sendHttpError(err, res, req); + } + } +} + +function sendHttpError(error, res, req) { + res.status(error.status); + //if (req.headers['x-requested-with'] == 'XMLHttpRequest') { + // res.json(error); + //} else { + // res.reply(error); + //} + res.reply(error); +} + +module.exports = errorHandler; diff --git a/magnus/middleware/notFound.js b/magnus/middleware/notFound.js new file mode 100644 index 0000000..8d97b06 --- /dev/null +++ b/magnus/middleware/notFound.js @@ -0,0 +1,6 @@ +"use strict"; +var HttpError = require("../lib/httpError"); + +module.exports = function(req, res, next) { + return next(new HttpError(404, 'Page not found!')); +}; diff --git a/magnus/middleware/pageInMagnus.js b/magnus/middleware/pageInMagnus.js new file mode 100644 index 0000000..bd2553a --- /dev/null +++ b/magnus/middleware/pageInMagnus.js @@ -0,0 +1,9 @@ +"use strict"; +module.exports = function(req, res, next) { + if (global.magnus.hasPage(req.path)) { + res.reply("Building " + req.path + "..."); + } else { + next(); + } +}; + diff --git a/magnus/middleware/reply.js b/magnus/middleware/reply.js new file mode 100644 index 0000000..bdd49be --- /dev/null +++ b/magnus/middleware/reply.js @@ -0,0 +1,8 @@ +"use strict"; +var path = require("path"); +module.exports = function(req, res, next) { + res.reply = function(info) { + this.render("index", {info: info}); + }; + next(); +}; diff --git a/magnus/package.json b/magnus/package.json new file mode 100644 index 0000000..76a83ca --- /dev/null +++ b/magnus/package.json @@ -0,0 +1,20 @@ +{ + "name": "magnus", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "express": "*", + "async": "*", + "winston": "*", + "morgan": "*", + "nconf": "*", + "errorhandler": "*", + "serve-favicon": "*", + "jade": "*", + "ws": "*", + "bintrees": "*" + } +} diff --git a/magnus/pages/CMakeLists.txt b/magnus/pages/CMakeLists.txt new file mode 100644 index 0000000..827434a --- /dev/null +++ b/magnus/pages/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(music.js music.js) +configure_file(home.js home.js) +configure_file(test.js test.js) +configure_file(list.js list.js) +configure_file(tempPage.js tempPage.js) +configure_file(artist.js artist.js) +configure_file(album.js album.js) +configure_file(song.js song.js) diff --git a/magnus/pages/album.js b/magnus/pages/album.js new file mode 100644 index 0000000..62146a5 --- /dev/null +++ b/magnus/pages/album.js @@ -0,0 +1,144 @@ +"use strict"; + +var TempPage = require("./tempPage"); +var String = require("../lib/wModel/string"); +var ProxyCatModel = require("../lib/wModel/proxy/catalogue"); +var PaneModel = require("../lib/wModel/proxy/pane"); +var Image = require("../lib/wModel/image"); +var Model = require("../lib/wModel/model"); + +var VCController = require("../lib/wController/vocabulary"); + +var Address = require("../lib/wType/address"); +var Uint64 = require("../lib/wType/uint64"); +var Vocabulary = require("../lib/wType/vocabulary"); + +var AlbumPage = TempPage.inherit({ + "className": "AlbumPage", + "constructor": function(address, name, id, proxySocket) { + TempPage.fn.constructor.call(this, address, name); + + this._remoteId = id; + this._proxySocket = proxySocket; + this._ctrls = Object.create(null); + + this._image = new Image(this._address["+"](new Address(["image"])), new Uint64(0)); + var imageOptions = new Vocabulary(); + imageOptions.insert("minWidth", new Uint64(200)); + imageOptions.insert("maxWidth", new Uint64(200)); + imageOptions.insert("minHeight", new Uint64(200)); + imageOptions.insert("maxHeight", new Uint64(200)); + this.addItem(this._image, 0, 0, 2, 1, TempPage.Aligment.CenterCenter, new Uint64(TempPage.getModelTypeId(this._image)), imageOptions); + + var header = this._header = new String(this._address["+"](new Address(["header"])), ""); + header.addProperty("fontFamily", "casualFont"); + this.addItem(header, 0, 1, 1, 1, TempPage.Aligment.LeftTop); + + var artist = this._artist = new String(this._address["+"](new Address(["artist"])), "Artist name"); + artist.addProperty("fontFamily", "casualFont"); + this.addItem(artist, 1, 1, 1, 1, TempPage.Aligment.LeftTop); + + var spacer = new Model(this._address["+"](new Address(["spacer"]))); + this.addItem(spacer, 0, 2, 2, 1); + + this._songs = new ProxyCatModel( + this._address["+"](new Address(["songs"])), + new Address(["songs"]), + { + sorting: {ascending: true, field: "name"}, + filter: {album: id.clone()} + }, + proxySocket + ); + this._songs.className = "PanesList"; + var PaneClass = PaneModel.Songs; + this._songs.setChildrenClass(PaneClass); + this.addItem(this._songs, 2, 0, 1, 3, TempPage.Aligment.CenterTop); + }, + "destructor": function() { + this._clearCtrls(); + + TempPage.fn.destructor.call(this); + }, + "_clearCtrls": function() { + for (var key in this._ctrls) { + this._ctrls[key].destructor(); + } + + this._ctrls = Object.create(null); + }, + "_onAlbumNewElement": function(key, el) { + switch(key) { + case "name": + this._header.set(el); + break; + case "image": + this._image.set(el.clone()); + break; + case "artist": + var arVC = new VCController(new Address(["artists", el.toString()])); + arVC.on("newElement", this._onArtistNewElement, this); + arVC.on("removeElement", this._onArtistRemoveElement, this); + this._ctrls.artist = arVC; + arVC.register(this._dp, this._proxySocket); + arVC.subscribe(); + break; + } + }, + "_onAlbumRemoveElement": function(key) { + switch(key) { + case "name": + this._header.set(""); + break; + case "image": + this._image.set(new Uint64(0)); + break; + case "artist": + this._artist.set(""); + this._ctrls.artist.destructor(); + delete this._ctrls.artist; + } + }, + "_onArtistNewElement": function(key, el) { + switch(key) { + case "name": + this._artist.set(el); + break; + } + }, + "_onArtistRemoveElement": function(key) { + switch(key) { + case "name": + this._artist.set("unknown"); + } + }, + "register": function(dp, server) { + TempPage.fn.register.call(this, dp, server); + + for (var key in this._ctrls) { + this._ctrls[key].register(dp, this._proxySocket); + } + }, + "setParentReporter": function(pr) { + TempPage.fn.setParentReporter.call(this, pr); + + this._pr.registerParent(this._songs.getAddress(), this._songs.reporterHandler); + this._songs.subscribe(); + + var albumVC = new VCController(new Address(["albums", this._remoteId.toString()])); + albumVC.on("newElement", this._onAlbumNewElement, this); + albumVC.on("removeElement", this._onAlbumRemoveElement, this); + this._ctrls.album = albumVC; + albumVC.register(this._dp, this._proxySocket); + albumVC.subscribe(); + }, + "unsetParentReporter": function() { + this._pr.unregisterParent(this._songs.getAddress()); + + this._clearCtrls(); + + TempPage.fn.unsetParentReporter.call(this); + } +}); + +module.exports = AlbumPage; diff --git a/magnus/pages/artist.js b/magnus/pages/artist.js new file mode 100644 index 0000000..ef496c7 --- /dev/null +++ b/magnus/pages/artist.js @@ -0,0 +1,112 @@ +"use strict"; + +var TempPage = require("./tempPage"); +var String = require("../lib/wModel/string"); +var ProxyCatModel = require("../lib/wModel/proxy/catalogue"); +var PaneModel = require("../lib/wModel/proxy/pane"); + +var VCController = require("../lib/wController/vocabulary"); + +var Address = require("../lib/wType/address"); + +var ArtistPage = TempPage.inherit({ + "className": "ArtistPage", + "constructor": function(address, name, id, proxySocket) { + TempPage.fn.constructor.call(this, address, name); + + this._remoteId = id; + this._proxySocket = proxySocket; + this._ctrls = Object.create(null); + + var header = this._header = new String(this._address["+"](new Address(["header"])), ""); + header.addProperty("fontFamily", "casualFont"); + this.addItem(header, 0, 0, 1, 1, TempPage.Aligment.CenterTop); + + this._albums = new ProxyCatModel( + this._address["+"](new Address(["albums"])), + new Address(["albums"]), + { + sorting: {ascending: true, field: "name"}, + filter: {artist: id.clone()} + }, + proxySocket + ); + this._albums.className = "PanesList"; + var PaneClass = PaneModel.Albums; + this._albums.setChildrenClass(PaneClass); + this.addItem(this._albums, 1, 0, 1, 1, TempPage.Aligment.CenterTop); + + this._songs = new ProxyCatModel( + this._address["+"](new Address(["songs"])), + new Address(["songs"]), + { + sorting: {ascending: true, field: "name"}, + filter: {artist: id.clone()} + }, + proxySocket + ); + this._songs.className = "PanesList"; + var PaneClass = PaneModel.Songs; + this._songs.setChildrenClass(PaneClass); + this.addItem(this._songs, 2, 0, 1, 1, TempPage.Aligment.CenterTop); + }, + "destructor": function() { + this._clearCtrls(); + + TempPage.fn.destructor.call(this); + }, + "_clearCtrls": function() { + for (var key in this._ctrls) { + this._ctrls[key].destructor(); + } + + this._ctrls = Object.create(null); + }, + "_onArtistNewElement": function(key, el) { + switch(key) { + case "name": + this._header.set(el); + break; + } + }, + "_onArtistRemoveElement": function(key) { + switch(key) { + case "name": + this._header.set(""); + break; + } + }, + "register": function(dp, server) { + TempPage.fn.register.call(this, dp, server); + + for (var key in this._ctrls) { + this._ctrls[key].register(dp, this._proxySocket); + } + }, + "setParentReporter": function(pr) { + TempPage.fn.setParentReporter.call(this, pr); + + this._pr.registerParent(this._albums.getAddress(), this._albums.reporterHandler); + this._pr.registerParent(this._songs.getAddress(), this._songs.reporterHandler); + + this._albums.subscribe() + this._songs.subscribe(); + + var artistVC = new VCController(new Address(["artists", this._remoteId.toString()])); + artistVC.on("newElement", this._onArtistNewElement, this); + artistVC.on("removeElement", this._onArtistRemoveElement, this); + this._ctrls.artist = artistVC; + artistVC.register(this._dp, this._proxySocket); + artistVC.subscribe(); + }, + "unsetParentReporter": function() { + this._pr.unregisterParent(this._albums.getAddress()); + this._pr.unregisterParent(this._songs.getAddress()); + + this._clearCtrls(); + + TempPage.fn.unsetParentReporter.call(this); + } +}); + +module.exports = ArtistPage; diff --git a/magnus/pages/home.js b/magnus/pages/home.js new file mode 100644 index 0000000..a99058d --- /dev/null +++ b/magnus/pages/home.js @@ -0,0 +1,19 @@ +"use strict"; + +var Page = require("../lib/wModel/page"); +var String = require("../lib/wModel/string"); + +var Address = require("../lib/wType/address"); + +var HomePage = Page.inherit({ + "className": "HomePage", + "constructor": function(address, name) { + Page.fn.constructor.call(this, address, name); + + var header = new String(this._address["+"](new Address(["message"])), "This is the root page"); + header.addProperty("fontFamily", "casualFont"); + this.addItem(header, 0, 0, 1, 1, Page.Aligment.CenterTop); + } +}); + +module.exports = HomePage; diff --git a/magnus/pages/list.js b/magnus/pages/list.js new file mode 100644 index 0000000..e4c91d9 --- /dev/null +++ b/magnus/pages/list.js @@ -0,0 +1,115 @@ +"use strict"; + +var Page = require("../lib/wModel/page"); +var String = require("../lib/wModel/string"); +var ProxyCatModel = require("../lib/wModel/proxy/catalogue"); +var PaneModel = require("../lib/wModel/proxy/pane"); + +var Address = require("../lib/wType/address"); +var Uint64 = require("../lib/wType/uint64"); + +var Handler = require("../lib/wDispatcher/handler"); + +var List = Page.inherit({ + "className": "ListPage", + "constructor": function(address, name, remoteAddress, socket, ChildClass) { + Page.fn.constructor.call(this, address, name); + + this._proxySocket = socket; + this._ChildClass = ChildClass; + + var header = new String(this._address["+"](new Address(["header"])), name); + header.addProperty("fontFamily", "casualFont"); + this.addItem(header, 0, 0, 1, 1, Page.Aligment.CenterTop); + + this._list = new ProxyCatModel( + this._address["+"](new Address(["list"])), + remoteAddress, + { + sorting: {ascending: true, field: "name"} + }, + socket + ); + this._list.className = "PanesList"; + var PaneClass = PaneModel[name]; + if (!PaneClass) { + PaneClass = PaneModel.inherit({}); + PaneClass.pageAddress = address; + } + this._list.setChildrenClass(PaneClass); + this.addItem(this._list, 1, 0, 1, 1, Page.Aligment.CenterTop); + + this._reporterHandler = new Handler(this._address["+"](new Address(["subscribeMember"])), this, this._h_subscribeMember); + }, + "destructor": function() { + this._reporterHandler.destructor(); + this.removeItem(this._list); + this._list.destructor(); + + Page.fn.destructor.call(this); + }, + "_createChildPage": function(id) { + var childName = id.toString(); + var postfix = new Address([childName]); + var childAddr = this._address["+"](postfix); + + var child = new this._ChildClass(childAddr, childName, id.clone(), this._proxySocket); + this.addPage(child); + child.on("destroyMe", this._destroyChild.bind(this, child)); //to remove model if it has no subscribers + child.checkSubscribersAndDestroy(); + + return child; + }, + "_destroyChild": function(child) { + this.removePage(child); + child.destructor(); + }, + "getChildPage": function(name) { + var child = this._childPages[name]; + if (child === undefined) { + var int = parseInt(name); + if (int == name) { + var id = new Uint64(int); + var itr = this._list.controller.data.find(id); + if (!itr["=="](this._list.controller.data.end())) { + child = this._createChildPage(id); + } + } + } + + return child; + }, + "_h_subscribeMember": function(ev) { + var dest = ev.getDestination(); + var lastHops = dest["<<"](this._address.length()); + + if (lastHops.length() === 2) { + var command = lastHops.back().toString(); + var numId = parseInt(lastHops.front().toString()); + if ((command === "subscribe" || command === "get" || command === "ping") && numId === numId) { + var id = new Uint64(numId); + var child = this._createChildPage(id); + child["_h_" + command](ev); + } else { + this.trigger("serviceMessage", "ListPage model got a strange event: " + ev.toString(), 1); + } + } else { + this.trigger("serviceMessage", "ListPage model got a strange event: " + ev.toString(), 1); + } + }, + "setParentReporter": function(pr) { + Page.fn.setParentReporter.call(this, pr); + + this._pr.registerParent(this._list.getAddress(), this._list.reporterHandler); + this._pr.registerParent(this.getAddress(), this._reporterHandler); + this._list.subscribe(); + }, + "unsetParentReporter": function() { + this._pr.unregisterParent(this.getAddress()); + this._pr.unregisterParent(this._list.getAddress()); + + Page.fn.unsetParentReporter.call(this); + } +}); + +module.exports = List; diff --git a/magnus/pages/music.js b/magnus/pages/music.js new file mode 100644 index 0000000..50296c4 --- /dev/null +++ b/magnus/pages/music.js @@ -0,0 +1,198 @@ +"use strict"; + +var Page = require("../lib/wModel/page"); +var String = require("../lib/wModel/string"); +var PanesList = require("../lib/wModel/panesList"); + +var Address = require("../lib/wType/address"); +var Vocabulary = require("../lib/wType/vocabulary"); +var Boolean = require("../lib/wType/boolean"); + +var Link = require("../lib/wModel/link"); + +var List = require("./list"); +var Artist = require("./artist"); +var Album = require("./album"); +var Song = require("./song"); + +var PaneModel = require("../lib/wModel/proxy/pane"); + +var MusicPage = Page.inherit({ + "className": "MusicPage", + "constructor": function(address, name) { + Page.fn.constructor.call(this, address, name); + + this._dbConnected = false; + this._addresses = Object.create(null); + + this._createAddresses(); + + var header = new String(this._address["+"](new Address(["header"])), "Music"); + header.addProperty("fontFamily", "casualFont"); + //var hvo = new Vocabulary(); + this.addItem(header, 0, 0, 1, 1, Page.Aligment.CenterTop); + + this._errMessage = new String(this._address["+"](new Address(["message"])), "Database is not connected"); + this._errMessage.addProperty("fontFamily", "largeFont"); + this._errMessage.addProperty("fontSize", "largeFontSize"); + + this.addItem(this._errMessage, 1, 0, 1, 1, Page.Aligment.CenterTop); + }, + "destructor": function() { + if (this._dbConnected && this._hasParentReporter) { + this._destroyLists(); + } else { + this.removeItem(this._errMessage); + } + this._errMessage.destructor(); + + for (var tag in this._addresses) { + var group = this._addresses[tag]; + for (var name in group) { + group[name].destructor(); + } + } + + Page.fn.destructor.call(this); + }, + "_createAddresses": function() { + var ra = new Address(["artists"]); + var ral = new Address(["albums"]); + var rs = new Address(["songs"]); + + var artists = Object.create(null); + var albums = Object.create(null); + var songs = Object.create(null); + + artists.remote = ra.clone(); + artists.local = this._address["+"](ra); + + albums.remote = ral.clone(); + albums.local = this._address["+"](ral); + + songs.remote = rs.clone(); + songs.local = this._address["+"](rs); + + this._addresses.artists = artists; + this._addresses.albums = albums; + this._addresses.songs = songs; + + var PaneArtist = PaneModel.Artists; + if (!PaneArtist) { + PaneArtist = PaneModel.inherit({}); + PaneModel.Artists = PaneArtist; + } else { + PaneArtist.pageAddress.destructor() + } + PaneArtist.pageAddress = artists.local.clone(); + + var PaneAlbum = PaneModel.Albums; + if (!PaneAlbum) { + PaneAlbum = PaneModel.inherit({}); + PaneModel.Albums = PaneAlbum; + } else { + PaneAlbum.pageAddress.destructor() + } + PaneAlbum.pageAddress = albums.local.clone(); + + var PaneSongs = PaneModel.Songs; + if (!PaneSongs) { + PaneSongs = PaneModel.inherit({}); + PaneModel.Songs = PaneSongs; + } else { + PaneSongs.pageAddress.destructor() + } + PaneSongs.pageAddress = songs.local.clone(); + + ra.destructor(); + ral.destructor(); + rs.destructor(); + }, + "_createLists": function(socket) { + this._artists = new List( + this._addresses.artists.local.clone(), + "Artists", + this._addresses.artists.remote.clone(), + socket, + Artist + ); + this._artistsLink = new Link(this._address["+"](new Address(["artistsLink"])), "Artists", this._addresses.artists.local.clone()); + this._artistsLink.label.addProperty("fontSize", "largeFontSize"); + this._artistsLink.label.addProperty("fontFamily", "largeFont"); + this._artistsLink.label.addProperty("color", "primaryFontColor"); + this._artistsLink.addProperty("backgroundColor", "primaryColor"); + + this._albums = new List( + this._addresses.albums.local.clone(), + "Albums", + this._addresses.albums.remote.clone(), + socket, + Album + ); + this._albumsLink = new Link(this._address["+"](new Address(["albumsLink"])), "Albums", this._addresses.albums.local.clone()); + this._albumsLink.label.addProperty("fontSize", "largeFontSize"); + this._albumsLink.label.addProperty("fontFamily", "largeFont"); + this._albumsLink.label.addProperty("color", "primaryFontColor"); + this._albumsLink.addProperty("backgroundColor", "primaryColor"); + + this._songs = new List( + this._addresses.songs.local.clone(), + "Songs", + this._addresses.songs.remote.clone(), + socket, + Song + ); + this._songsLink = new Link(this._address["+"](new Address(["songsLink"])), "Songs", this._addresses.songs.local.clone()); + this._songsLink.label.addProperty("fontSize", "largeFontSize"); + this._songsLink.label.addProperty("fontFamily", "largeFont"); + this._songsLink.label.addProperty("color", "primaryFontColor"); + this._songsLink.addProperty("backgroundColor", "primaryColor"); + + this.addItem(this._artistsLink, 1, 0, 1, 1); + this.addItem(this._albumsLink, 2, 0, 1, 1); + this.addItem(this._songsLink, 3, 0, 1, 1); + + this.addPage(this._artists); + this.addPage(this._albums); + this.addPage(this._songs); + }, + "_destroyLists": function() { + this.removePage(this._artists); + this.removePage(this._albums); + this.removePage(this._songs); + + this.removeItem(this._artistsLink); + this.removeItem(this._albumsLink); + this.removeItem(this._songsLink); + + this._artists.destructor(); + this._albums.destructor(); + this._songs.destructor(); + + this._artistsLink.destructor(); + this._albumsLink.destructor(); + this._songsLink.destructor(); + }, + "showError": function() { + if (this._dbConnected) { + if (!this._hasParentReporter) { + throw new Error("Parent reporter is required in music page"); + } + this._destroyLists() + this.addItem(this._errMessage, 1, 0, 1, 1, Page.Aligment.CenterTop); + this._dbConnected = false; + } + }, + "showBandList": function(perturaboSocket) { + if (!this._hasParentReporter) { + throw new Error("Parent reporter is required in music page"); + } + if (!this._dbConnected) { + this.removeItem(this._errMessage); + this._createLists(perturaboSocket); + this._dbConnected = true; + } + } +}); + +module.exports = MusicPage; diff --git a/magnus/pages/song.js b/magnus/pages/song.js new file mode 100644 index 0000000..122b9a4 --- /dev/null +++ b/magnus/pages/song.js @@ -0,0 +1,151 @@ +"use strict"; + +var TempPage = require("./tempPage"); +var String = require("../lib/wModel/string"); +var Image = require("../lib/wModel/image"); + +var VCController = require("../lib/wController/vocabulary"); + +var Address = require("../lib/wType/address"); +var Uint64 = require("../lib/wType/uint64"); + +var SongPage = TempPage.inherit({ + "className": "SongPage", + "constructor": function(address, name, id, proxySocket) { + TempPage.fn.constructor.call(this, address, name); + + this._remoteId = id; + this._proxySocket = proxySocket; + this._ctrls = Object.create(null); + + var header = this._header = new String(this._address["+"](new Address(["header"])), ""); + header.addProperty("fontFamily", "casualFont"); + this.addItem(header, 0, 1, 1, 1, TempPage.Aligment.CenterTop); + + var album = this._album = new String(this._address["+"](new Address(["album"])), "Album name"); + album.addProperty("fontFamily", "casualFont"); + this.addItem(album, 1, 1, 1, 1, TempPage.Aligment.CenterTop); + + var artist = this._artist = new String(this._address["+"](new Address(["artist"])), "Artist name"); + artist.addProperty("fontFamily", "casualFont"); + this.addItem(artist, 2, 1, 1, 1, TempPage.Aligment.CenterTop); + + var image = this._image = new Image(this._address["+"](new Address(["image"])), new Uint64(0)); + this.addItem(image, 0, 0, 3, 1, TempPage.Aligment.CenterCenter); + }, + "destructor": function() { + this._clearCtrls(); + + TempPage.fn.destructor.call(this); + }, + "_clearCtrls": function() { + for (var key in this._ctrls) { + this._ctrls[key].destructor(); + } + + this._ctrls = Object.create(null); + }, + "_onAlbumNewElement": function(key, el) { + switch(key) { + case "name": + this._album.set(el); + break; + case "image": + this._image.set(el.clone()); + break; + + } + }, + "_onAlbumRemoveElement": function(key) { + switch(key) { + case "name": + this._album.set("unknown"); + case "image": + this._image.set(new Uint64(0)); + break; + } + }, + "_onArtistNewElement": function(key, el) { + switch(key) { + case "name": + this._artist.set(el); + break; + } + }, + "_onArtistRemoveElement": function(key) { + switch(key) { + case "name": + this._artist.set("unknown"); + } + }, + "_onSongNewElement": function(key, el) { + switch(key) { + case "name": + this._header.set(el); + break; + case "album": + if (this._ctrls.album) { + this.trigger("serviceMessage", "an album controller reinitializes in song page, not suppose to happen!", 1); + this._ctrls.album.destructor(); + } + var aVC = new VCController(new Address(["albums", el.toString()])); + aVC.on("newElement", this._onAlbumNewElement, this); + aVC.on("removeElement", this._onAlbumRemoveElement, this); + this._ctrls.album = aVC; + aVC.register(this._dp, this._proxySocket); + aVC.subscribe(); + break; + case "artist": + if (this._ctrls.artist) { + this.trigger("serviceMessage", "an artist controller reinitializes in song page, not suppose to happen!", 1); + this._ctrls.artist.destructor(); + } + var arVC = new VCController(new Address(["artists", el.toString()])); + arVC.on("newElement", this._onArtistNewElement, this); + arVC.on("removeElement", this._onArtistRemoveElement, this); + this._ctrls.artist = arVC; + arVC.register(this._dp, this._proxySocket); + arVC.subscribe(); + break; + } + }, + "_onSongRemoveElement": function(key) { + switch(key) { + case "name": + this._header.set(""); + break; + case "album": + this._album.set(""); + this._ctrls.album.destructor(); + delete this._ctrls.album; + case "artist": + this._artist.set(""); + this._ctrls.artist.destructor(); + delete this._ctrls.artist; + } + }, + "register": function(dp, server) { + TempPage.fn.register.call(this, dp, server); + + for (var key in this._ctrls) { + this._ctrls[key].register(dp, this._proxySocket); + } + }, + "setParentReporter": function(pr) { + TempPage.fn.setParentReporter.call(this, pr); + + var songVC = new VCController(new Address(["songs", this._remoteId.toString()])); + songVC.on("newElement", this._onSongNewElement, this); + songVC.on("removeElement", this._onSongRemoveElement, this); + this._ctrls.song = songVC; + songVC.register(this._dp, this._proxySocket); + songVC.subscribe(); + }, + "unsetParentReporter": function() { + this._clearCtrls(); + + TempPage.fn.unsetParentReporter.call(this); + } +}); + +module.exports = SongPage; diff --git a/magnus/pages/tempPage.js b/magnus/pages/tempPage.js new file mode 100644 index 0000000..ab304af --- /dev/null +++ b/magnus/pages/tempPage.js @@ -0,0 +1,46 @@ +"use strict"; + +var Page = require("../lib/wModel/page"); + +var config = require("../config/config.json"); + +var TempPage = Page.inherit({ + "className": "TempPage", + "constructor": function(address, name) { + Page.fn.constructor.call(this, address, name); + + this._destructionTimeout = undefined; + }, + "destructor": function() { + if (this._destructionTimeout) { + clearTimeout(this._destructionTimeout); + } + Page.fn.destructor.call(this); + }, + "checkSubscribersAndDestroy": function() { + if (this._subscribersCount === 0 && this._destructionTimeout === undefined) { + this.trigger("serviceMessage", this._address.toString() + " has no more subscribers, destroying page"); + this._destructionTimeout = setTimeout(this.trigger.bind(this, "destroyMe"), config.modelDestructionTimeout); + } + }, + "_h_subscribe": function(ev) { + Page.fn._h_subscribe.call(this, ev); + + if (this._destructionTimeout !== undefined) { + clearTimeout(this._destructionTimeout); + this._destructionTimeout = undefined; + } + }, + "_h_unsubscribe": function(ev) { + Page.fn._h_unsubscribe.call(this, ev); + + this.checkSubscribersAndDestroy(); + }, + "_onSocketDisconnected": function(ev, socket) { + Page.fn._onSocketDisconnected.call(this, ev, socket); + + this.checkSubscribersAndDestroy(); + } +}); + +module.exports = TempPage; diff --git a/magnus/pages/test.js b/magnus/pages/test.js new file mode 100644 index 0000000..584493e --- /dev/null +++ b/magnus/pages/test.js @@ -0,0 +1,19 @@ +"use strict"; + +var Page = require("../lib/wModel/page"); +var String = require("../lib/wModel/string"); + +var Address = require("../lib/wType/address"); + +var TestPage = Page.inherit({ + "className": "TestPage", + "constructor": function(address, name) { + Page.fn.constructor.call(this, address, name); + + var header = new String(this._address["+"](new Address(["message"])), "This is a test page"); + header.addProperty("fontFamily", "casualFont"); + this.addItem(header, 0, 0, 1, 1, Page.Aligment.CenterTop); + } +}); + +module.exports = TestPage; diff --git a/magnus/test/CMakeLists.txt b/magnus/test/CMakeLists.txt new file mode 100644 index 0000000..d55da71 --- /dev/null +++ b/magnus/test/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(test.js test.js) \ No newline at end of file diff --git a/magnus/test/test.js b/magnus/test/test.js new file mode 100644 index 0000000..6f00f9a --- /dev/null +++ b/magnus/test/test.js @@ -0,0 +1,64 @@ +"use strict"; +var Class = require("../lib/utils/class"); +var TUint64 = require("../lib/wTest/uint64"); +var TAbstractMap = require("../lib/wTest/abstractmap"); +var TAbstractList = require("../lib/wTest/abstractlist"); +var TAbstractOrder = require("../lib/wTest/abstractorder"); +var log = require("../lib/log")(module); + +var Test = Class.inherit({ + "className": "Test", + "constructor": function() { + Class.fn.constructor.call(this); + + this._s = 0; + this._t = 0; + this._tests = []; + + this.addTest(new TUint64()); + this.addTest(new TAbstractList()); + this.addTest(new TAbstractMap()); + this.addTest(new TAbstractOrder()); + }, + "destructor": function() { + for (var i = 0; i < this._tests.length; ++i) { + this._tests[i].destructor(); + } + + Class.fn.destructor.call(this); + }, + "addTest": function(test) { + test.on("start", this._onStart, this); + test.on("end", this._onEnd, this); + test.on("progress", this._onProgress, this); + test.on("fail", this._onFail, this); + + this._tests.push(test); + }, + "run": function() { + log.info("Starting tests"); + for (var i = 0; i < this._tests.length; ++i) { + this._tests[i].run(); + } + log.info("Testing complete. " + this._s + "/" + this._t); + }, + "_onStart": function(name) { + log.info("Testing " + name); + }, + "_onEnd": function(name, s, t) { + log.info("Finished " + name + ". " + s + "/" + t); + + this._s += s; + this._t += t; + }, + "_onProgress": function(name, current, total) { + + }, + "_onFail": function(name, current, error) { + log.warn("Test failed! Action " + current + "."); + log.warn("Error message:" + error.message); + log.warn("Error stack: \n" + error.stack); + } +}); + +module.exports = Test; diff --git a/magnus/views/CMakeLists.txt b/magnus/views/CMakeLists.txt new file mode 100644 index 0000000..13f92b1 --- /dev/null +++ b/magnus/views/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8.12) + +configure_file(index.jade index.jade) \ No newline at end of file diff --git a/magnus/views/index.jade b/magnus/views/index.jade new file mode 100644 index 0000000..b0d94ab --- /dev/null +++ b/magnus/views/index.jade @@ -0,0 +1,15 @@ +// + Created by betrayer on 27.11.15. + +doctype html +html(lang='en') + head + meta(charset='utf-8') + title RadioW + link(href='/css/main.css', rel='stylesheet') + script(data-main='/main' src='/lib/requirejs/require.js') + + body + #serverMessage + = info + p I am diff --git a/perturabo/CMakeLists.txt b/perturabo/CMakeLists.txt new file mode 100644 index 0000000..82b4d97 --- /dev/null +++ b/perturabo/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 2.8.12) +project(perturabo) + +find_package(Qt5Core REQUIRED) +find_package(Qt5Network REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + perturabo.h +) + +set(SOURCES + main.cpp + perturabo.cpp +) + +add_executable(perturabo ${HEADERS} ${SOURCES}) + +target_link_libraries(perturabo Qt5::Core) +target_link_libraries(perturabo Qt5::Network) + +target_link_libraries(perturabo wSocket) +target_link_libraries(perturabo wDispatcher) +target_link_libraries(perturabo utils) +target_link_libraries(perturabo wModel) +target_link_libraries(perturabo wServerUtils) +target_link_libraries(perturabo wDatabase) + +install(TARGETS perturabo RUNTIME DESTINATION bin) diff --git a/perturabo/main.cpp b/perturabo/main.cpp new file mode 100644 index 0000000..5708a6d --- /dev/null +++ b/perturabo/main.cpp @@ -0,0 +1,18 @@ +#include +#include + +#include + +#include "perturabo.h" + +int main(int argc, char **argv) { + QCoreApplication app(argc, argv); + W::SignalCatcher sc(&app); + + Perturabo* perturabo = new Perturabo(&app); + + QTimer::singleShot(0, perturabo, SLOT(start())); + QObject::connect(&app, SIGNAL(aboutToQuit()), perturabo, SLOT(stop())); + + return app.exec(); +} diff --git a/perturabo/perturabo.cpp b/perturabo/perturabo.cpp new file mode 100644 index 0000000..4d5917d --- /dev/null +++ b/perturabo/perturabo.cpp @@ -0,0 +1,189 @@ +#include "perturabo.h" + +#include + +using std::cout; +using std::endl; + +Perturabo* Perturabo::perturabo = 0; + +Perturabo::Perturabo(QObject *parent): + QObject(parent), + server(new W::Server(W::String(u"Perturabo"), this)), + logger(new W::Logger()), + parentReporter(new W::ParentReporter()), + attributes(new M::Attributes(W::Address({u"attributes"}))), + commands(new U::Commands(W::Address{u"management"})), + connector(0), + databases(), + dispatcher(new W::Dispatcher()) +{ + if (perturabo != 0) + { + throw SingletonError(); + } + Perturabo::perturabo = this; + + connector = new U::Connector(dispatcher, server, commands); + connector->addIgnoredNode(W::String(u"Lorgar")); + connector->addIgnoredNode(W::String(u"Roboute")); + + connect(attributes, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + connect(commands, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + connect(connector, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + connect(server, SIGNAL(connectionCountChange(uint64_t)), SLOT(onConnectionCountChanged(uint64_t))); + + dispatcher->registerDefaultHandler(parentReporter); + dispatcher->registerDefaultHandler(logger); + + attributes->addAttribute(W::String(u"connectionsCount"), new M::String(W::String(u"0"), W::Address({u"attributes", u"connectionCount"}))); + attributes->addAttribute(W::String(u"name"), new M::String(W::String(u"Perturabo"), W::Address({u"attributes", u"name"}))); + attributes->addAttribute(W::String(u"version"), new M::String(W::String(u"0.0.3"), W::Address({u"attributes", u"version"}))); + + createDatabases(); + + W::Handler* clearDatabase = W::Handler::create(W::Address({u"management", u"clearDatabase"}), this, &Perturabo::_h_clearDatabase); + W::Vocabulary clearArgs; + clearArgs.insert(u"name", W::Uint64(W::Object::string)); + commands->addCommand(W::String(u"clearDatabase"), clearDatabase, clearArgs); +} + +Perturabo::~Perturabo() +{ + std::map::iterator beg = databases.begin(); + std::map::iterator end = databases.end(); + + for (; beg != end; ++beg) { + delete beg->second; + } + + delete connector; + + dispatcher->unregisterDefaultHandler(logger); + dispatcher->unregisterDefaultHandler(parentReporter); + + delete commands; + delete attributes; + + delete parentReporter; + delete logger; + delete dispatcher; + + Perturabo::perturabo = 0; +} + +void Perturabo::onConnectionCountChanged(uint64_t count) +{ + attributes->setAttribute(W::String(u"connectionsCount"), new W::String(std::to_string(count))); +} + +void Perturabo::start() +{ + std::map::iterator beg = databases.begin(); + std::map::iterator end = databases.end(); + + cout << "Starting perturabo..." << endl; + server->listen(8082); + + cout << "Registering models..." << endl; + attributes->registerModel(dispatcher, server); + commands->registerModel(dispatcher, server); + + for (; beg != end; ++beg) { + beg->second->registerModel(dispatcher, server); + } + + cout << "Opening and indexing databases..." << endl; + + beg = databases.begin(); + for (; beg != end; ++beg) { + beg->second->open(); + } + + commands->enableCommand(W::String(u"clearDatabase"), true); + + cout << "Perturabo is ready" << endl; +} + +void Perturabo::stop() +{ + std::map::iterator beg = databases.begin(); + std::map::iterator end = databases.end(); + + cout << "Stopping perturabo..." << endl; + + commands->enableCommand(W::String(u"clearDatabase"), false); + + for (; beg != end; ++beg) { + beg->second->unregisterModel(); + } + + commands->unregisterModel(); + attributes->unregisterModel(); + server->stop(); +} + +void Perturabo::onModelServiceMessage(const QString& msg) +{ + cout << msg.toStdString() << endl; +} + +void Perturabo::h_clearDatabase(const W::Event& ev) +{ + const W::Vocabulary& vc = static_cast(ev.getData()); + const W::String& name = static_cast(vc.at(u"name")); + + cout << "received command to clear database " << name.toString() << endl; + + std::map::iterator itr = databases.find(name); + if (itr == databases.end()) { + cout << "database " << name.toString() << " doesn't exist" << endl; + } else { + itr->second->clear(); + } +} + + +void Perturabo::addDatabase(Database* db) +{ + connect(db, SIGNAL(serviceMessage(const QString&)), SLOT(onModelServiceMessage(const QString&))); + parentReporter->registerParent(db->getAddress(), db->subscribeMember); + + databases.insert(std::make_pair(db->name, db)); +} + +void Perturabo::createDatabases() +{ + Database* artists = new Database(W::String(u"artists")); + Database* albums = new Database(W::String(u"albums")); + Database* songs = new Database(W::String(u"songs")); + + artists->addIndex(W::String(u"name"), W::Object::string); + + albums->addIndex(W::String(u"name"), W::Object::string); + albums->addIndex(W::String(u"artist"), W::Object::uint64); + + songs->addIndex(W::String(u"name"), W::Object::string); + songs->addIndex(W::String(u"artist"), W::Object::uint64); + songs->addIndex(W::String(u"album"), W::Object::uint64); + + attributes->addAttribute(W::String(artists->name), new M::String(W::String(u"0"), W::Address({u"attributes", artists->name}))); + attributes->addAttribute(W::String(albums->name), new M::String(W::String(u"0"), W::Address({u"attributes", albums->name}))); + attributes->addAttribute(W::String(songs->name), new M::String(W::String(u"0"), W::Address({u"attributes", songs->name}))); + + connect(artists, SIGNAL(countChange(uint64_t)), SLOT(onDatabaseCountChange(uint64_t))); + connect(albums, SIGNAL(countChange(uint64_t)), SLOT(onDatabaseCountChange(uint64_t))); + connect(songs, SIGNAL(countChange(uint64_t)), SLOT(onDatabaseCountChange(uint64_t))); + + addDatabase(artists); + addDatabase(albums); + addDatabase(songs); +} + +void Perturabo::onDatabaseCountChange(uint64_t count) +{ + Database* db = static_cast(sender()); + + attributes->setAttribute(db->name, W::String(std::to_string(count))); +} + diff --git a/perturabo/perturabo.h b/perturabo/perturabo.h new file mode 100644 index 0000000..2fc4281 --- /dev/null +++ b/perturabo/perturabo.h @@ -0,0 +1,84 @@ +#ifndef PERTURABO_H +#define PERTURABO_H + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +class Perturabo: public QObject +{ + Q_OBJECT + +public: + Perturabo(QObject *parent = 0); + ~Perturabo(); + + static Perturabo* perturabo; + +private: + W::Server *server; + W::Logger *logger; + W::ParentReporter* parentReporter; + + M::Attributes* attributes; + U::Commands* commands; + U::Connector* connector; + + std::map databases; + + handler(clearDatabase); +// handler(parseDirectory); + +public: + W::Dispatcher *dispatcher; + +public slots: + void start(); + void stop(); + +private slots: + void onModelServiceMessage(const QString& msg); + void onConnectionCountChanged(uint64_t count); + void onDatabaseCountChange(uint64_t count); + +private: + void createDatabases(); + void addDatabase(Database* db); + +private: + class SingletonError: + public Utils::Exception + { + public: + SingletonError():Exception(){} + + std::string getMessage() const{return "Perturabo is a singleton, there was an attempt to construct it at the second time";} + }; +}; + +#endif // PERTURABO_H diff --git a/roboute/CMakeLists.txt b/roboute/CMakeLists.txt new file mode 100644 index 0000000..504edbd --- /dev/null +++ b/roboute/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8.12) +project(roboute) + +find_package(Qt5Core REQUIRED) +find_package(Qt5Widgets REQUIRED) +find_package(Qt5Network REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + roboute.h + mainwindow.h +) + +set(SOURCES + main.cpp + roboute.cpp + mainwindow.cpp +) + +add_executable(roboute ${HEADERS} ${SOURCES}) + +add_subdirectory(views) +add_subdirectory(models) + +target_link_libraries(roboute Qt5::Core) +target_link_libraries(roboute Qt5::Widgets) +target_link_libraries(roboute Qt5::Network) + +target_link_libraries(roboute wSocket) +target_link_libraries(roboute wDispatcher) +target_link_libraries(roboute utils) +target_link_libraries(roboute robouteViews) +target_link_libraries(roboute robouteModels) + +install(TARGETS roboute RUNTIME DESTINATION bin) diff --git a/roboute/applistitemdelegate.cpp b/roboute/applistitemdelegate.cpp new file mode 100644 index 0000000..5b7e801 --- /dev/null +++ b/roboute/applistitemdelegate.cpp @@ -0,0 +1,80 @@ +#include "applistitemdelegate.h" +#include + +#define QFIXED_MAX (INT_MAX/256) + +AppListItemDelegate::AppListItemDelegate(QObject* parent) : + QItemDelegate(parent) +{ +} + + +void AppListItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + //Q_ASSERT(index.isValid()); + + QStyleOptionViewItem opt = setOptions(index, option); + + // prepare + painter->save(); +// if (d->clipPainting) +// painter->setClipRect(opt.rect); + + // get the data and the rectangles + + QVariant value; + + QPixmap pixmap; + QRect decorationRect; +// value = index.data(Qt::DecorationRole); +// if (value.isValid()) { +// // ### we need the pixmap to call the virtual function +// pixmap = decoration(opt, value); +// if (value.type() == QVariant::Icon) { +// d->tmp.icon = qvariant_cast(value); +// d->tmp.mode = d->iconMode(option.state); +// d->tmp.state = d->iconState(option.state); +// const QSize size = d->tmp.icon.actualSize(option.decorationSize, +// d->tmp.mode, d->tmp.state); +// decorationRect = QRect(QPoint(0, 0), size); +// } else { +// d->tmp.icon = QIcon(); +// decorationRect = QRect(QPoint(0, 0), pixmap.size()); +// } +// } else { +// d->tmp.icon = QIcon(); +// decorationRect = QRect(); +// } + + QString text; + QRect displayRect; + value = index.data(Qt::DisplayRole); + std::cout << "ha" << std::endl; + if (value.isValid() && !value.isNull()) { + text = value.toMap().value("name").toString(); + displayRect = opt.rect; + displayRect.setWidth(QFIXED_MAX); + } + QRect checkRect; + Qt::CheckState checkState = Qt::Unchecked; +// value = index.data(Qt::CheckStateRole); +// if (value.isValid()) { +// checkState = static_cast(value.toInt()); +// checkRect = check(opt, opt.rect, value); +// } + + // do the layout + + doLayout(opt, &checkRect, &decorationRect, &displayRect, false); + + // draw the item + + drawBackground(painter, opt, index); + drawCheck(painter, opt, checkRect, checkState); + drawDecoration(painter, opt, decorationRect, pixmap); + drawDisplay(painter, opt, displayRect, text); + drawFocus(painter, opt, displayRect); + + // done + painter->restore(); +} diff --git a/roboute/applistitemdelegate.h b/roboute/applistitemdelegate.h new file mode 100644 index 0000000..674daae --- /dev/null +++ b/roboute/applistitemdelegate.h @@ -0,0 +1,17 @@ +#ifndef APPLISTITEMDELEGATE_H +#define APPLISTITEMDELEGATE_H + +#include +#include + +class AppListItemDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + AppListItemDelegate(QObject* parent = 0); + + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const; +}; + +#endif // APPLISTITEMDELEGATE_H diff --git a/roboute/main.cpp b/roboute/main.cpp new file mode 100644 index 0000000..288c9d4 --- /dev/null +++ b/roboute/main.cpp @@ -0,0 +1,64 @@ +#include +#include + +#include + +#include "roboute.h" +#include "mainwindow.h" + +int main(int argc, char **argv) { + QApplication app(argc, argv); + QCoreApplication::setOrganizationName("RadioW"); + QCoreApplication::setApplicationName("Roboute"); + QCoreApplication::setApplicationVersion("0.0.1"); + + W::SignalCatcher sc(&app); + + Roboute* roboute = new Roboute(&app); + MainWindow* wnd = new MainWindow();; + + QObject::connect(roboute, SIGNAL(debugMessage(const QString&)), wnd, SLOT(robouteMessage(const QString&))); + QObject::connect(roboute, SIGNAL(newService(uint64_t, const QString&)), wnd, SLOT(newService(uint64_t, const QString&))); + QObject::connect(roboute, SIGNAL(serviceConnecting(uint64_t)), wnd, SLOT(serviceConnecting(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceConnected(uint64_t)), wnd, SLOT(serviceConnected(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceDisconnecting(uint64_t)), wnd, SLOT(serviceDisconnecting(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceDisconnected(uint64_t)), wnd, SLOT(serviceDisconnected(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceConnectionFailed(uint64_t)), wnd, SLOT(serviceConnectionFailed(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceLaunched(uint64_t)), wnd, SLOT(serviceLaunched(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceStopped(uint64_t)), wnd, SLOT(serviceStopped(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceLaunching(uint64_t)), wnd, SLOT(serviceLaunching(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceStopping(uint64_t)), wnd, SLOT(serviceStopping(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceRemoved(uint64_t)), wnd, SLOT(serviceRemoved(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceAttrChange(uint64_t, const QString&, const QString&)), + wnd, SLOT(serviceAttrChange(uint64_t, const QString&, const QString&))); + QObject::connect(roboute, SIGNAL(log(uint64_t, const QString&)), wnd, SLOT(serviceLog(uint64_t, const QString&))); + QObject::connect(roboute, SIGNAL(serviceAddCommand(uint64_t, const QString&, const QMap)), + wnd, SLOT(serviceAddCommand(uint64_t, const QString&, const QMap))); + QObject::connect(roboute, SIGNAL(serviceRemoveCommand(uint64_t, const QString&)), + wnd, SLOT(serviceRemoveCommand(uint64_t, const QString&))); + QObject::connect(roboute, SIGNAL(serviceClearCommands(uint64_t)), wnd, SLOT(serviceClearCommands(uint64_t))); + QObject::connect(roboute, SIGNAL(serviceChangeName(uint64_t, const QString&)), wnd, SLOT(serviceNameChange(uint64_t, const QString&))); + QObject::connect(roboute, SIGNAL(serviceEdit(uint64_t, const QMap&)), wnd, SLOT(serviceEdit(uint64_t, const QMap&))); + + QObject::connect(wnd, SIGNAL(addService(const QMap&)), roboute, SLOT(addService(const QMap&))); + QObject::connect(wnd, SIGNAL(connectService(uint64_t)), roboute, SLOT(connectService(uint64_t))); + QObject::connect(wnd, SIGNAL(disconnectService(uint64_t)), roboute, SLOT(disconnectService(uint64_t))); + QObject::connect(wnd, SIGNAL(launchService(uint64_t)), roboute, SLOT(launchService(uint64_t))); + QObject::connect(wnd, SIGNAL(stopService(uint64_t)), roboute, SLOT(stopService(uint64_t))); + QObject::connect(wnd, SIGNAL(removeService(uint64_t)), roboute, SLOT(removeService(uint64_t))); + QObject::connect(wnd, SIGNAL(launchCommand(uint64_t, const QString&, const QMap&)), + roboute, SLOT(launchCommand(uint64_t, const QString&, const QMap&))); + QObject::connect(wnd, SIGNAL(editService(uint64_t)), roboute, SLOT(editService(uint64_t))); + QObject::connect(wnd, SIGNAL(changeService(uint64_t, const QMap&)), roboute, SLOT(changeService(uint64_t, const QMap&))); + + + QTimer::singleShot(0, roboute, SLOT(start())); + QObject::connect(&app, SIGNAL(aboutToQuit()), roboute, SLOT(stop())); + QObject::connect(&app, SIGNAL(aboutToQuit()), wnd, SLOT(saveSettings())); + + wnd->show(); + int result = app.exec(); + delete wnd; + + return result; +} diff --git a/roboute/mainwindow.cpp b/roboute/mainwindow.cpp new file mode 100644 index 0000000..9d4c81b --- /dev/null +++ b/roboute/mainwindow.cpp @@ -0,0 +1,328 @@ +#include + +#include "mainwindow.h" +#include + + +MainWindow::MainWindow(): + QMainWindow(), + apps(new AppListModel(this)), + widget(new MainView(apps, this)), + newApp(0), + commandForm(0), + rightBar(new QToolBar(this)), + editingService(0) +{ + createActions(); + createToolbar(); + setCentralWidget(widget); + + apps->push_back(0, "Roboute"); + apps->setLaunched(0, true); + apps->setConnected(0, true); + apps->setEditable(0, false); + + QItemSelectionModel* as = widget->list->selectionModel(); + + connect( + as, SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(selectionChanged(const QItemSelection&, const QItemSelection&)) + ); + connect(widget->details, SIGNAL(connect(uint64_t)), this, SIGNAL(connectService(uint64_t))); + connect(widget->details, SIGNAL(disconnect(uint64_t)), this, SIGNAL(disconnectService(uint64_t))); + connect(widget->details, SIGNAL(launch(uint64_t)), this, SIGNAL(launchService(uint64_t))); + connect(widget->details, SIGNAL(stop(uint64_t)), this, SIGNAL(stopService(uint64_t))); + connect(widget->details, SIGNAL(remove(uint64_t)), this, SIGNAL(removeService(uint64_t))); + connect(widget->details, SIGNAL(edit(uint64_t)), this, SIGNAL(editService(uint64_t))); + connect(widget->details, SIGNAL(clearLog(uint64_t)), this, SLOT(clearServiceLog(uint64_t))); + connect(widget->details, SIGNAL(launchCommand(uint64_t, const QString&)), this, SLOT(onLaunchedCommand(uint64_t, const QString&))); + + restoreSettings(); +} + +void MainWindow::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + QModelIndexList deselectedIndexes = deselected.indexes(); + QModelIndexList::const_iterator dItr = deselectedIndexes.begin(); + QModelIndexList::const_iterator dEnd = deselectedIndexes.end(); + for (; dItr != dEnd; ++dItr) { + unsubscribeDetailsById(apps->data(*dItr, Qt::UserRole).toInt()); + } + + QModelIndexList selectedIndexes = selected.indexes(); + QModelIndexList::const_iterator sItr = selectedIndexes.begin(); + QModelIndexList::const_iterator sEnd = selectedIndexes.end(); + for (; sItr != sEnd; ++sItr) { + subscribeDetailsById(apps->data(*sItr, Qt::UserRole).toInt()); + } + + if (deselectedIndexes.size() == 1 && selectedIndexes.size() == 0) { + widget->hideDetails(); + rightBar->hide(); + } else if (deselectedIndexes.size() == 0 && selectedIndexes.size() == 1) { + widget->showDetails(); + rightBar->show(); + } +} + +void MainWindow::subscribeDetailsById(quint64 id) +{ + widget->details->setModel(apps->getApp(id)); +} + +void MainWindow::unsubscribeDetailsById(quint64 id) +{ + widget->details->clearModel(); +} + +void MainWindow::robouteMessage(const QString& msg) +{ + apps->logMessage(0, msg); +} + +void MainWindow::unselectAll() +{ + widget->list->selectionModel()->clearSelection(); +} + +void MainWindow::createActions() +{ + QMenu *actionsMenu = menuBar()->addMenu(tr("Actions")); + + const QIcon newIcon = QIcon::fromTheme("document-new"); + QAction *newAct = new QAction(newIcon, tr("New application"), this); + newAct->setShortcuts(QKeySequence::New); + newAct->setStatusTip(tr("Add new application")); + connect(newAct, &QAction::triggered, this, &MainWindow::newApplication); + actionsMenu->addAction(newAct); +} + +void MainWindow::createToolbar() +{ + addToolBar(Qt::RightToolBarArea, rightBar); + rightBar->setMovable(false); + rightBar->setObjectName("rightBar"); + rightBar->hide(); + + QAction* attrs = rightBar->addAction(QIcon::fromTheme("dialog-object-properties"), tr("Attributes")); + QAction* commands = rightBar->addAction(QIcon::fromTheme("dialog-scripts"), tr("Commands")); + + attrs->setCheckable(true); + commands->setCheckable(true); + + QActionGroup* ag = new QActionGroup(rightBar); + ag->setExclusive(true); + ag->addAction(attrs); + ag->addAction(commands); + + connect(attrs, SIGNAL(toggled(bool)), SLOT(attrsToggled(bool))); + connect(commands, SIGNAL(toggled(bool)), SLOT(commandsToggled(bool))); + +} + + +void MainWindow::newApplication() +{ + newApp = new NewAppDialogue(this); + connect(newApp, SIGNAL(accepted()), SLOT(newAppAccepted())); + connect(newApp, SIGNAL(rejected()), SLOT(newAppRejected())); + newApp->setModal(true); + newApp->setWindowTitle(tr("New application")); + newApp->show(); +} + +void MainWindow::newAppAccepted() +{ + if (editingService == 0) { + emit addService(newApp->getData()); + } else { + emit changeService(editingService, newApp->getData()); + editingService = 0; + } + delete newApp; + newApp = 0; +} + +void MainWindow::newAppRejected() +{ + editingService = 0; + delete newApp; + newApp = 0; +} + +void MainWindow::newService(uint64_t id, const QString& name) +{ + apps->push_back(id, name); + apps->setConnectable(id, true); + apps->setEditable(id, true); +} + +void MainWindow::serviceConnecting(uint64_t id) +{ + apps->setConnectable(id, false); + apps->setConnected(id, false); + apps->setEditable(id, false); +} + +void MainWindow::serviceConnected(uint64_t id) +{ + apps->setConnectable(id, true); + apps->setConnected(id, true); + apps->setEditable(id, false); +} + +void MainWindow::serviceDisconnecting(uint64_t id) +{ + apps->setConnectable(id, false); + apps->setConnected(id, true); + apps->setEditable(id, false); +} + +void MainWindow::serviceDisconnected(uint64_t id) +{ + apps->setConnectable(id, true); + apps->setConnected(id, false); + apps->setEditable(id, true); +} + +void MainWindow::serviceConnectionFailed(uint64_t id) +{ + apps->setConnected(id, false); + apps->setEditable(id, true); +} + +void MainWindow::serviceLaunched(uint64_t id) +{ + apps->setLaunched(id, true); + apps->setLaunchable(id, true); +} + +void MainWindow::serviceStopped(uint64_t id) +{ + apps->setLaunched(id, false); + apps->setLaunchable(id, true); +} + +void MainWindow::serviceLaunching(uint64_t id) +{ + apps->setLaunched(id, false); + apps->setLaunchable(id, false); +} + +void MainWindow::serviceStopping(uint64_t id) +{ + apps->setLaunched(id, true); + apps->setLaunchable(id, false); +} + +void MainWindow::serviceLog(uint64_t id, const QString& log) +{ + apps->logMessage(id, log); +} + +void MainWindow::serviceRemoved(uint64_t id) +{ + apps->removeElement(id); +} + +void MainWindow::restoreSettings() +{ + QSettings settings; + + restoreGeometry(settings.value("window/geometry").toByteArray()); + restoreState(settings.value("window/state").toByteArray()); + + widget->readSettings(); + + rightBar->hide(); +} + +void MainWindow::saveSettings() +{ + QSettings settings; + settings.beginGroup("window"); + + settings.setValue("geometry", saveGeometry()); + settings.setValue("state", saveState()); + + settings.endGroup(); + + widget->saveSettings(); +} + +void MainWindow::serviceAttrChange(uint64_t id, const QString& key, const QString& value) +{ + apps->setAttribute(id, key, value); +} + +void MainWindow::attrsToggled(bool checked) +{ + widget->details->showAttrs(checked); +} + +void MainWindow::commandsToggled(bool checked) +{ + widget->details->showCommands(checked); +} + +void MainWindow::serviceAddCommand(uint64_t id, const QString& key, const QMap& arguments) +{ + apps->addCommand(id, key, arguments); +} + +void MainWindow::serviceRemoveCommand(uint64_t id, const QString& key) +{ + apps->removeCommand(id, key); +} + +void MainWindow::serviceClearCommands(uint64_t id) +{ + apps->clearCommands(id); +} + +void MainWindow::onLaunchedCommand(uint64_t id, const QString& name) +{ + commandForm = new CommandForm(name, apps->getApp(id)->commands.getCommandArgs(name), this); + connect(commandForm, SIGNAL(accepted()), SLOT(commandFormAccepted())); + connect(commandForm, SIGNAL(rejected()), SLOT(commandFormRejected())); + commandForm->setModal(true); + commandForm->setWindowTitle(tr("Execute the command")); + commandForm->show(); +} + +void MainWindow::commandFormAccepted() +{ + emit launchCommand(widget->details->getModelId(), commandForm->getName(), commandForm->getData()); + delete commandForm; + commandForm = 0; +} + +void MainWindow::commandFormRejected() +{ + delete commandForm; + commandForm = 0; +} + +void MainWindow::clearServiceLog(uint64_t id) +{ + apps->clearLog(id); +} + +void MainWindow::serviceEdit(uint64_t id, const QMap& data) +{ + if (editingService == 0) { + editingService = id; + + newApp = new NewAppDialogue(data, this); + connect(newApp, SIGNAL(accepted()), SLOT(newAppAccepted())); + connect(newApp, SIGNAL(rejected()), SLOT(newAppRejected())); + newApp->setModal(true); + newApp->setWindowTitle(tr("Edit application")); + newApp->show(); + } +} + +void MainWindow::serviceNameChange(uint64_t id, const QString& name) +{ + apps->setName(id, name); +} diff --git a/roboute/mainwindow.h b/roboute/mainwindow.h new file mode 100644 index 0000000..c252a81 --- /dev/null +++ b/roboute/mainwindow.h @@ -0,0 +1,86 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +#include "views/mainview.h" +#include "views/newappdialogue.h" +#include "views/commandform.h" +#include "models/applistmodel.h" +#include "models/appmodel.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + +private: + AppListModel* apps; + MainView* widget; + NewAppDialogue* newApp; + CommandForm* commandForm; + QToolBar* rightBar; + uint64_t editingService; + +private: + void createActions(); + void createToolbar(); + + void subscribeDetailsById(quint64 id); + void unsubscribeDetailsById(quint64 id); + void restoreSettings(); + +signals: + void addService(const QMap&); + void connectService(uint64_t); + void disconnectService(uint64_t); + void launchService(uint64_t); + void stopService(uint64_t); + void removeService(uint64_t id); + void editService(uint64_t id); + void changeService(uint64_t id, const QMap&); + void launchCommand(uint64_t id, const QString& name, const QMap& args); + +public slots: + void saveSettings(); + void robouteMessage(const QString& msg); + void newService(uint64_t id, const QString& name); + void serviceAttrChange(uint64_t id, const QString& key, const QString& value); + void serviceConnected(uint64_t id); + void serviceConnecting(uint64_t id); + void serviceDisconnected(uint64_t id); + void serviceDisconnecting(uint64_t id); + void serviceConnectionFailed(uint64_t id); + void serviceLaunched(uint64_t id); + void serviceLaunching(uint64_t id); + void serviceStopped(uint64_t id); + void serviceStopping(uint64_t id); + void serviceLog(uint64_t id, const QString& log); + void serviceRemoved(uint64_t id); + void serviceAddCommand(uint64_t id, const QString& key, const QMap& arguments); + void serviceRemoveCommand(uint64_t id, const QString& key); + void serviceNameChange(uint64_t id, const QString& name); + void serviceClearCommands(uint64_t id); + void serviceEdit(uint64_t id, const QMap& data); + +private slots: + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void unselectAll(); + + void newApplication(); + void newAppAccepted(); + void newAppRejected(); + void commandFormAccepted(); + void commandFormRejected(); + + void attrsToggled(bool checked); + void commandsToggled(bool checked); + void onLaunchedCommand(uint64_t id, const QString& name); + void clearServiceLog(uint64_t id); +}; + +#endif // MAINWINDOW_H diff --git a/roboute/models/CMakeLists.txt b/roboute/models/CMakeLists.txt new file mode 100644 index 0000000..8939611 --- /dev/null +++ b/roboute/models/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 2.8.12) +project(robouteModels) + +find_package(Qt5Core REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + applistmodel.h + appmodel.h + service.h + apppropertiesmodel.h + appcommandsmodel.h +) + +set(SOURCES + applistmodel.cpp + appmodel.cpp + service.cpp + apppropertiesmodel.cpp + appcommandsmodel.cpp +) + +add_library(robouteModels STATIC ${HEADERS} ${SOURCES}) + +target_link_libraries(robouteModels Qt5::Core) +target_link_libraries(robouteModels wType) +target_link_libraries(robouteModels wSocket) +target_link_libraries(robouteModels wSsh) +target_link_libraries(robouteModels wController) diff --git a/roboute/models/appcommandsmodel.cpp b/roboute/models/appcommandsmodel.cpp new file mode 100644 index 0000000..fe95eac --- /dev/null +++ b/roboute/models/appcommandsmodel.cpp @@ -0,0 +1,110 @@ +#include "appcommandsmodel.h" + +AppCommandsModel::AppCommandsModel(QObject* parent): + QAbstractListModel(parent), + index(), + map(), + toInsert() +{ +} + +AppCommandsModel::~AppCommandsModel() +{ + clear(); +} + +void AppCommandsModel::inserCommand(const QString& name, const ArgsMap& args) +{ + toInsert.push_back(Pair(name, new ArgsMap(args))); + insertRows(rowCount(), 1); +} + +void AppCommandsModel::removeCommand(const QString& name) +{ + for (int i = 0; i < index.size(); ++i) { + if (index[i].key() == name) { + removeRows(i, 1); + break; + } + } +} + +int AppCommandsModel::rowCount(const QModelIndex& parent) const +{ + return index.size(); +} + +QVariant AppCommandsModel::data(const QModelIndex& i, int role) const +{ + Map::iterator itr = index[i.row()]; + + switch(role) { + case Qt::DisplayRole: + return itr.key(); + + case Qt::TextAlignmentRole: + return Qt::AlignCenter + Qt::AlignVCenter; + } + + return QVariant(); +} + +bool AppCommandsModel::insertRows(int row, int count, const QModelIndex& parent) +{ + if (toInsert.size() != count) { + return false; + } + beginInsertRows(parent, row, row + count - 1); + + Index::const_iterator target = index.begin() + row; + for (int i = 0; i < count; ++i) { + List::iterator itr = toInsert.begin(); + Map::iterator mItr = map.insert(itr->first, itr->second); + index.insert(target, mItr); + toInsert.erase(itr); + } + + endInsertRows(); + return true; +} + +bool AppCommandsModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (row + count > index.size()) { + return false; + } + beginRemoveRows(parent, row, row + count - 1); + Index::iterator itr; + Index::iterator beg = index.begin() + row; + Index::iterator end = beg + count; + for (itr = beg; itr != end; ++itr) { + Map::iterator mItr = *itr; + ArgsMap* app = *mItr; + delete app; + map.erase(mItr); + } + index.erase(beg, end); + + endRemoveRows(); + return true; +} + +AppCommandsModel::ArgsMap AppCommandsModel::getCommandArgs(const QString& name) +{ + return *(map[name]); +} + +void AppCommandsModel::clear() +{ + beginResetModel(); + Map::iterator itr = map.begin(); + Map::iterator end = map.end(); + + for (; itr != end; ++itr) { + delete itr.value(); + } + map.clear(); + index.clear(); + endResetModel(); +} + diff --git a/roboute/models/appcommandsmodel.h b/roboute/models/appcommandsmodel.h new file mode 100644 index 0000000..342e74e --- /dev/null +++ b/roboute/models/appcommandsmodel.h @@ -0,0 +1,40 @@ +#ifndef APPCOMMANDSMODEL_H +#define APPCOMMANDSMODEL_H + +#include +#include + +#include +#include + +class AppCommandsModel : public QAbstractListModel +{ + Q_OBJECT + + typedef QMap ArgsMap; +public: + AppCommandsModel(QObject* parent = 0); + ~AppCommandsModel(); + + void inserCommand(const QString& name, const ArgsMap& args); + void removeCommand(const QString& name); + ArgsMap getCommandArgs(const QString& name); + void clear(); + + QVariant data(const QModelIndex &i, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + +private: + typedef QMap Map; + typedef std::deque Index; + typedef std::pair Pair; + typedef std::list List; + + Index index; + Map map; + List toInsert; +}; + +#endif // APPCOMMANDSMODEL_H diff --git a/roboute/models/applistmodel.cpp b/roboute/models/applistmodel.cpp new file mode 100644 index 0000000..aff3094 --- /dev/null +++ b/roboute/models/applistmodel.cpp @@ -0,0 +1,187 @@ +#include "applistmodel.h" +#include + +AppListModel::AppListModel(QObject* parent): + QAbstractListModel(parent), + index(), + helper(), + map(), + toInsert() +{ + +} + +AppListModel::~AppListModel() +{ + clear(); +} + + +void AppListModel::push_back(uint64_t id, const QString& name) +{ + AppModel* item = new AppModel(id, name); + + toInsert.push_back(Pair(id, item)); + insertRows(rowCount(), 1); +} + +void AppListModel::removeElement(uint64_t id) +{ + int index = *(helper.find(id)); + removeRows(index, 1); +} + + +int AppListModel::rowCount(const QModelIndex& parent) const +{ + //std::cout << index.size() << std::endl; + return index.size(); +} + +QVariant AppListModel::data(const QModelIndex& i, int role) const +{ + Map::iterator itr = index[i.row()]; + + switch(role) { + case Qt::DisplayRole: + return itr.value()->getName(); + + case Qt::TextAlignmentRole: + return Qt::AlignCenter + Qt::AlignVCenter; + + case Qt::UserRole: + quint64 id = itr.key(); + return id; + } + + return QVariant(); +} + +bool AppListModel::insertRows(int row, int count, const QModelIndex& parent) +{ + if (toInsert.size() != count) { + return false; + } + beginInsertRows(parent, row, row + count - 1); + + Index::const_iterator target = index.begin() + row; + for (int i = 0; i < count; ++i) { + List::iterator itr = toInsert.begin(); + Map::iterator mItr = map.insert(itr->first, itr->second); + index.insert(target, mItr); + helper.insert(itr->first, row + i); + toInsert.erase(itr); + } + + endInsertRows(); + return true; +} + +bool AppListModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (row + count > index.size()) { + return false; + } + beginRemoveRows(parent, row, row + count - 1); + Index::iterator itr; + Index::iterator beg = index.begin() + row; + Index::iterator end = beg + count; + for (itr = beg; itr != end; ++itr) { + Map::iterator mItr = *itr; + AppModel* app = *mItr; + IndexHelper::iterator hItr = helper.find(app->id); + delete app; + map.erase(mItr); + helper.erase(hItr); + } + index.erase(beg, end); + + endRemoveRows(); + return true; +} + +void AppListModel::logMessage(uint64_t id, const QString& msg) +{ + map[id]->logMessage(msg); + +} + +AppModel* AppListModel::getApp(uint64_t id) +{ + return map[id]; +} + +void AppListModel::clear() +{ + beginResetModel(); + Map::iterator itr = map.begin(); + Map::iterator end = map.end(); + + for (; itr != end; ++itr) { + delete itr.value(); + } + map.clear(); + index.clear(); + helper.clear(); + endResetModel(); +} + +void AppListModel::setConnectable(uint64_t id, bool value) +{ + map[id]->setConnectable(value); +} + +void AppListModel::setConnected(uint64_t id, bool value) +{ + map[id]->setConnected(value); +} + +void AppListModel::setLaunchable(uint64_t id, bool value) +{ + map[id]->setLaunchable(value); +} + +void AppListModel::setLaunched(uint64_t id, bool value) +{ + map[id]->setLaunched(value); +} + +void AppListModel::setEditable(uint64_t id, bool value) +{ + map[id]->setEditable(value); +} + + +void AppListModel::setAttribute(uint64_t id, const QString& key, const QString& value) +{ + map[id]->props.setProp(key, value); +} + +void AppListModel::addCommand(uint64_t id, const QString& key, const QMap& arguments) +{ + map[id]->commands.inserCommand(key, arguments); +} + +void AppListModel::removeCommand(uint64_t id, const QString& key) +{ + map[id]->commands.removeCommand(key); +} + +void AppListModel::clearCommands(uint64_t id) +{ + map[id]->commands.clear(); +} + +void AppListModel::clearLog(uint64_t id) +{ + map[id]->clearLog(); +} + +void AppListModel::setName(uint64_t id, const QString& name) +{ + map[id]->setName(name); + int row = *(helper.find(id)); + + emit dataChanged(QAbstractListModel::index(row), QAbstractListModel::index(row), {Qt::DisplayRole}); +} + diff --git a/roboute/models/applistmodel.h b/roboute/models/applistmodel.h new file mode 100644 index 0000000..0149bfe --- /dev/null +++ b/roboute/models/applistmodel.h @@ -0,0 +1,58 @@ +#ifndef APPLISTMODEL_H +#define APPLISTMODEL_H + +#include +#include +#include + +#include +#include + +#include "appmodel.h" + +class AppListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + AppListModel(QObject* parent = 0); + ~AppListModel(); + + void push_back(uint64_t id, const QString& name); + void removeElement(uint64_t id); + AppModel* getApp(uint64_t id); + void clear(); + + QVariant data(const QModelIndex &i, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + void setConnectable(uint64_t id, bool value); + void setConnected(uint64_t id, bool value); + void setLaunchable(uint64_t id, bool value); + void setLaunched(uint64_t id, bool value); + void setEditable(uint64_t id, bool value); + void setName(uint64_t id, const QString& name); + void setAttribute(uint64_t id, const QString& key, const QString& value); + void addCommand(uint64_t id, const QString& key, const QMap& arguments); + void removeCommand(uint64_t id, const QString& key); + void clearCommands(uint64_t id); + void clearLog(uint64_t id); + +private: + typedef QMap Map; + typedef QMap IndexHelper; + typedef std::deque Index; + typedef std::pair Pair; + typedef std::list List; + + Index index; + IndexHelper helper; + Map map; + List toInsert; + +public slots: + void logMessage(uint64_t id, const QString& msg); +}; + +#endif // APPLISTMODEL_H diff --git a/roboute/models/appmodel.cpp b/roboute/models/appmodel.cpp new file mode 100644 index 0000000..07c8a63 --- /dev/null +++ b/roboute/models/appmodel.cpp @@ -0,0 +1,119 @@ +#include "appmodel.h" + +AppModel::AppModel(uint64_t p_id, const QString& p_name): + QObject(), + id(p_id), + props(), + commands(), + name(p_name), + log(), + connectable(false), + connected(false), + launchable(false), + launched(false), + editable(false) +{ +} + +const QString & AppModel::getName() const +{ + return name; +} + +void AppModel::logMessage(const QString& msg) +{ + log.push_back(msg); + emit newLogMessage(msg); +} + +QString* AppModel::getHistory() const +{ + List::const_iterator itr = log.begin(); + List::const_iterator end = log.end(); + + QString* history = new QString(); + + for (; itr != end; ++itr) { + history->append(*itr); + } + + return history; +} + +bool AppModel::getConnectable() const +{ + return connectable; +} + +bool AppModel::getConnected() const +{ + return connected; +} + +bool AppModel::getLaunchable() const +{ + return launchable && connected; +} + +bool AppModel::getLaunched() const +{ + return launched; +} + +bool AppModel::getEditable() const +{ + return editable && !connected; +} + +void AppModel::setConnectable(bool value) +{ + if (value != connectable) { + connectable = value; + emit changedConnectable(connectable); + } +} + +void AppModel::setConnected(bool value) +{ + if (value != connected) { + connected = value; + emit changedConnected(connected); + emit changedLaunchable(launchable && connected); + } +} + +void AppModel::setLaunchable(bool value) +{ + if (value != launchable) { + launchable = value; + emit changedLaunchable(launchable && connected); + } +} + +void AppModel::setLaunched(bool value) +{ + if (value != launched) { + launched = value; + emit changedLaunched(launched); + } +} + +void AppModel::setEditable(bool value) +{ + if (value != editable) { + editable = value; + emit changedEditable(editable && !connected); + } +} + + +void AppModel::clearLog() +{ + log.clear(); + emit clearedLog(); +} + +void AppModel::setName(const QString& p_name) +{ + name = p_name; +} diff --git a/roboute/models/appmodel.h b/roboute/models/appmodel.h new file mode 100644 index 0000000..389330b --- /dev/null +++ b/roboute/models/appmodel.h @@ -0,0 +1,61 @@ +#ifndef APPMODEL_H +#define APPMODEL_H + +#include "apppropertiesmodel.h" +#include "appcommandsmodel.h" + +#include +#include + +class AppModel : public QObject +{ + Q_OBJECT + +public: + AppModel(uint64_t p_id, const QString& p_name); + + const QString& getName() const; + void setName(const QString& p_name); + void logMessage(const QString& msg); + QString* getHistory() const; + bool getConnectable() const; + bool getConnected() const; + bool getLaunchable() const; + bool getLaunched() const; + bool getEditable() const; + void clearLog(); + +public: + const uint64_t id; + AppPropertiesModel props; + AppCommandsModel commands; + +signals: + void newLogMessage(const QString& msg); + void changedConnectable(bool value); + void changedConnected(bool value); + void changedLaunchable(bool value); + void changedLaunched(bool value); + void changedEditable(bool value); + void clearedLog(); + +public slots: + void setConnectable(bool value); + void setConnected(bool value); + void setLaunchable(bool value); + void setLaunched(bool value); + void setEditable(bool value); + +private: + typedef std::list List; + QString name; + List log; + bool connectable; + bool connected; + bool launchable; + bool launched; + bool editable; + +}; + +#endif // APPMODEL_H diff --git a/roboute/models/apppropertiesmodel.cpp b/roboute/models/apppropertiesmodel.cpp new file mode 100644 index 0000000..6b44926 --- /dev/null +++ b/roboute/models/apppropertiesmodel.cpp @@ -0,0 +1,143 @@ +#include "apppropertiesmodel.h" + +AppPropertiesModel::AppPropertiesModel(QObject* parent): + QAbstractTableModel(parent), + index(), + helper(), + map(), + toInsert() +{ +} + +AppPropertiesModel::~AppPropertiesModel() +{ + clear(); +} + + +void AppPropertiesModel::clear() +{ + beginResetModel(); + Map::iterator itr = map.begin(); + Map::iterator end = map.end(); + + for (; itr != end; ++itr) { + delete itr.value(); + } + map.clear(); + index.clear(); + helper.clear(); + endResetModel(); +} + +int AppPropertiesModel::columnCount(const QModelIndex& parent) const +{ + return 2; +} + +bool AppPropertiesModel::insertRows(int row, int count, const QModelIndex& parent) +{ + if (toInsert.size() != count) { + return false; + } + beginInsertRows(parent, row, row + count - 1); + + Index::const_iterator target = index.begin() + row; + for (int i = 0; i < count; ++i) { + List::iterator itr = toInsert.begin(); + Map::iterator mItr = map.insert(itr->first, itr->second); + index.insert(target, mItr); + helper.insert(itr->first, row + i); + toInsert.erase(itr); + } + + endInsertRows(); + return true; +} + +bool AppPropertiesModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (row + count > index.size()) { + return false; + } + beginRemoveRows(parent, row, row + count - 1); + Index::iterator itr; + Index::iterator beg = index.begin() + row; + Index::iterator end = beg + count; + for (itr = beg; itr != end; ++itr) { + Map::iterator mItr = *itr; + AppProp* prop = *mItr; + IndexHelper::iterator hItr = helper.find(prop->key); + delete prop; + map.erase(mItr); + helper.erase(hItr); + } + index.erase(beg, end); + + endRemoveRows(); + return true; +} + +int AppPropertiesModel::rowCount(const QModelIndex& parent) const +{ + return index.size(); +} + +void AppPropertiesModel::setProp(const QString& key, const QString& value) +{ + Map::iterator itr = map.find(key); + if (itr != map.end()) { + itr.value()->value = value; + const QModelIndex ind = QAbstractTableModel::index(*(helper.find(key)), 1); + emit dataChanged(ind, ind); + } else { + AppProp* item = new AppProp{key, value}; + + toInsert.push_back(Pair(key, item)); + insertRows(rowCount(), 1); + } +} + +void AppPropertiesModel::removeProp(const QString& key) +{ + IndexHelper::iterator itr = helper.find(key); + if (itr != helper.end()) { + removeRows(*itr, 1); + } +} + +QVariant AppPropertiesModel::data(const QModelIndex& i, int role) const +{ + Map::iterator itr = index[i.row()]; + int col = i.column(); + + switch(role) { + case Qt::DisplayRole: + if (col == 0) { + return itr.key(); + } else if (col == 1) { + return itr.value()->value; + } + } + + return QVariant(); +} + +QVariant AppPropertiesModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (orientation) { + case Qt::Horizontal: + switch(role) { + case Qt::DisplayRole: + if (section == 0) { + return "Key"; + } else if (section == 1) { + return "Value"; + } + } + } + + + return QVariant(); +} + diff --git a/roboute/models/apppropertiesmodel.h b/roboute/models/apppropertiesmodel.h new file mode 100644 index 0000000..eb65d6d --- /dev/null +++ b/roboute/models/apppropertiesmodel.h @@ -0,0 +1,51 @@ +#ifndef APPPROPERTIESMODEL_H +#define APPPROPERTIESMODEL_H + +#include +#include +#include + +#include +#include + +class AppPropertiesModel : public QAbstractTableModel +{ + Q_OBJECT + + struct AppProp + { + QString key; + QString value; + }; + +public: + AppPropertiesModel(QObject* parent = 0); + ~AppPropertiesModel(); + + void setProp(const QString& key, const QString& value); + void removeProp(const QString& key); + void clear(); + + QVariant data(const QModelIndex &i, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + +private: + typedef QMap Map; + typedef QMap IndexHelper; + typedef std::deque Index; + typedef std::pair Pair; + typedef std::list List; + + Index index; + IndexHelper helper; + Map map; + List toInsert; + +}; + + +#endif // APPPROPERTIESMODEL_H diff --git a/roboute/models/commands.cpp b/roboute/models/commands.cpp new file mode 100644 index 0000000..1ef3aee --- /dev/null +++ b/roboute/models/commands.cpp @@ -0,0 +1,10 @@ +#include "commands.h" + +Commands::Commands(const W::Address& address, QObject* parent): + C::Vocabulary(address, parent) +{ +} + +Commands::~Commands() +{ +} diff --git a/roboute/models/commands.h b/roboute/models/commands.h new file mode 100644 index 0000000..d0036c8 --- /dev/null +++ b/roboute/models/commands.h @@ -0,0 +1,13 @@ +#ifndef ROBOUTE_COMMANDS_H +#define ROBOUTE_COMMANDS_H + +#include + +class Commands : public C::Vocabulary +{ +public: + Commands(const W::Address& address, QObject* parent = 0); + ~Commands(); +}; + +#endif // ROBOUTE_COMMANDS_H diff --git a/roboute/models/service.cpp b/roboute/models/service.cpp new file mode 100644 index 0000000..88fd8dc --- /dev/null +++ b/roboute/models/service.cpp @@ -0,0 +1,489 @@ +#include "service.h" + +uint64_t Service::lastId = 0; + +Service::Service( + uint64_t p_id, + const QString& p_name, + const QString& p_address, + const QString& p_port, + const QString& p_login, + const QString& p_password, + const QString& p_logFile, + const QString& p_command +): + QObject(), + socket(new W::Socket(W::String(u"Roboute"))), + dataSsh(new W::SshSocket(p_login, p_password)), + commandSsh(new W::SshSocket(p_login, p_password)), + attributes(new C::Attributes(W::Address{u"attributes"})), + commands(new C::Vocabulary(W::Address{u"management"})), + login(p_login), + password(p_password), + logFile(p_logFile), + command(p_command), + psResults(), + pid(), + state(Disconnected), + appState(Unknown), + name(p_name), + address(p_address), + port(p_port), + id(p_id) +{ + QObject::connect(dataSsh, SIGNAL(opened()), this, SLOT(onDataSshOpened())); + QObject::connect(dataSsh, SIGNAL(closed()), this, SLOT(onSshClosed())); + QObject::connect(dataSsh, SIGNAL(data(const QString&)), this, SLOT(onDataSshData(const QString&))); + QObject::connect(dataSsh, SIGNAL(error(W::SshSocket::Error, const QString&)), this, SLOT(onSshError(W::SshSocket::Error, const QString&))); + QObject::connect(dataSsh, SIGNAL(finished()), this, SLOT(onDataSshFinished())); + + QObject::connect(commandSsh, SIGNAL(opened()), this, SLOT(onCommandSshOpened())); + QObject::connect(commandSsh, SIGNAL(closed()), this, SLOT(onSshClosed())); + QObject::connect(commandSsh, SIGNAL(data(const QString&)), this, SLOT(onCommandSshData( const QString&))); + QObject::connect(commandSsh, SIGNAL(error(W::SshSocket::Error, const QString&)), this, SLOT(onSshError(W::SshSocket::Error, const QString&))); + QObject::connect(commandSsh, SIGNAL(finished()), this, SLOT(onCommandSshFinished())); + + QObject::connect(socket, SIGNAL(connected()), this, SLOT(onSocketConnected())); + QObject::connect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); + QObject::connect(socket, SIGNAL(error(W::Socket::SocketError, const QString&)), this, SLOT(onSocketError(W::Socket::SocketError, const QString&))); + + QObject::connect(attributes, SIGNAL(attributeChange(const W::String&, const W::Object&)), + this, SLOT(onAttrChange(const W::String&, const W::Object&))); + QObject::connect(attributes, SIGNAL(serviceMessage(const QString&)), SIGNAL(serviceMessage(const QString&))); + QObject::connect(commands, SIGNAL(serviceMessage(const QString&)), SIGNAL(serviceMessage(const QString&))); + QObject::connect(commands, SIGNAL(newElement(const W::String&, const W::Object&)), SLOT(onAddCommand(const W::String&, const W::Object&))); + QObject::connect(commands, SIGNAL(removeElement(const W::String&)), SLOT(onRemoveCommand(const W::String&))); + QObject::connect(commands, SIGNAL(clear()), SLOT(onClearCommands())); +} + +Service::~Service() +{ + delete commands; + delete attributes; + delete commandSsh; + delete dataSsh; + delete socket; +} + +Service* Service::create(const QMap& params) +{ + QString name = params["name"]; + QString address = params["address"]; + QString port = params["port"]; + QString login = params["login"]; + QString password = params["password"]; + QString logFile = params["logFile"]; + QString command = params["command"]; + + Service* srv = new Service(++lastId, name, address, port, login, password, logFile, command); + return srv; +} + +Service* Service::fromSerialized(const QMap& params) +{ + QString name = params["name"].toString(); + QString address = params["address"].toString(); + QString port = params["port"].toString(); + QString login = params["login"].toString(); + QString password = params["password"].toString(); + QString logFile = params["logFile"].toString(); + QString command = params["command"].toString(); + uint64_t id = params["id"].toUInt(); + + if (id > lastId) { + lastId = id; + } + Service* srv = new Service(id, name, address, port, login, password, logFile, command); + return srv; +} + +void Service::onDataSshOpened() +{ + if (state == Connecting) { + state = Echo; + dataSsh->execute("echo === Roboute connected === >> " + logFile); + emit serviceMessage("checking log file"); + + } else { + //TODO; + } +} + +void Service::onCommandSshOpened() +{ + if (appState == Unknown) { + appState = Checking; + requestPid(); + emit serviceMessage("checking if the process launched"); + } +} + +void Service::onSshClosed() +{ + if (state == Disconnected) { + emit serviceMessage("connection clozed"); + emit stopped(); + emit disconnected(); + } + if (state == Disconnecting) { + state = Disconnected; + } +} + +void Service::onSshError(W::SshSocket::Error errCode, const QString& msg) +{ + emit serviceMessage(msg); + switch (state) { + case Disconnected: + break; + case Connecting: + state = Disconnected; + emit disconnected(); + break; + case Echo: + case Listening: + case Connected: + disconnect(); + break; + default: + break; + } +} + +void Service::onDataSshData(const QString& data) +{ + switch (state) { + case Listening: + state = Connected; + emit connected(); + emit serviceMessage("first data from log file, connected!"); + case Connected: + if (appState == Launching) { + if (data.contains("ready")) { + connectWebsocket(); + requestPid(); + } + } + emit log(data); + break; + default: + break; + } +} + +void Service::onCommandSshData(const QString& data) +{ + QStringList list = data.split("\n"); + psResults.insert(psResults.end(), list.begin(), list.end()); +} + +void Service::onCommandSshFinished() +{ + switch (appState) { + case Checking: + case WaitingWebSocket: + case Active: //that's very bad! + { + bool found = false; + std::list::const_iterator itr = psResults.begin(); + std::list::const_iterator end = psResults.end(); + QString option; + for (; itr != end; ++itr) { + option = *itr; + if (!option.contains(" grep ") && option.contains(command)) { + found = true; + break; + } + } + + if (found) { + QStringList mems = option.split(QRegExp("\\s")); + QStringList::const_iterator mItr = mems.begin(); + QStringList::const_iterator mEnd = mems.end(); + found = false; + for (; mItr != mEnd; ++mItr) { + QString candidate = *mItr; + if (candidate.contains(QRegExp("\\d{2,}"))) { + pid = candidate; + found = true; + break; + } + } + if (found) { + emit serviceMessage("got the process id: " + pid + ", correct?"); + } else { + emit serviceMessage("Couldn't find process id"); + } + + emit serviceMessage("process seems to be launched"); + + if (appState == Checking) { + connectWebsocket(); + } + } else { + appState = Dead; + emit stopped(); + emit serviceMessage("process seems to be not launched"); + } + break; + } + case Launching: + emit serviceMessage(QString("process launch command sent,") + + " requesting pid, waiting for specific 'ready' key in log"); //need to do smthing about this + break; + default: + break; + + } +} + + +void Service::connect() +{ + if (state == Disconnected) { + dataSsh->open(address); + commandSsh->open(address); + state = Connecting; + emit serviceMessage("connecting to " + address); + emit connecting(); + } else { + //TODO; + } +} + +void Service::disconnect() +{ + if (state != Disconnected) { + state = Disconnecting; + if (appState == Active) { + commands->unsubscribe(); + attributes->unsubscribe(); + socket->close(); + } + pid = ""; + psResults.clear(); + appState = Unknown; + emit serviceMessage("disconnecting"); + emit disconnecting(); + emit stopped(); + dataSsh->interrupt(); + dataSsh->close(); + commandSsh->close(); + } +} + +void Service::onDataSshFinished() +{ + switch (state) { + case Echo: + emit serviceMessage("log file checked"); + dataSsh->execute("tail -f " + logFile); + state = Listening; + emit serviceMessage("listening to the log file"); + break; + default: + break; + } +} + +QVariant Service::saveState() const +{ + QMap state; + quint64 qid = id; + state.insert("id", qid); + state.insert("login", login); + state.insert("password", password); + state.insert("logFile", logFile); + state.insert("name", name); + state.insert("address", address); + state.insert("port", port); + state.insert("command", command); + + return state; +} + +void Service::launch() +{ + if (state == Connected && appState == Dead) { + appState = Launching; + commandSsh->execute("nohup " + command + " >> " + logFile + " 2>&1 &"); + emit launching(); + } +} + +void Service::stop() +{ + if (state == Connected && appState == Active) { + QString file = command.section("/", -1); + commandSsh->execute("kill -s SIGINT " + pid); + appState = Stopping; + emit stopping(); + } +} + + +void Service::onSocketConnected() +{ + appState = Active; //this is a fail It's not right! + attributes->subscribe(); + commands->subscribe(); + emit launched(); +} + +void Service::onSocketDisconnected() +{ + appState = Dead; //this is not correct! + emit stopped(); +} + +void Service::onSocketError(W::Socket::SocketError err, const QString& msg) +{ + emit serviceMessage(msg); //this is not correct! + appState = Dead; + emit stopped(); +} + +void Service::registerContollers(W::Dispatcher* dp) +{ + QObject::connect(socket, SIGNAL(message(const W::Event&)), dp, SLOT(pass(const W::Event&))); + attributes->registerController(dp, socket); + commands->registerController(dp, socket); +} + +void Service::unregisterControllers(W::Dispatcher* dp) +{ + QObject::disconnect(socket, SIGNAL(message(const W::Event&)), dp, SLOT(pass(const W::Event&))); + commands->unregisterController(); + attributes->unregisterController(); +} + +void Service::requestPid() +{ + pid = ""; + psResults.clear(); + commandSsh->execute("ps -ax | grep '" + command + "'"); +} + +void Service::connectWebsocket() +{ + appState = WaitingWebSocket; + socket->open(W::String(address.toStdString()), W::Uint64(port.toInt())); + emit serviceMessage("trying to reach service by websocket"); +} + +void Service::onAttrChange(const W::String& key, const W::Object& value) +{ + emit attributeChanged(QString::fromStdString(key.toString()), QString::fromStdString(value.toString())); +} + +void Service::onAddCommand(const W::String& key, const W::Object& value) +{ + QMap arguments; + const W::Vocabulary& vc = static_cast(value); + const W::Vocabulary& args = static_cast(vc.at(u"arguments")); + + W::Vector keys = args.keys(); + uint64_t size = keys.length(); + for (int i = 0; i < size; ++i) { + const W::String& name = static_cast(keys.at(i)); + const W::Uint64& type = static_cast(args.at(name)); + + arguments.insert(QString::fromStdString(name.toString()), type); + } + + emit addCommand(QString::fromStdString(key.toString()), arguments); +} + +void Service::onRemoveCommand(const W::String& key) +{ + emit removeCommand(QString::fromStdString(key.toString())); +} + +void Service::onClearCommands() +{ + emit clearCommands(); +} + +void Service::launchCommand(const QString& name, const QMap& args) +{ + const W::Vocabulary& val = static_cast(commands->at(W::String(name.toStdString()))); + const W::Vocabulary& aT = static_cast(val.at(u"arguments")); + + QMap::const_iterator itr = args.begin(); + QMap::const_iterator end = args.end(); + W::Vocabulary* vc = new W::Vocabulary(); + + for (; itr != end; ++itr) { + W::String wKey(itr.key().toStdString()); + const W::Uint64& wType = static_cast(aT.at(wKey)); + int type = wType; + W::Object* value; + switch (type) { + case 0: + value = new W::String(itr.value().toString().toStdString()); + break; + case 2: + value = new W::Uint64(itr.value().toInt()); + break; + default: + throw 1; + } + vc->insert(wKey, value); + } + + W::Event ev(static_cast(val.at(u"address")), vc); + ev.setSenderId(socket->getId()); + socket->send(ev); +} + +QMap Service::getData() const +{ + QMap data; + + data["name"] = name; + data["address"] = address; + data["port"] = port; + data["login"] = login; + data["password"] = password; + data["logFile"] = logFile; + data["command"] = command; + + return data; +} + +void Service::passNewData(const QMap data) +{ + if (data.contains("name") && data.value("name") != name) { + name = data.value("name"); + emit changeName(name); + } + + if (data.contains("address") && data.value("address") != address) { + address = data.value("address"); + } + + if (data.contains("port") && data.value("port") != port) { + port = data.value("port"); + } + + if (data.contains("login") && data.value("login") != login) { + login = data.value("login"); + dataSsh->setLogin(login); + commandSsh->setLogin(login); + } + + if (data.contains("password") && data.value("password") != password) { + password = data.value("password"); + dataSsh->setPassword(password); + commandSsh->setPassword(password); + } + + if (data.contains("logFile") && data.value("logFile") != logFile) { + logFile = data.value("logFile"); + } + + if (data.contains("command") && data.value("command") != command) { + command = data.value("command"); + } +} + diff --git a/roboute/models/service.h b/roboute/models/service.h new file mode 100644 index 0000000..cf9d4ca --- /dev/null +++ b/roboute/models/service.h @@ -0,0 +1,131 @@ +#ifndef SERVICE_H +#define SERVICE_H + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +class Service : public QObject +{ + Q_OBJECT +private: + Service(uint64_t p_id, + const QString& p_name, + const QString& p_address, + const QString& p_port, + const QString& p_login, + const QString& p_password, + const QString& p_logFile, + const QString& p_command + ); + +public: + ~Service(); + + static Service* create(const QMap& params); + static Service* fromSerialized(const QMap& params); + QVariant saveState() const; + void registerContollers(W::Dispatcher* dp); + void unregisterControllers(W::Dispatcher* dp); + QMap getData() const; + void passNewData(const QMap data); + +private: + enum State { + Disconnected, + Connecting, + Echo, + Listening, + Connected, + Disconnecting + }; + + enum AppState { + Unknown, + Checking, + Dead, + Launching, + WaitingWebSocket, + Active, + Stopping + }; + + W::Socket* socket; + W::SshSocket* dataSsh; + W::SshSocket* commandSsh; + C::Attributes* attributes; + C::Vocabulary* commands; + static uint64_t lastId; + QString login; + QString password; + QString logFile; + QString command; + std::list psResults; + QString pid; + State state; + AppState appState; + + void requestPid(); + void connectWebsocket(); + +public: + QString name; + QString address; + QString port; + const uint64_t id; + +signals: + void serviceMessage(const QString& msg); + void connecting(); + void connected(); + void disconnecting(); + void disconnected(); + void launching(); + void launched(); + void stopping(); + void stopped(); + void log(const QString& data); + void attributeChanged(const QString& name, const QString& value); + void addCommand(const QString& name, const QMap& arguments); + void removeCommand(const QString& name); + void clearCommands(); + void changeName(const QString& name); + +public slots: + void connect(); + void disconnect(); + void launch(); + void stop(); + void launchCommand(const QString& name, const QMap& args); + +private slots: + void onDataSshOpened(); + void onCommandSshOpened(); + void onSshClosed(); + void onDataSshData(const QString& data); + void onCommandSshData(const QString& data); + void onSshError(W::SshSocket::Error errCode, const QString& msg); + void onDataSshFinished(); + void onCommandSshFinished(); + + void onSocketConnected(); + void onSocketDisconnected(); + void onSocketError(W::Socket::SocketError err, const QString& msg); + void onAttrChange(const W::String& key, const W::Object& value); + void onAddCommand(const W::String& key, const W::Object& value); + void onRemoveCommand(const W::String& key); + void onClearCommands(); +}; + +#endif // SERVICE_H diff --git a/roboute/roboute.cpp b/roboute/roboute.cpp new file mode 100644 index 0000000..100db41 --- /dev/null +++ b/roboute/roboute.cpp @@ -0,0 +1,295 @@ +#include "roboute.h" + +#include + +using std::cout; +using std::endl; + +Roboute* Roboute::roboute = 0; + +Roboute::Roboute(QObject *parent): + QObject(parent), + logger(new W::Logger()), + services(), + dispatcher(new W::Dispatcher()) +{ + if (roboute != 0) + { + throw SingletonError(); + } + Roboute::roboute = this; + + dispatcher->registerDefaultHandler(logger); +} + +Roboute::~Roboute() +{ + QMap::iterator beg = services.begin(); + QMap::iterator end = services.end(); + + for (; beg != end; ++beg) { + delete *beg; + } + + dispatcher->unregisterDefaultHandler(logger); + + delete logger; + delete dispatcher; + + Roboute::roboute = 0; +} + +void Roboute::start() +{ + debug("Starting roboute..."); + readSettings(); + debug("Roboute is ready"); +} + +void Roboute::stop() +{ + debug("Stopping roboute..."); + saveSettings(); + QMap::iterator beg = services.begin(); + QMap::iterator end = services.end(); + + for (; beg != end; ++beg) { + Service* srv = *beg; + srv->disconnect(); + srv->unregisterControllers(dispatcher); + } +} + +void Roboute::debug(std::string str) const +{ + cout << str << endl; + QString dbg = str.c_str(); + dbg.append("\n"); + emit debugMessage(dbg); +} + +void Roboute::debug(uint64_t id, const QString& msg) const +{ + Service* srv = services[id]; + QString dbg = srv->name + ": " + msg; + debug(dbg.toStdString()); +} + + +void Roboute::addService(const QMap& params) +{ + Service* srv = Service::create(params); + addService(srv); +} + +void Roboute::removeService(uint64_t id) +{ + QMap::iterator itr = services.find(id); + if (itr != services.end()) { + Service* srv = *itr; + debug(id, "removing..."); + srv->unregisterControllers(dispatcher); + srv->disconnect(); + srv->deleteLater(); + services.erase(itr); + emit serviceRemoved(id); + } +} + +void Roboute::addService(Service* srv) +{ + services.insert(srv->id, srv); + + connect(srv, SIGNAL(serviceMessage(const QString&)), this, SLOT(onServiceMessage(const QString&))); + connect(srv, SIGNAL(changeName(const QString&)), this, SLOT(onServiceChangeName(const QString&))); + connect(srv, SIGNAL(connecting()), this, SLOT(onServiceConnecting())); + connect(srv, SIGNAL(connected()), this, SLOT(onServiceConnected())); + connect(srv, SIGNAL(disconnecting()), this, SLOT(onServiceDisconnecting())); + connect(srv, SIGNAL(disconnected()), this, SLOT(onServiceDisconnected())); + connect(srv, SIGNAL(launching()), this, SLOT(onServiceLaunching())); + connect(srv, SIGNAL(launched()), this, SLOT(onServiceLaunched())); + connect(srv, SIGNAL(stopping()), this, SLOT(onServiceStopping())); + connect(srv, SIGNAL(stopped()), this, SLOT(onServiceStopped())); + connect(srv, SIGNAL(attributeChanged(const QString&, const QString&)), this, SLOT(onAttributeChanged(const QString&, const QString&))); + connect(srv, SIGNAL(log(const QString&)), this, SLOT(onServiceLog(const QString&))); + connect(srv, SIGNAL(addCommand(const QString&, const QMap&)), SLOT(onAddCommand(const QString&, const QMap&))); + connect(srv, SIGNAL(removeCommand(const QString&)), SLOT(onRemoveCommand(const QString&))); + connect(srv, SIGNAL(clearCommands()), SLOT(onClearCommands())); + + srv->registerContollers(dispatcher); + + emit newService(srv->id, srv->name); +} + + +void Roboute::connectService(uint64_t id) +{ + Service* srv = services[id]; + srv->connect(); +} + +void Roboute::disconnectService(uint64_t id) +{ + Service* srv = services[id]; + srv->disconnect(); +} + +void Roboute::launchService(uint64_t id) +{ + Service* srv = services[id]; + srv->launch(); +} + +void Roboute::stopService(uint64_t id) +{ + Service* srv = services[id]; + srv->stop(); +} + +void Roboute::onServiceMessage(const QString& msg) +{ + Service* srv = static_cast(sender()); + debug(srv->id, msg); +} + +void Roboute::onServiceConnecting() +{ + Service* srv = static_cast(sender()); + emit serviceConnecting(srv->id); +} + +void Roboute::onServiceConnected() +{ + Service* srv = static_cast(sender()); + emit serviceConnected(srv->id); +} + +void Roboute::onServiceDisconnecting() +{ + Service* srv = static_cast(sender()); + emit serviceDisconnecting(srv->id); +} + +void Roboute::onServiceDisconnected() +{ + Service* srv = static_cast(sender()); + emit serviceDisconnected(srv->id); +} + +void Roboute::onServiceLog(const QString& msg) +{ + Service* srv = static_cast(sender()); + emit log(srv->id, msg); +} + +void Roboute::saveSettings() const +{ + debug("Saving settings..."); + + QSettings settings; + settings.beginGroup("services"); + + QList list; + + QMap::const_iterator beg = services.begin(); + QMap::const_iterator end = services.end(); + + for (; beg != end; ++beg) { + list.push_back((*beg)->saveState()); + } + settings.setValue("list", list); + + settings.endGroup(); +} + +void Roboute::readSettings() +{ + debug("Reading settings..."); + + QSettings settings; + + QList list = settings.value("services/list").toList(); + + QList::const_iterator beg = list.begin(); + QList::const_iterator end = list.end(); + + for (; beg != end; ++beg) { + addService(Service::fromSerialized(beg->toMap())); + } +} + +void Roboute::onServiceLaunched() +{ + Service* srv = static_cast(sender()); + emit serviceLaunched(srv->id); +} + +void Roboute::onServiceLaunching() +{ + Service* srv = static_cast(sender()); + emit serviceLaunching(srv->id); +} + +void Roboute::onServiceStopped() +{ + Service* srv = static_cast(sender()); + emit serviceStopped(srv->id); +} + +void Roboute::onServiceStopping() +{ + Service* srv = static_cast(sender()); + emit serviceStopping(srv->id); +} + +void Roboute::onAttributeChanged(const QString& key, const QString& value) +{ + Service* srv = static_cast(sender()); + emit serviceAttrChange(srv->id, key, value); +} + +void Roboute::onAddCommand(const QString& key, const QMap& arguments) +{ + Service* srv = static_cast(sender()); + emit serviceAddCommand(srv->id, key, arguments); +} + +void Roboute::onRemoveCommand(const QString& key) +{ + Service* srv = static_cast(sender()); + emit serviceRemoveCommand(srv->id, key); +} + +void Roboute::onClearCommands() +{ + Service* srv = static_cast(sender()); + emit serviceClearCommands(srv->id); +} + +void Roboute::launchCommand(uint64_t id, const QString& name, const QMap& args) +{ + Service* srv = services[id]; + srv->launchCommand(name, args); +} + +void Roboute::editService(uint64_t id) +{ + Service* srv = services[id]; + + emit serviceEdit(id, srv->getData()); +} + +void Roboute::changeService(uint64_t id, const QMap& params) +{ + Service* srv = services[id]; + + srv->passNewData(params); +} + +void Roboute::onServiceChangeName(const QString& name) +{ + Service* srv = static_cast(sender()); + + emit serviceChangeName(srv->id, name); +} + diff --git a/roboute/roboute.h b/roboute/roboute.h new file mode 100644 index 0000000..e5afa2b --- /dev/null +++ b/roboute/roboute.h @@ -0,0 +1,115 @@ +#ifndef ROBOUTE_H +#define ROBOUTE_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +class Roboute: public QObject +{ + Q_OBJECT + +public: + Roboute(QObject *parent = 0); + ~Roboute(); + + static Roboute* roboute; + +private: + + W::Logger *logger; + QMap services; + +public: + W::Dispatcher *dispatcher; + +public: + +private: + void debug(std::string str) const; + void debug(uint64_t id, const QString& msg) const; + void saveSettings() const; + void readSettings(); + void addService(Service* srv); + +signals: + void debugMessage(const QString& msg) const; + void log(uint64_t id,const QString& msg); + void newService(uint64_t id, const QString& name); + void serviceChangeName(uint64_t id, const QString& name); + void serviceAttrChange(uint64_t id, const QString& key, const QString& value); + void serviceRemoved(uint64_t id); + void serviceConnected(uint64_t id); + void serviceConnecting(uint64_t id); + void serviceDisconnecting(uint64_t id); + void serviceDisconnected(uint64_t id); + void serviceConnectionFailed(uint64_t id); + void serviceLaunched(uint64_t id); + void serviceStopped(uint64_t id); + void serviceLaunching(uint64_t id); + void serviceStopping(uint64_t id); + void serviceEdit(uint64_t id, const QMap& params); + void serviceAddCommand(uint64_t id, const QString& key, const QMap& arguments); + void serviceRemoveCommand(uint64_t id, const QString& key); + void serviceClearCommands(uint64_t id); + + +public slots: + void start(); + void stop(); + void addService(const QMap& params); + void changeService(uint64_t id, const QMap& params); + void removeService(uint64_t id); + void connectService(uint64_t id); + void disconnectService(uint64_t id); + void launchService(uint64_t id); + void editService(uint64_t id); + void stopService(uint64_t id); + void launchCommand(uint64_t id, const QString& name, const QMap& args); + +private slots: + void onServiceMessage(const QString& msg); + void onServiceChangeName(const QString& name); + void onServiceConnecting(); + void onServiceConnected(); + void onServiceDisconnecting(); + void onServiceDisconnected(); + void onServiceLaunching(); + void onServiceLaunched(); + void onServiceStopping(); + void onServiceStopped(); + void onServiceLog(const QString& msg); + void onAttributeChanged(const QString& key, const QString& value); + void onAddCommand(const QString& key, const QMap& arguments); + void onRemoveCommand(const QString& key); + void onClearCommands(); + +private: + class SingletonError: + public Utils::Exception + { + public: + SingletonError():Exception(){} + + std::string getMessage() const{return "Roboute is a singleton, there was an attempt to construct it at the second time";} + }; +}; + +#endif // ROBOUTE_H diff --git a/roboute/views/CMakeLists.txt b/roboute/views/CMakeLists.txt new file mode 100644 index 0000000..0dbf042 --- /dev/null +++ b/roboute/views/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.12) +project(robouteViews) + +find_package(Qt5Core REQUIRED) +find_package(Qt5Widgets REQUIRED) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +set(HEADERS + mainview.h + detailedview.h + newappdialogue.h + commandform.h +) + +set(SOURCES + mainview.cpp + detailedview.cpp + newappdialogue.cpp + commandform.cpp +) + +add_library(robouteViews STATIC ${HEADERS} ${SOURCES}) + +target_link_libraries(robouteViews Qt5::Core) +target_link_libraries(robouteViews Qt5::Widgets) diff --git a/roboute/views/commandform.cpp b/roboute/views/commandform.cpp new file mode 100644 index 0000000..c17288a --- /dev/null +++ b/roboute/views/commandform.cpp @@ -0,0 +1,104 @@ +#include "commandform.h" + +#include +#include +#include +#include + +CommandForm::CommandForm(const QString& commandName, const AMap& p_args, QWidget* parent): + QDialog(parent), + args(p_args), + editors(), + name(commandName) +{ + QVBoxLayout* mainLayout = new QVBoxLayout(this); + QHBoxLayout* buttonsLayout = new QHBoxLayout(); + QFormLayout* formLayout = new QFormLayout(); + + mainLayout->addWidget(new QLabel(name, this)); + mainLayout->addLayout(formLayout); + mainLayout->addStretch(); + mainLayout->addLayout(buttonsLayout); + + setLayout(mainLayout); + createForm(formLayout); + createButtons(buttonsLayout); +} + +void CommandForm::createButtons(QHBoxLayout* layout) +{ + layout->addStretch(); + + QPushButton* accept = new QPushButton(QIcon::fromTheme("dialog-ok"), tr("OK"), this); + QPushButton* reject = new QPushButton(QIcon::fromTheme("dialog-cancel"), tr("Cancel"), this); + + connect(accept, SIGNAL(clicked()), SLOT(accept())); + connect(reject, SIGNAL(clicked()), SLOT(reject())); + + layout->addWidget(accept); + layout->addWidget(reject); +} + +void CommandForm::createForm(QFormLayout* layout) +{ + AMap::const_iterator itr = args.begin(); + AMap::const_iterator end = args.end(); + + for (; itr != end; ++itr) { + bool supportable = false; + QWidget* editor; + + switch (itr.value()) { + case 0: + editor = new QLineEdit(this); + supportable = true; + break; + case 2: + QSpinBox* spbox = new QSpinBox(this); + spbox->setMaximum(UINT16_MAX); //TODO what the hell is wrong with this shit? + editor = spbox; + supportable = true; + break; + } + + if (supportable) { + layout->addRow(tr(itr.key().toStdString().c_str()), editor); + editors.insert(itr.key(), editor); + } + + } +} + +QMap CommandForm::getData() const +{ + AMap::const_iterator itr = args.begin(); + AMap::const_iterator end = args.end(); + + QMap result; + + for (; itr != end; ++itr) { + bool supportable = false; + QWidget* editor = editors.find(itr.key()).value(); + + switch (itr.value()) { + case 0: + result.insert(itr.key(), static_cast(editor)->text()); + supportable = true; + break; + case 2: + result.insert(itr.key(), static_cast(editor)->value()); + supportable = true; + break; + } + } + + return result; +} + +QString CommandForm::getName() const +{ + return name; +} + + + diff --git a/roboute/views/commandform.h b/roboute/views/commandform.h new file mode 100644 index 0000000..083b270 --- /dev/null +++ b/roboute/views/commandform.h @@ -0,0 +1,31 @@ +#ifndef COMMANDFORM_H +#define COMMANDFORM_H + +#include +#include +#include +#include +#include +#include + +class CommandForm : public QDialog +{ + Q_OBJECT + typedef QMap AMap; +public: + CommandForm(const QString& commandName, const AMap& p_args, QWidget* parent = 0); + + QMap getData() const; + QString getName() const; + +private: + AMap args; + QMap editors; + QString name; + + void createForm(QFormLayout* layout); + void createButtons(QHBoxLayout* layout); + +}; + +#endif // COMMANDFORM_H diff --git a/roboute/views/detailedview.cpp b/roboute/views/detailedview.cpp new file mode 100644 index 0000000..b3fb580 --- /dev/null +++ b/roboute/views/detailedview.cpp @@ -0,0 +1,321 @@ +#include "detailedview.h" + +#include + +DetailedView::DetailedView(QWidget* parent): + QWidget(parent), + layout(new QGridLayout(this)), + topPanel(new QHBoxLayout()), + logArea(new QTextEdit(this)), + splitter(new QSplitter(this)), + dock(new QWidget(this)), + props(new QTableView(dock)), + commands(new QListView(dock)), + connectBtn(new QPushButton(QIcon::fromTheme("state-ok"), "", this)), + launchBtn(new QPushButton(QIcon::fromTheme("system-run"), "", this)), + clearBtn(new QPushButton(QIcon::fromTheme("trash-empty"), "", this)), + removeBtn(new QPushButton(QIcon::fromTheme("delete"), "", this)), + editBtn(new QPushButton(QIcon::fromTheme("edit-rename"), "", this)), + connected(false), + launched(false), + propsShown(false), + commandsShown(false), + model(0) +{ + setLayout(layout); + logArea->setReadOnly(true); + + layout->addLayout(topPanel, 0, 0, 1, 1); + layout->addWidget(splitter, 1, 0, 1, 1); + + splitter->addWidget(logArea); + splitter->addWidget(dock); + + QHBoxLayout* lay = new QHBoxLayout(); + dock->setLayout(lay); + lay->addWidget(props); + lay->addWidget(commands); + lay->setContentsMargins(0,0,0,0); + + props->verticalHeader()->hide(); + props->horizontalHeader()->setStretchLastSection(true); + props->setCornerButtonEnabled(false); + props->setShowGrid(false); + + props->hide(); + commands->hide(); + dock->hide(); + + connectBtn->setToolTip(tr("Connect")); + connectBtn->setEnabled(false); + launchBtn->setToolTip(tr("Launch")); + launchBtn->setEnabled(false); + clearBtn->setToolTip(tr("Clear log")); + clearBtn->setEnabled(false); + removeBtn->setToolTip(tr("Remove")); + removeBtn->setEnabled(false); + editBtn->setToolTip(tr("Edit")); + editBtn->setEnabled(false); + QObject::connect(connectBtn, SIGNAL(clicked()), this, SLOT(onConnectClick())); + QObject::connect(launchBtn, SIGNAL(clicked()), this, SLOT(onLaunchClick())); + QObject::connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClearClick())); + QObject::connect(removeBtn, SIGNAL(clicked()), this, SLOT(onRemoveClick())); + QObject::connect(editBtn, SIGNAL(clicked()), this, SLOT(onEditClick())); + QObject::connect(commands, SIGNAL(doubleClicked(const QModelIndex)), SLOT(onCommandDoubleClicked(const QModelIndex))); + + topPanel->addWidget(connectBtn); + topPanel->addWidget(launchBtn); + topPanel->addWidget(clearBtn); + topPanel->addStretch(); + topPanel->addWidget(editBtn); + topPanel->addWidget(removeBtn); + + layout->setContentsMargins(0,0,0,0); +} + +void DetailedView::appendMessage(const QString& msg) +{ + QStringList list = msg.split('\n'); + QStringList::const_iterator itr = list.begin(); + QStringList::const_iterator end = list.end(); + for (;itr != end; ++itr) { + QString str = *itr; + if (str != "") { + logArea->append(*itr); + } + } +} + +void DetailedView::clear() +{ + logArea->clear(); + connectBtn->setToolTip(tr("Connect")); + connectBtn->setEnabled(false); + connectBtn->setIcon(QIcon::fromTheme("state-ok")); + launchBtn->setToolTip(tr("Launch")); + launchBtn->setEnabled(false); + launchBtn->setIcon(QIcon::fromTheme("kt-start")); + clearBtn->setEnabled(false); + removeBtn->setEnabled(false); + connected = false; + launched = false; +} + +void DetailedView::setConnectable(bool value) +{ + connectBtn->setEnabled(value); +} + +void DetailedView::setConnected(bool value) +{ + if (connected != value) { + connected = value; + if (connected) { + connectBtn->setToolTip(tr("Disonnect")); + connectBtn->setIcon(QIcon::fromTheme("state-error")); + } else { + connectBtn->setToolTip(tr("Connect")); + connectBtn->setIcon(QIcon::fromTheme("state-ok")); + } + } +} + +void DetailedView::setLaunchable(bool value) +{ + launchBtn->setEnabled(value); +} + +void DetailedView::setLaunched(bool value) +{ + if (launched != value) { + launched = value; + if (launched) { + launchBtn->setToolTip(tr("Stop")); + launchBtn->setIcon(QIcon::fromTheme("kt-stop")); + } else { + launchBtn->setToolTip(tr("Launch")); + launchBtn->setIcon(QIcon::fromTheme("kt-start")); + } + } +} + +void DetailedView::onConnectClick() +{ + if (model == 0) { + return; + } + if (connected) { + emit disconnect(model->id); + } else { + emit connect(model->id); + } +} + +void DetailedView::onLaunchClick() +{ + if (model == 0) { + return; + } + if (launched) { + emit stop(model->id); + } else { + emit launch(model->id); + } +} + + +void DetailedView::onRemoveClick() +{ + if (model == 0) { + return; + } + emit remove(model->id); +} + +void DetailedView::onEditClick() +{ + if (model == 0) { + return; + } + emit edit(model->id); +} + +void DetailedView::setRemovable(bool value) +{ + removeBtn->setEnabled(value); +} + +void DetailedView::setEditable(bool value) +{ + editBtn->setEnabled(value); +} + + +void DetailedView::setModel(AppModel* p_model) +{ + if (model != 0) { + clearModel(); + } + model = p_model; + QString* history = model->getHistory(); + appendMessage(*history); + setConnectable(model->getConnectable()); + setConnected(model->getConnected()); + setLaunchable(model->getLaunchable()); + setLaunched(model->getLaunched()); + setRemovable(model->id != 0); + setEditable(model->getEditable()); + clearBtn->setEnabled(true); + delete history; + QObject::connect(model, SIGNAL(newLogMessage(const QString&)), this, SLOT(appendMessage(const QString&))); + QObject::connect(model, SIGNAL(changedConnectable(bool)), this, SLOT(setConnectable(bool))); + QObject::connect(model, SIGNAL(changedConnected(bool)), this, SLOT(setConnected(bool))); + QObject::connect(model, SIGNAL(changedLaunchable(bool)), this, SLOT(setLaunchable(bool))); + QObject::connect(model, SIGNAL(changedLaunched(bool)), this, SLOT(setLaunched(bool))); + QObject::connect(model, SIGNAL(changedEditable(bool)), this, SLOT(setEditable(bool))); + QObject::connect(model, SIGNAL(clearedLog()), this, SLOT(clearedLog())); + + QItemSelectionModel *m1 = props->selectionModel(); + props->setModel(&model->props); + delete m1; + + QItemSelectionModel *m2 = commands->selectionModel(); + commands->setModel(&model->commands); + delete m2; +} + +void DetailedView::clearModel() +{ + if (model != 0) { + clear(); + QObject::disconnect(model, SIGNAL(newLogMessage(const QString&)), this, SLOT(appendMessage(const QString&))); + QObject::disconnect(model, SIGNAL(changedConnectable(bool)), this, SLOT(setConnectable(bool))); + QObject::disconnect(model, SIGNAL(changedConnected(bool)), this, SLOT(setConnected(bool))); + QObject::disconnect(model, SIGNAL(changedLaunchable(bool)), this, SLOT(setLaunchable(bool))); + QObject::disconnect(model, SIGNAL(changedLaunched(bool)), this, SLOT(setLaunched(bool))); + QObject::disconnect(model, SIGNAL(clearedLog()), this, SLOT(clearedLog())); + + model = 0; + } +} + +void DetailedView::saveSettings() +{ + QSettings settings; + + settings.beginGroup("detailedView"); + + settings.setValue("splitterState", splitter->saveState()); + settings.setValue("propsHeaderState", props->horizontalHeader()->saveState()); + + settings.endGroup(); +} + +void DetailedView::readSettings() +{ + QSettings settings; + + splitter->restoreState(settings.value("detailedView/splitterState").toByteArray()); + props->horizontalHeader()->restoreState(settings.value("detailedView/propsHeaderState").toByteArray()); +} + +void DetailedView::showAttrs(bool value) +{ + if (value) { + props->show(); + } else { + props->hide(); + } + propsShown = value; + checkDock(); + +} + +void DetailedView::showCommands(bool value) +{ + if (value) { + commands->show(); + } else { + commands->hide(); + } + commandsShown = value; + checkDock(); +} + +void DetailedView::checkDock() +{ + if (commandsShown || propsShown) { + dock->show(); + } else { + dock->hide(); + } +} + +void DetailedView::onCommandDoubleClicked(const QModelIndex& index) +{ + if (model == 0) { + return; + } + emit launchCommand(model->id, index.data().toString()); +} + +uint64_t DetailedView::getModelId() +{ + if (model == 0) { + throw 1; + } + return model->id; +} + +void DetailedView::onClearClick() +{ + if (model == 0) { + return; + } + emit clearLog(model->id); +} + +void DetailedView::clearedLog() +{ + logArea->clear(); +} diff --git a/roboute/views/detailedview.h b/roboute/views/detailedview.h new file mode 100644 index 0000000..db02746 --- /dev/null +++ b/roboute/views/detailedview.h @@ -0,0 +1,86 @@ +#ifndef DETAILEDVIEW_H +#define DETAILEDVIEW_H + +#include "../models/appmodel.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DetailedView : public QWidget +{ + Q_OBJECT + +public: + DetailedView(QWidget* parent = 0); + void setModel(AppModel* p_model); + void clearModel(); + uint64_t getModelId(); + + void saveSettings(); + void readSettings(); + + void showAttrs(bool value); + void showCommands(bool value); + +private: + QGridLayout* layout; + QHBoxLayout* topPanel; + QTextEdit* logArea; + QSplitter* splitter; + QWidget* dock; + QTableView* props; + QListView* commands; + QPushButton* connectBtn; + QPushButton* launchBtn; + QPushButton* clearBtn; + QPushButton* removeBtn; + QPushButton* editBtn; + + bool connected; + bool launched; + bool propsShown; + bool commandsShown; + + AppModel* model; + +public slots: + void appendMessage(const QString& msg); + void clear(); + void setConnectable(bool value); + void setConnected(bool value); + void setLaunchable(bool value); + void setLaunched(bool value); + void setRemovable(bool value); + void setEditable(bool value); + void clearedLog(); + +signals: + void connect(uint64_t id); + void disconnect(uint64_t id); + void launch(uint64_t id); + void stop(uint64_t id); + void remove(uint64_t id); + void edit(uint64_t id); + void launchCommand(uint64_t id, const QString& name); + void clearLog(uint64_t id); + +private slots: + void onConnectClick(); + void onLaunchClick(); + void onClearClick(); + void onRemoveClick(); + void onEditClick(); + void checkDock(); + void onCommandDoubleClicked(const QModelIndex& index); +}; + +#endif // DETAILEDVIEW_H diff --git a/roboute/views/mainview.cpp b/roboute/views/mainview.cpp new file mode 100644 index 0000000..d13b187 --- /dev/null +++ b/roboute/views/mainview.cpp @@ -0,0 +1,59 @@ +#include "mainview.h" + +MainView::MainView(QAbstractListModel* model, QWidget* parent): + QWidget(parent), + splitter(new QSplitter(this)), + list(new QListView(this)), + details(new DetailedView(this)), + detailed(false) +{ + QGridLayout* layout = new QGridLayout(); + setLayout(layout); + + //AppListItemDelegate* dlg = new AppListItemDelegate(this); + //list->setItemDelegate(dlg); + list->setModel(model); + + layout->addWidget(splitter, 0, 0, 1, 1); + + splitter->addWidget(list); + splitter->addWidget(details); + details->hide(); +} + +void MainView::hideDetails() +{ + if (detailed) { + detailed = false; + details->hide(); + } +} + +void MainView::showDetails() +{ + if (!detailed) { + detailed = true; + details->show(); + } +} + +void MainView::saveSettings() +{ + QSettings settings; + settings.beginGroup("view"); + + settings.setValue("splitterState", splitter->saveState()); + + settings.endGroup(); + + details->saveSettings(); +} + +void MainView::readSettings() +{ + QSettings settings; + + splitter->restoreState(settings.value("view/splitterState").toByteArray()); + + details->readSettings(); +} diff --git a/roboute/views/mainview.h b/roboute/views/mainview.h new file mode 100644 index 0000000..bebbee0 --- /dev/null +++ b/roboute/views/mainview.h @@ -0,0 +1,40 @@ +#ifndef MAINVIEW_H +#define MAINVIEW_H + +#include +#include +#include +#include +#include +#include +#include + +//#include "applistitemdelegate.h" +#include "detailedview.h" + +class MainView : public QWidget +{ + Q_OBJECT + +public: + MainView(QAbstractListModel* model, QWidget* parent = 0); + +public: + QSplitter* splitter; + QListView* list; + DetailedView* details; + + void saveSettings(); + void readSettings(); + + +private: + bool detailed; + +public: + void showDetails(); + void hideDetails(); + +}; + +#endif // MAINVIEW_H diff --git a/roboute/views/newappdialogue.cpp b/roboute/views/newappdialogue.cpp new file mode 100644 index 0000000..4a5ad0f --- /dev/null +++ b/roboute/views/newappdialogue.cpp @@ -0,0 +1,114 @@ +#include "newappdialogue.h" + +NewAppDialogue::NewAppDialogue(QWidget* parent): + QDialog(parent), + name(new QLineEdit(this)), + address(new QLineEdit(this)), + port(new QLineEdit(this)), + login(new QLineEdit(this)), + pass(new QLineEdit(this)), + log(new QLineEdit(this)), + command(new QLineEdit(this)) +{ + construct(); +} + +NewAppDialogue::NewAppDialogue(const QMap& data, QWidget* parent): + QDialog(parent), + name(new QLineEdit(this)), + address(new QLineEdit(this)), + port(new QLineEdit(this)), + login(new QLineEdit(this)), + pass(new QLineEdit(this)), + log(new QLineEdit(this)), + command(new QLineEdit(this)) +{ + construct(); + + if (data.contains("name")) { + name->setText(data.value("name")); + } + + if (data.contains("address")) { + address->setText(data.value("address")); + } + + if (data.contains("port")) { + port->setText(data.value("port")); + } + + if (data.contains("login")) { + login->setText(data.value("login")); + } + + if (data.contains("password")) { + pass->setText(data.value("password")); + } + + if (data.contains("logFile")) { + log->setText(data.value("logFile")); + } + + if (data.contains("command")) { + command->setText(data.value("command")); + } +} + +void NewAppDialogue::construct() +{ + QVBoxLayout* mainLayout = new QVBoxLayout(this); + QHBoxLayout* buttonsLayout = new QHBoxLayout(); + QFormLayout* formLayout = new QFormLayout(); + + mainLayout->addLayout(formLayout); + mainLayout->addStretch(); + mainLayout->addLayout(buttonsLayout); + + setLayout(mainLayout); + + createButtons(buttonsLayout); + createForm(formLayout); +} + + +void NewAppDialogue::createButtons(QHBoxLayout* layout) +{ + layout->addStretch(); + + QPushButton* accept = new QPushButton(QIcon::fromTheme("dialog-ok"), tr("OK"), this); + QPushButton* reject = new QPushButton(QIcon::fromTheme("dialog-cancel"), tr("Cancel"), this); + + connect(accept, SIGNAL(clicked()), SLOT(accept())); + connect(reject, SIGNAL(clicked()), SLOT(reject())); + + layout->addWidget(accept); + layout->addWidget(reject); +} + +void NewAppDialogue::createForm(QFormLayout* layout) +{ + pass->setEchoMode(QLineEdit::Password); + + layout->addRow(tr("Name"), name); + layout->addRow(tr("Server address"), address); + layout->addRow(tr("Service port"), port); + layout->addRow(tr("ssh login"), login); + layout->addRow(tr("Password"), pass); + layout->addRow(tr("Log file"), log); + layout->addRow(tr("Command"), command); +} + +QMap NewAppDialogue::getData() const +{ + QMap map; + map.insert("name", name->text()); + map.insert("address", address->text()); + map.insert("port", port->text()); + map.insert("login", login->text()); + map.insert("password", pass->text()); + map.insert("logFile", log->text()); + map.insert("command", command->text()); + + return map; +} + diff --git a/roboute/views/newappdialogue.h b/roboute/views/newappdialogue.h new file mode 100644 index 0000000..6b9baa1 --- /dev/null +++ b/roboute/views/newappdialogue.h @@ -0,0 +1,37 @@ +#ifndef NEWAPPDIALOGUE_H +#define NEWAPPDIALOGUE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class NewAppDialogue : public QDialog +{ + Q_OBJECT +public: + NewAppDialogue(QWidget* parent = 0); + NewAppDialogue(const QMap& data, QWidget* parent = 0); + + QMap getData() const; + +private: + QLineEdit* name; + QLineEdit* address; + QLineEdit* port; + QLineEdit* login; + QLineEdit* pass; + QLineEdit* log; + QLineEdit* command; + +private: + void construct(); + void createButtons(QHBoxLayout* layout); + void createForm(QFormLayout* layout); +}; + +#endif // NEWAPPDIALOGUE_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..5fa9f00 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 2.8.12) +project(test) + +find_package(CxxTest) +if(CXXTEST_FOUND) + include_directories(${CXXTEST_INCLUDE_DIR}) + enable_testing() + CXXTEST_ADD_TEST(testtypes testtypes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/testTypes.h) + target_link_libraries(testtypes wType) + + CXXTEST_ADD_TEST(testtools testtools.cpp ${CMAKE_CURRENT_SOURCE_DIR}/testTools.h) + target_link_libraries(testtools tools) + target_link_libraries(testtools tag) + + find_package(Qt5Core REQUIRED) + + set(CMAKE_INCLUDE_CURRENT_DIR ON) + set(CMAKE_AUTOMOC ON) + CXXTEST_ADD_TEST(testsocket testsocket.cpp ${CMAKE_CURRENT_SOURCE_DIR}/testSocket.h) + target_link_libraries(testsocket Qt5::Core) + target_link_libraries(testsocket wSocket) + + CXXTEST_ADD_TEST(testdispatcher testdispatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/testDispatcher.h) + target_link_libraries(testdispatcher Qt5::Core) + target_link_libraries(testdispatcher wDispatcher) + target_link_libraries(testdispatcher wType) + +endif() + diff --git a/test/testDispatcher.h b/test/testDispatcher.h new file mode 100644 index 0000000..107d05c --- /dev/null +++ b/test/testDispatcher.h @@ -0,0 +1,92 @@ +#ifndef TESTDISPATCHER_H +#define TESTDISPATCHER_H + +#include + +#include +#include + +#include +#include +#include + +class TestObject: public QObject +{ + Q_OBJECT + +public: + TestObject(const W::Address& addr, W::Dispatcher* p_dp): + QObject(), + right_h(0), + wrong_h(0), + dp(p_dp) + { + right_h = W::Handler::create(addr + W::Address({u"right"}), this, &TestObject::right); + wrong_h = W::Handler::create(addr + W::Address({u"wrong"}), this, &TestObject::wrong); + + dp->registerHandler(right_h); + dp->registerHandler(wrong_h); + } + + ~TestObject() + { + dp->unregisterHandler(right_h); + dp->unregisterHandler(wrong_h); + + delete right_h; + delete wrong_h; + } + + void right(const W::Object& data) + { + emit success(); + } + + void wrong(const W::Object& data) + { + + } + + W::Handler* right_h; + W::Handler* wrong_h; + W::Dispatcher* dp; + +signals: + void success(); + +public slots: + void launch() + { + W::Event ev(W::Address({u"client", u"123", u"some_hop", u"main", u"right"}), W::String(u"hello!")); + dp->pass(ev); + } + +}; + +class TestDispatcher : public CxxTest::TestSuite +{ + +public: + void testEventPassing() + { + char a1[] = "nothing"; + char* argv[] = {a1}; + int argc = (int)(sizeof(argv) / sizeof(argv[0])) - 1; + QCoreApplication app (argc, argv); + + W::Dispatcher* root_dp = new W::Dispatcher(); + + TestObject *test_object = new TestObject(W::Address({u"client", u"123", u"some_hop", u"main"}), root_dp); + + QObject::connect(test_object, SIGNAL(success()), &app, SLOT(quit())); + + QTimer::singleShot(0, test_object, SLOT(launch())); + + app.exec(); + + delete test_object; + delete root_dp; + } +}; + +#endif //TESTDISPATCHER_H \ No newline at end of file diff --git a/test/testSocket.h b/test/testSocket.h new file mode 100644 index 0000000..aa593ec --- /dev/null +++ b/test/testSocket.h @@ -0,0 +1,98 @@ +#ifndef TESTSOCKET_H +#define TESTSOCKET_H + +#include + +#include + +#include +#include + +class TestServer : public QObject + { + Q_OBJECT + public: + TestServer(QObject* parent): + QObject(parent), + server(new W::Server(W::String(u"test_server"), this)) + { + connect(server, SIGNAL(newConnection(const W::Socket&)), SLOT(onNewConnection(const W::Socket&))); + server->listen(8080); + } + + private: + W::Server *server; + + signals: + void success(); + + private slots: + void onNewConnection(const W::Socket& socket) + { + connect(&socket, SIGNAL(message(const W::Event&)), SLOT(onSocketMessage(const W::Event&))); + } + + void onSocketMessage(const W::Event& event) + { + W::Socket* socket = static_cast(sender()); + W::Address addr({socket->getName()}); + + const W::String& msg = static_cast(event.getData()); + + TS_ASSERT_EQUALS(addr, event.getDestination()); + TS_ASSERT_EQUALS(msg, u"Hello, dear test server!"); + emit success(); + } + }; + + class TestClient : public QObject + { + Q_OBJECT + public: + TestClient(QObject* parent): + QObject(parent), + socket(new W::Socket(W::String(u"test_client"), this)) + { + connect(socket, SIGNAL(connected()), SLOT(onConnected())); + socket->open(W::String(u"localhost"), W::Uint64(8080)); + } + + private: + W::Socket *socket; + + private slots: + void onConnected() + { + W::Address addr({socket->getRemoteName()}); + W::String message(u"Hello, dear test server!"); + W::Event ev(addr, message); + + ev.setSenderId(socket->getId()); + + socket->send(ev); + } + }; + +class TestSocket : public CxxTest::TestSuite +{ + +public: + void testHandshake() + { + char a1[] = "nothing"; + char* argv[] = {a1}; + int argc = (int)(sizeof(argv) / sizeof(argv[0])) - 1; + QCoreApplication* app = new QCoreApplication(argc, argv); + + TestServer* srv = new TestServer(app); + TestClient* cli = new TestClient(app); + + QObject::connect(srv, SIGNAL(success()), app, SLOT(quit())); + + app->exec(); + + delete app; + } +}; + +#endif //TESTSOCKET_H diff --git a/test/testTools.h b/test/testTools.h new file mode 100644 index 0000000..8d5d166 --- /dev/null +++ b/test/testTools.h @@ -0,0 +1,26 @@ +#ifndef TESTUTILS_H +#define TESTUTILS_H + +#include + +#include +#include +#include +#include + +class TestUtils : public CxxTest::TestSuite +{ +public: + void testFile() { + TagLib::FileRef ref("/home/betrayer/Music/Disturbed/Indestructible/Façade.mp3"); + TS_ASSERT_EQUALS(ref.tag()->title().to8Bit(true), "Façade"); + + W::String wPath(u"/home/betrayer/Music/Disturbed/Indestructible/Façade.mp3"); + W::String wTitle(u"Façade"); + + TagLib::FileRef ref2(wPath.toString().c_str()); + TS_ASSERT_EQUALS(W::String(ref.tag()->title().to8Bit(true)), wTitle); + } +}; + +#endif //TESTUTILS_H diff --git a/test/testTypes.h b/test/testTypes.h new file mode 100644 index 0000000..17c5d31 --- /dev/null +++ b/test/testTypes.h @@ -0,0 +1,317 @@ +#ifndef TESTTYPES_H +#define TESTTYPES_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class TestTypes : public CxxTest::TestSuite +{ + +public: + void testStringSize() + { + W::String str(u"hey!"); + TS_ASSERT_EQUALS(str.length(), 4); + } + + void testStringToString() + { + W::String str(u"hello world!"); + TS_ASSERT_EQUALS(str.toString(), "hello world!"); + } + + void testStringToString2() + { + W::String str(u"Сраные стандарты стингов!"); + TS_ASSERT_EQUALS(str.toString(), "Сраные стандарты стингов!"); + } + void testStringCopying() + { + W::String str(u"string"); + W::String str2 = str; + } + + void testStringSerialization() + { + W::String str(u"serialization"); + int testSize = 13*2 + 4; //16 bits for each symbol and 32 bytes for length + int size = str.size(); + + TS_ASSERT_EQUALS(size, testSize); + + W::ByteArray bytes(size + 1); //one more byte for type + bytes.push8(str.getType()); + str.serialize(bytes); + + TS_ASSERT_EQUALS(bytes.size(), testSize + 1); + + W::Object *obj = W::Object::fromByteArray(bytes); + W::String *str2 = static_cast(obj); + + TS_ASSERT_EQUALS(bytes.size(), 0); + TS_ASSERT_EQUALS(str.toString(), "serialization"); + TS_ASSERT_EQUALS(str2->toString(), "serialization"); + + delete obj; + } + void testStringSerialization2() + { + W::String str(u"разве сложно сразу сделать все нормально?!"); + int testSize = 42*2 + 4; //16 bits for each symbol and 32 bytes for length + int size = str.size(); + + TS_ASSERT_EQUALS(size, testSize); + + W::ByteArray bytes(size + 1); //one more byte for type + bytes.push8(str.getType()); + str.serialize(bytes); + + TS_ASSERT_EQUALS(bytes.size(), testSize + 1); + + W::Object *obj = W::Object::fromByteArray(bytes); + W::String *str2 = static_cast(obj); + + TS_ASSERT_EQUALS(bytes.size(), 0); + TS_ASSERT_EQUALS(str.toString(), "разве сложно сразу сделать все нормально?!"); + TS_ASSERT_EQUALS(str2->toString(), "разве сложно сразу сделать все нормально?!"); + TS_ASSERT_EQUALS(str, *str2); + + delete obj; + } + void testUint64Serialization() + { + W::Uint64 a(895458745634); + W::ByteArray bytes(a.size() + 1); + + bytes.push8(a.getType()); + a.serialize(bytes); + + TS_ASSERT_EQUALS(bytes.size(), 1 + 8); + + W::Object *obj = W::Object::fromByteArray(bytes); + W::Uint64 *b = static_cast(obj); + + TS_ASSERT_EQUALS(a, *b); + delete obj; + } + void testVCSerialization() + { + W::String key1(u"foo"); + W::String val1(u"bar"); + + W::Vocabulary vc; + vc.insert(key1, val1); + + W::ByteArray bytes(vc.size() + 1); + TS_ASSERT_EQUALS(vc.length(), 1); + + TS_TRACE(vc.toString()); + + bytes.push8(vc.getType()); + vc.serialize(bytes); + + W::Object* deserialized = W::Object::fromByteArray(bytes); + W::Vocabulary* dvc = static_cast(deserialized); + + TS_ASSERT_EQUALS(vc.length(), dvc->length()); + W::String val2 = static_cast(dvc->at(key1)); + TS_ASSERT_EQUALS(val2, val1); + + delete deserialized; + } + void testVectorSerialization() + { + W::String str1(u"foo"); + W::String str2(u"bar"); + + W::Vector vec; + + vec.push(str1); + vec.push(str2); + vec.push(str2); + vec.push(str2); + vec.push(str1); + + W::ByteArray bytes(vec.size() + 1); + + TS_ASSERT_EQUALS(vec.length(), 5); + TS_TRACE(vec.toString()); + + bytes.push8(vec.getType()); + vec.serialize(bytes); + + W::Object* deserialized = W::Object::fromByteArray(bytes); + W::Vector* dvec = static_cast(deserialized); + + TS_ASSERT_EQUALS(vec.length(), dvec->length()); + W::String str22 = static_cast(dvec->at(3)); + + TS_ASSERT_EQUALS(str2, str22); + + delete deserialized; + } + void testAddressOperators() + { + W::Address a1({u"hey"}); + W::Address a2({u"hey", u"you"}); + W::Address a3({u"hey1", u"you"}); + W::Address a4({u"hey", u"you1"}); + + TS_ASSERT_EQUALS(a1, a1); + TS_ASSERT_DIFFERS(a1, a2); + TS_ASSERT_LESS_THAN(a1, a2); + TS_ASSERT_LESS_THAN(a2, a3); + TS_ASSERT_LESS_THAN(a2, a4); + TS_ASSERT_LESS_THAN(a4, a3); + } + void testAddressFunctions() + { + W::Address a1({u"1st", u"2nd", u"3rd", u"4th"}); + W::Address a2 = a1 >> 1; + W::Address a3 = a1 << 1; + + W::Address ae; + W::Address a4({u"1st"}); + W::Address a5({u"1st", u"2nd"}); + + W::Address a6({u"1st", u"3rd"}); + + W::Address a7({u"3rd", u"4th"}); + W::Address a8({u"4th"}); + + W::Address a2c({u"1st", u"2nd", u"3rd"}); + W::Address a3c({u"2nd", u"3rd", u"4th"}); + + TS_ASSERT_EQUALS(a2, a2c); + TS_ASSERT_EQUALS(a3, a3c); + + TS_ASSERT(a4.begins(ae)); + TS_ASSERT(a4.ends(ae)); + + TS_ASSERT(a1.begins(ae)); + TS_ASSERT(a1.ends(ae)); + + TS_ASSERT(a1.begins(a4)); + TS_ASSERT(a1.begins(a5)); + TS_ASSERT(!a1.begins(a6)); + + TS_ASSERT(a1.ends(a7)); + TS_ASSERT(a1.ends(a8)); + TS_ASSERT(!a1.ends(a6)); + + TS_ASSERT(a1.begins(a2c)); + TS_ASSERT(a1.ends(a3c)); + } + void testAddressSerialization() + { + W::Address addr({u"hello", u"world"}); + + W::ByteArray bytes(addr.size() + 1); + + bytes.push8(addr.getType()); + addr.serialize(bytes); + + W::Object *obj = W::Object::fromByteArray(bytes); + W::Address *addrd = static_cast(obj); + + TS_ASSERT_EQUALS(addr, *addrd); + TS_TRACE(addr.toString()); + TS_TRACE(addrd->toString()); + + delete addrd; + } + void testBooleanSerialization() + { + W::Boolean a(true); + W::Boolean b(false); + W::Boolean c; + W::Boolean d; + c = false; + d = true; + + TS_ASSERT_EQUALS(a, true); + TS_ASSERT_EQUALS(b, false); + TS_ASSERT_EQUALS(c, false); + TS_ASSERT_EQUALS(d, true); + + W::ByteArray bytes(a.size() + b.size() + c.size() + d.size() + 4); + + bytes.push8(a.getType()); + a.serialize(bytes); + + bytes.push8(b.getType()); + b.serialize(bytes); + + bytes.push8(c.getType()); + c.serialize(bytes); + + bytes.push8(d.getType()); + d.serialize(bytes); + + W::Object *a_o = W::Object::fromByteArray(bytes); + W::Object *b_o = W::Object::fromByteArray(bytes); + W::Object *c_o = W::Object::fromByteArray(bytes); + W::Object *d_o = W::Object::fromByteArray(bytes); + + W::Boolean *ad = static_cast(a_o); + W::Boolean *bd = static_cast(b_o); + W::Boolean *cd = static_cast(c_o); + W::Boolean *dd = static_cast(d_o); + + TS_ASSERT_EQUALS(*ad, a); + TS_ASSERT_EQUALS(*bd, b); + TS_ASSERT_EQUALS(*cd, c); + TS_ASSERT_EQUALS(*dd, d); + + TS_TRACE(ad->toString()); + TS_TRACE(bd->toString()); + + delete ad; + delete bd; + delete cd; + delete dd; + } + void testEventSerialization() + { + W::Address dest({u"to", u"somebody"}); + W::Uint64 id(5); + W::Vocabulary dat; + W::String val(u"some value"); + W::Uint64 val2(7887198479813); + dat.insert(u"key1", val); + dat.insert(u"key2", val2); + + W::Event ev(dest, dat); + ev.setSenderId(id); + + W::ByteArray bytes(ev.size() + 1); + + bytes.push8(ev.getType()); + ev.serialize(bytes); + + W::Object *obj = W::Object::fromByteArray(bytes); + W::Event *evd = static_cast(obj); + + TS_ASSERT_EQUALS(evd->isSystem(), false); + TS_ASSERT_EQUALS(evd->getDestination(), dest); + const W::Vocabulary vcd = static_cast(evd->getData()); + TS_ASSERT_EQUALS(static_cast(vcd.at(u"key1")), val); + TS_ASSERT_EQUALS(static_cast(vcd.at(u"key2")), val2); + TS_ASSERT_EQUALS(evd->getSenderId(), id); + + delete obj; + } +}; + + +#endif