From 4b60ece582da19b34e3d45535d14327ab087fbeb Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 5 Aug 2018 00:46:25 +0300 Subject: [PATCH] initial commit --- CMakeLists.txt | 34 + README.md | 34 + corax/CMakeLists.txt | 40 + corax/corax.cpp | 236 +++ corax/corax.h | 88 ++ corax/main.cpp | 18 + corax/tools/audioid.cpp | 61 + corax/tools/audioid.h | 28 + corax/tools/audiotag.cpp | 35 + corax/tools/audiotag.h | 29 + corax/tools/parser.cpp | 309 ++++ corax/tools/parser.h | 71 + lib/CMakeLists.txt | 15 + lib/fontParser/CMakeLists.txt | 17 + lib/fontParser/font.cpp | 223 +++ lib/fontParser/font.h | 62 + lib/fontParser/main.cpp | 50 + lib/fontParser/tables/CMakeLists.txt | 13 + lib/fontParser/tables/cmap.cpp | 218 +++ lib/fontParser/tables/cmap.h | 66 + lib/fontParser/tables/head.cpp | 93 ++ lib/fontParser/tables/head.h | 28 + lib/fontParser/tables/hhea.cpp | 76 + lib/fontParser/tables/hhea.h | 27 + lib/fontParser/tables/hmtx.cpp | 59 + lib/fontParser/tables/hmtx.h | 29 + lib/fontParser/tables/name.cpp | 136 ++ lib/fontParser/tables/name.h | 35 + lib/fontParser/tables/table.cpp | 56 + lib/fontParser/tables/table.h | 26 + lib/tools/CMakeLists.txt | 15 + lib/tools/file.cpp | 115 ++ lib/tools/file.h | 33 + lib/utils/CMakeLists.txt | 22 + lib/utils/defines.h | 9 + lib/utils/exception.cpp | 14 + lib/utils/exception.h | 22 + lib/utils/signalcatcher.cpp | 59 + lib/utils/signalcatcher.h | 33 + lib/wContainer/CMakeLists.txt | 2 + lib/wContainer/order.h | 136 ++ lib/wController/CMakeLists.txt | 35 + lib/wController/attributes.cpp | 87 ++ lib/wController/attributes.h | 43 + lib/wController/catalogue.cpp | 220 +++ lib/wController/catalogue.h | 63 + lib/wController/collection.cpp | 136 ++ lib/wController/collection.h | 52 + lib/wController/controller.cpp | 278 ++++ lib/wController/controller.h | 82 + lib/wController/controllerstring.cpp | 25 + lib/wController/controllerstring.h | 33 + lib/wController/list.cpp | 57 + lib/wController/list.h | 36 + lib/wController/vocabulary.cpp | 119 ++ lib/wController/vocabulary.h | 49 + lib/wDatabase/CMakeLists.txt | 25 + lib/wDatabase/database.cpp | 230 +++ lib/wDatabase/database.h | 56 + lib/wDatabase/lmdb++.h | 1913 ++++++++++++++++++++++++ lib/wDatabase/resourcecache.cpp | 277 ++++ lib/wDatabase/resourcecache.h | 84 ++ lib/wDispatcher/CMakeLists.txt | 27 + lib/wDispatcher/defaulthandler.cpp | 11 + lib/wDispatcher/defaulthandler.h | 18 + lib/wDispatcher/dispatcher.cpp | 84 ++ lib/wDispatcher/dispatcher.h | 48 + lib/wDispatcher/handler.cpp | 17 + lib/wDispatcher/handler.h | 56 + lib/wDispatcher/logger.cpp | 24 + lib/wDispatcher/logger.h | 21 + lib/wDispatcher/parentreporter.cpp | 44 + lib/wDispatcher/parentreporter.h | 25 + lib/wModel/CMakeLists.txt | 36 + lib/wModel/attributes.cpp | 60 + lib/wModel/attributes.h | 32 + lib/wModel/catalogue.cpp | 66 + lib/wModel/catalogue.h | 29 + lib/wModel/file/file.cpp | 86 ++ lib/wModel/file/file.h | 44 + lib/wModel/icatalogue.cpp | 469 ++++++ lib/wModel/icatalogue.h | 316 ++++ lib/wModel/list.cpp | 100 ++ lib/wModel/list.h | 41 + lib/wModel/model.cpp | 339 +++++ lib/wModel/model.h | 92 ++ lib/wModel/modelstring.cpp | 77 + lib/wModel/modelstring.h | 40 + lib/wModel/vocabulary.cpp | 136 ++ lib/wModel/vocabulary.h | 45 + lib/wServerUtils/CMakeLists.txt | 26 + lib/wServerUtils/commands.cpp | 85 ++ lib/wServerUtils/commands.h | 43 + lib/wServerUtils/connector.cpp | 124 ++ lib/wServerUtils/connector.h | 68 + lib/wSocket/CMakeLists.txt | 27 + lib/wSocket/server.cpp | 158 ++ lib/wSocket/server.h | 79 + lib/wSocket/socket.cpp | 256 ++++ lib/wSocket/socket.h | 108 ++ lib/wSsh/CMakeLists.txt | 23 + lib/wSsh/qsshsocket.cpp | 186 +++ lib/wSsh/qsshsocket.h | 81 + lib/wSsh/sshsocket.cpp | 195 +++ lib/wSsh/sshsocket.h | 69 + lib/wType/CMakeLists.txt | 31 + lib/wType/address.cpp | 326 ++++ lib/wType/address.h | 70 + lib/wType/blob.cpp | 146 ++ lib/wType/blob.h | 46 + lib/wType/boolean.cpp | 146 ++ lib/wType/boolean.h | 49 + lib/wType/bytearray.cpp | 167 +++ lib/wType/bytearray.h | 49 + lib/wType/event.cpp | 179 +++ lib/wType/event.h | 53 + lib/wType/object.cpp | 106 ++ lib/wType/object.h | 55 + lib/wType/string.cpp | 208 +++ lib/wType/string.h | 65 + lib/wType/uint64.cpp | 114 ++ lib/wType/uint64.h | 49 + lib/wType/vector.cpp | 191 +++ lib/wType/vector.h | 60 + lib/wType/vocabulary.cpp | 296 ++++ lib/wType/vocabulary.h | 72 + libjs/CMakeLists.txt | 0 libjs/polymorph/CMakeLists.txt | 0 libjs/polymorph/dependency.js | 76 + libjs/polymorph/depresolver.js | 51 + libjs/polymorph/index.js | 75 + libjs/polymorph/pathCheck.js | 34 + libjs/utils/CMakeLists.txt | 5 + libjs/utils/class.js | 52 + libjs/utils/globalMethods.js | 69 + libjs/utils/subscribable.js | 94 ++ libjs/wContainer/CMakeLists.txt | 7 + libjs/wContainer/abstractlist.js | 204 +++ libjs/wContainer/abstractmap.js | 142 ++ libjs/wContainer/abstractorder.js | 104 ++ libjs/wContainer/abstractpair.js | 111 ++ libjs/wContainer/abstractset.js | 143 ++ libjs/wController/CMakeLists.txt | 19 + libjs/wController/attributes.js | 1 + libjs/wController/catalogue.js | 141 ++ libjs/wController/controller.js | 382 +++++ libjs/wController/file/file.js | 86 ++ libjs/wController/globalControls.js | 60 + libjs/wController/image.js | 69 + libjs/wController/imagePane.js | 40 + libjs/wController/link.js | 38 + libjs/wController/list.js | 51 + libjs/wController/localModel.js | 28 + libjs/wController/navigationPanel.js | 24 + libjs/wController/page.js | 120 ++ libjs/wController/pageStorage.js | 38 + libjs/wController/panesList.js | 80 + libjs/wController/string.js | 22 + libjs/wController/theme.js | 31 + libjs/wController/themeSelecter.js | 56 + libjs/wController/vocabulary.js | 69 + libjs/wDispatcher/CMakeLists.txt | 7 + libjs/wDispatcher/defaulthandler.js | 35 + libjs/wDispatcher/dispatcher.js | 91 ++ libjs/wDispatcher/handler.js | 44 + libjs/wDispatcher/logger.js | 18 + libjs/wDispatcher/parentreporter.js | 55 + libjs/wModel/CMakeLists.txt | 17 + libjs/wModel/attributes.js | 44 + libjs/wModel/globalControls.js | 60 + libjs/wModel/image.js | 47 + libjs/wModel/link.js | 44 + libjs/wModel/list.js | 51 + libjs/wModel/model.js | 316 ++++ libjs/wModel/page.js | 163 ++ libjs/wModel/pageStorage.js | 124 ++ libjs/wModel/panesList.js | 21 + libjs/wModel/proxy/CMakeLists.txt | 6 + libjs/wModel/proxy/catalogue.js | 54 + libjs/wModel/proxy/list.js | 46 + libjs/wModel/proxy/proxy.js | 180 +++ libjs/wModel/proxy/vocabulary.js | 43 + libjs/wModel/string.js | 47 + libjs/wModel/theme.js | 70 + libjs/wModel/themeStorage.js | 54 + libjs/wModel/vocabulary.js | 79 + libjs/wTest/CMakeLists.txt | 0 libjs/wTest/abstractlist.js | 136 ++ libjs/wTest/abstractmap.js | 78 + libjs/wTest/abstractorder.js | 93 ++ libjs/wTest/test.js | 29 + libjs/wTest/uint64.js | 40 + libjs/wType/CMakeLists.txt | 12 + libjs/wType/address.js | 228 +++ libjs/wType/blob.js | 65 + libjs/wType/boolean.js | 62 + libjs/wType/bytearray.js | 106 ++ libjs/wType/event.js | 121 ++ libjs/wType/factory.js | 42 + libjs/wType/object.js | 70 + libjs/wType/string.js | 84 ++ libjs/wType/uint64.js | 95 ++ libjs/wType/vector.js | 112 ++ libjs/wType/vocabulary.js | 155 ++ lorgar/CMakeLists.txt | 12 + lorgar/core/CMakeLists.txt | 3 + lorgar/core/lorgar.js | 289 ++++ lorgar/css/CMakeLists.txt | 4 + lorgar/css/Liberation.ttf | Bin 0 -> 350200 bytes lorgar/css/main.css | 47 + lorgar/favicon.ico | Bin 0 -> 1150 bytes lorgar/index.html | 13 + lorgar/lib/CMakeLists.txt | 12 + lorgar/lib/bintrees/CMakeLists.txt | 3 + lorgar/lib/bintrees/index.js | 478 ++++++ lorgar/lib/fonts/CMakeLists.txt | 3 + lorgar/lib/fonts/Liberation.js | 94 ++ lorgar/lib/requirejs/CMakeLists.txt | 3 + lorgar/lib/requirejs/require.js | 37 + lorgar/lib/utils/CMakeLists.txt | 5 + lorgar/lib/wContainer/CMakeLists.txt | 6 + lorgar/lib/wController/CMakeLists.txt | 19 + lorgar/lib/wDispatcher/CMakeLists.txt | 6 + lorgar/lib/wSocket/CMakeLists.txt | 3 + lorgar/lib/wSocket/socket.js | 203 +++ lorgar/lib/wTest/CMakeLists.txt | 6 + lorgar/lib/wType/CMakeLists.txt | 13 + lorgar/main.js | 47 + lorgar/test/CMakeLists.txt | 3 + lorgar/test/test.js | 73 + lorgar/views/CMakeLists.txt | 15 + lorgar/views/gridLayout.js | 436 ++++++ lorgar/views/helpers/CMakeLists.txt | 4 + lorgar/views/helpers/draggable.js | 148 ++ lorgar/views/helpers/scrollable.js | 222 +++ lorgar/views/image.js | 35 + lorgar/views/label.js | 88 ++ lorgar/views/layout.js | 242 +++ lorgar/views/mainLayout.js | 51 + lorgar/views/nav.js | 58 + lorgar/views/navigationPanel.js | 50 + lorgar/views/page.js | 90 ++ lorgar/views/pane.js | 109 ++ lorgar/views/panesList.js | 189 +++ lorgar/views/view.js | 320 ++++ magnus/CMakeLists.txt | 15 + magnus/app.js | 46 + magnus/config/CMakeLists.txt | 4 + magnus/config/config.json | 9 + magnus/config/index.js | 8 + magnus/core/CMakeLists.txt | 5 + magnus/core/commands.js | 85 ++ magnus/core/connector.js | 120 ++ magnus/core/magnus.js | 153 ++ magnus/lib/CMakeLists.txt | 13 + magnus/lib/httpError.js | 15 + magnus/lib/log.js | 18 + magnus/lib/utils/CMakeLists.txt | 5 + magnus/lib/wContainer/CMakeLists.txt | 7 + magnus/lib/wController/CMakeLists.txt | 6 + magnus/lib/wDispatcher/CMakeLists.txt | 7 + magnus/lib/wModel/CMakeLists.txt | 17 + magnus/lib/wModel/proxy/CMakeLists.txt | 8 + magnus/lib/wModel/proxy/pane.js | 32 + magnus/lib/wSocket/CMakeLists.txt | 4 + magnus/lib/wSocket/server.js | 154 ++ magnus/lib/wSocket/socket.js | 217 +++ magnus/lib/wTest/CMakeLists.txt | 7 + magnus/lib/wType/CMakeLists.txt | 13 + magnus/middleware/CMakeLists.txt | 6 + magnus/middleware/errorHandler.js | 36 + magnus/middleware/notFound.js | 6 + magnus/middleware/pageInMagnus.js | 9 + magnus/middleware/reply.js | 8 + magnus/package.json | 20 + magnus/pages/CMakeLists.txt | 10 + magnus/pages/album.js | 144 ++ magnus/pages/artist.js | 112 ++ magnus/pages/home.js | 19 + magnus/pages/list.js | 115 ++ magnus/pages/music.js | 198 +++ magnus/pages/song.js | 151 ++ magnus/pages/tempPage.js | 46 + magnus/pages/test.js | 19 + magnus/test/CMakeLists.txt | 3 + magnus/test/test.js | 64 + magnus/views/CMakeLists.txt | 3 + magnus/views/index.jade | 15 + perturabo/CMakeLists.txt | 31 + perturabo/main.cpp | 18 + perturabo/perturabo.cpp | 189 +++ perturabo/perturabo.h | 84 ++ roboute/CMakeLists.txt | 37 + roboute/applistitemdelegate.cpp | 80 + roboute/applistitemdelegate.h | 17 + roboute/main.cpp | 64 + roboute/mainwindow.cpp | 328 ++++ roboute/mainwindow.h | 86 ++ roboute/models/CMakeLists.txt | 31 + roboute/models/appcommandsmodel.cpp | 110 ++ roboute/models/appcommandsmodel.h | 40 + roboute/models/applistmodel.cpp | 187 +++ roboute/models/applistmodel.h | 58 + roboute/models/appmodel.cpp | 119 ++ roboute/models/appmodel.h | 61 + roboute/models/apppropertiesmodel.cpp | 143 ++ roboute/models/apppropertiesmodel.h | 51 + roboute/models/commands.cpp | 10 + roboute/models/commands.h | 13 + roboute/models/service.cpp | 489 ++++++ roboute/models/service.h | 131 ++ roboute/roboute.cpp | 295 ++++ roboute/roboute.h | 115 ++ roboute/views/CMakeLists.txt | 27 + roboute/views/commandform.cpp | 104 ++ roboute/views/commandform.h | 31 + roboute/views/detailedview.cpp | 321 ++++ roboute/views/detailedview.h | 86 ++ roboute/views/mainview.cpp | 59 + roboute/views/mainview.h | 40 + roboute/views/newappdialogue.cpp | 114 ++ roboute/views/newappdialogue.h | 37 + test/CMakeLists.txt | 29 + test/testDispatcher.h | 92 ++ test/testSocket.h | 98 ++ test/testTools.h | 26 + test/testTypes.h | 317 ++++ 327 files changed, 28286 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 corax/CMakeLists.txt create mode 100644 corax/corax.cpp create mode 100644 corax/corax.h create mode 100644 corax/main.cpp create mode 100644 corax/tools/audioid.cpp create mode 100644 corax/tools/audioid.h create mode 100644 corax/tools/audiotag.cpp create mode 100644 corax/tools/audiotag.h create mode 100644 corax/tools/parser.cpp create mode 100644 corax/tools/parser.h create mode 100644 lib/CMakeLists.txt create mode 100644 lib/fontParser/CMakeLists.txt create mode 100644 lib/fontParser/font.cpp create mode 100644 lib/fontParser/font.h create mode 100644 lib/fontParser/main.cpp create mode 100644 lib/fontParser/tables/CMakeLists.txt create mode 100644 lib/fontParser/tables/cmap.cpp create mode 100644 lib/fontParser/tables/cmap.h create mode 100644 lib/fontParser/tables/head.cpp create mode 100644 lib/fontParser/tables/head.h create mode 100644 lib/fontParser/tables/hhea.cpp create mode 100644 lib/fontParser/tables/hhea.h create mode 100644 lib/fontParser/tables/hmtx.cpp create mode 100644 lib/fontParser/tables/hmtx.h create mode 100644 lib/fontParser/tables/name.cpp create mode 100644 lib/fontParser/tables/name.h create mode 100644 lib/fontParser/tables/table.cpp create mode 100644 lib/fontParser/tables/table.h create mode 100644 lib/tools/CMakeLists.txt create mode 100644 lib/tools/file.cpp create mode 100644 lib/tools/file.h create mode 100644 lib/utils/CMakeLists.txt create mode 100644 lib/utils/defines.h create mode 100644 lib/utils/exception.cpp create mode 100644 lib/utils/exception.h create mode 100644 lib/utils/signalcatcher.cpp create mode 100644 lib/utils/signalcatcher.h create mode 100644 lib/wContainer/CMakeLists.txt create mode 100644 lib/wContainer/order.h create mode 100644 lib/wController/CMakeLists.txt create mode 100644 lib/wController/attributes.cpp create mode 100644 lib/wController/attributes.h create mode 100644 lib/wController/catalogue.cpp create mode 100644 lib/wController/catalogue.h create mode 100644 lib/wController/collection.cpp create mode 100644 lib/wController/collection.h create mode 100644 lib/wController/controller.cpp create mode 100644 lib/wController/controller.h create mode 100644 lib/wController/controllerstring.cpp create mode 100644 lib/wController/controllerstring.h create mode 100644 lib/wController/list.cpp create mode 100644 lib/wController/list.h create mode 100644 lib/wController/vocabulary.cpp create mode 100644 lib/wController/vocabulary.h create mode 100644 lib/wDatabase/CMakeLists.txt create mode 100644 lib/wDatabase/database.cpp create mode 100644 lib/wDatabase/database.h create mode 100644 lib/wDatabase/lmdb++.h create mode 100644 lib/wDatabase/resourcecache.cpp create mode 100644 lib/wDatabase/resourcecache.h create mode 100644 lib/wDispatcher/CMakeLists.txt create mode 100644 lib/wDispatcher/defaulthandler.cpp create mode 100644 lib/wDispatcher/defaulthandler.h create mode 100644 lib/wDispatcher/dispatcher.cpp create mode 100644 lib/wDispatcher/dispatcher.h create mode 100644 lib/wDispatcher/handler.cpp create mode 100644 lib/wDispatcher/handler.h create mode 100644 lib/wDispatcher/logger.cpp create mode 100644 lib/wDispatcher/logger.h create mode 100644 lib/wDispatcher/parentreporter.cpp create mode 100644 lib/wDispatcher/parentreporter.h create mode 100644 lib/wModel/CMakeLists.txt create mode 100644 lib/wModel/attributes.cpp create mode 100644 lib/wModel/attributes.h create mode 100644 lib/wModel/catalogue.cpp create mode 100644 lib/wModel/catalogue.h create mode 100644 lib/wModel/file/file.cpp create mode 100644 lib/wModel/file/file.h create mode 100644 lib/wModel/icatalogue.cpp create mode 100644 lib/wModel/icatalogue.h create mode 100644 lib/wModel/list.cpp create mode 100644 lib/wModel/list.h create mode 100644 lib/wModel/model.cpp create mode 100644 lib/wModel/model.h create mode 100644 lib/wModel/modelstring.cpp create mode 100644 lib/wModel/modelstring.h create mode 100644 lib/wModel/vocabulary.cpp create mode 100644 lib/wModel/vocabulary.h create mode 100644 lib/wServerUtils/CMakeLists.txt create mode 100644 lib/wServerUtils/commands.cpp create mode 100644 lib/wServerUtils/commands.h create mode 100644 lib/wServerUtils/connector.cpp create mode 100644 lib/wServerUtils/connector.h create mode 100644 lib/wSocket/CMakeLists.txt create mode 100644 lib/wSocket/server.cpp create mode 100644 lib/wSocket/server.h create mode 100644 lib/wSocket/socket.cpp create mode 100644 lib/wSocket/socket.h create mode 100644 lib/wSsh/CMakeLists.txt create mode 100644 lib/wSsh/qsshsocket.cpp create mode 100644 lib/wSsh/qsshsocket.h create mode 100644 lib/wSsh/sshsocket.cpp create mode 100644 lib/wSsh/sshsocket.h create mode 100644 lib/wType/CMakeLists.txt create mode 100644 lib/wType/address.cpp create mode 100644 lib/wType/address.h create mode 100644 lib/wType/blob.cpp create mode 100644 lib/wType/blob.h create mode 100644 lib/wType/boolean.cpp create mode 100644 lib/wType/boolean.h create mode 100644 lib/wType/bytearray.cpp create mode 100644 lib/wType/bytearray.h create mode 100644 lib/wType/event.cpp create mode 100644 lib/wType/event.h create mode 100644 lib/wType/object.cpp create mode 100644 lib/wType/object.h create mode 100644 lib/wType/string.cpp create mode 100644 lib/wType/string.h create mode 100644 lib/wType/uint64.cpp create mode 100644 lib/wType/uint64.h create mode 100644 lib/wType/vector.cpp create mode 100644 lib/wType/vector.h create mode 100644 lib/wType/vocabulary.cpp create mode 100644 lib/wType/vocabulary.h create mode 100644 libjs/CMakeLists.txt create mode 100644 libjs/polymorph/CMakeLists.txt create mode 100644 libjs/polymorph/dependency.js create mode 100644 libjs/polymorph/depresolver.js create mode 100644 libjs/polymorph/index.js create mode 100644 libjs/polymorph/pathCheck.js create mode 100644 libjs/utils/CMakeLists.txt create mode 100644 libjs/utils/class.js create mode 100644 libjs/utils/globalMethods.js create mode 100644 libjs/utils/subscribable.js create mode 100644 libjs/wContainer/CMakeLists.txt create mode 100644 libjs/wContainer/abstractlist.js create mode 100644 libjs/wContainer/abstractmap.js create mode 100644 libjs/wContainer/abstractorder.js create mode 100644 libjs/wContainer/abstractpair.js create mode 100644 libjs/wContainer/abstractset.js create mode 100644 libjs/wController/CMakeLists.txt create mode 100644 libjs/wController/attributes.js create mode 100644 libjs/wController/catalogue.js create mode 100644 libjs/wController/controller.js create mode 100644 libjs/wController/file/file.js create mode 100644 libjs/wController/globalControls.js create mode 100644 libjs/wController/image.js create mode 100644 libjs/wController/imagePane.js create mode 100644 libjs/wController/link.js create mode 100644 libjs/wController/list.js create mode 100644 libjs/wController/localModel.js create mode 100644 libjs/wController/navigationPanel.js create mode 100644 libjs/wController/page.js create mode 100644 libjs/wController/pageStorage.js create mode 100644 libjs/wController/panesList.js create mode 100644 libjs/wController/string.js create mode 100644 libjs/wController/theme.js create mode 100644 libjs/wController/themeSelecter.js create mode 100644 libjs/wController/vocabulary.js create mode 100644 libjs/wDispatcher/CMakeLists.txt create mode 100644 libjs/wDispatcher/defaulthandler.js create mode 100644 libjs/wDispatcher/dispatcher.js create mode 100644 libjs/wDispatcher/handler.js create mode 100644 libjs/wDispatcher/logger.js create mode 100644 libjs/wDispatcher/parentreporter.js create mode 100644 libjs/wModel/CMakeLists.txt create mode 100644 libjs/wModel/attributes.js create mode 100644 libjs/wModel/globalControls.js create mode 100644 libjs/wModel/image.js create mode 100644 libjs/wModel/link.js create mode 100644 libjs/wModel/list.js create mode 100644 libjs/wModel/model.js create mode 100644 libjs/wModel/page.js create mode 100644 libjs/wModel/pageStorage.js create mode 100644 libjs/wModel/panesList.js create mode 100644 libjs/wModel/proxy/CMakeLists.txt create mode 100644 libjs/wModel/proxy/catalogue.js create mode 100644 libjs/wModel/proxy/list.js create mode 100644 libjs/wModel/proxy/proxy.js create mode 100644 libjs/wModel/proxy/vocabulary.js create mode 100644 libjs/wModel/string.js create mode 100644 libjs/wModel/theme.js create mode 100644 libjs/wModel/themeStorage.js create mode 100644 libjs/wModel/vocabulary.js create mode 100644 libjs/wTest/CMakeLists.txt create mode 100644 libjs/wTest/abstractlist.js create mode 100644 libjs/wTest/abstractmap.js create mode 100644 libjs/wTest/abstractorder.js create mode 100644 libjs/wTest/test.js create mode 100644 libjs/wTest/uint64.js create mode 100644 libjs/wType/CMakeLists.txt create mode 100644 libjs/wType/address.js create mode 100644 libjs/wType/blob.js create mode 100644 libjs/wType/boolean.js create mode 100644 libjs/wType/bytearray.js create mode 100644 libjs/wType/event.js create mode 100644 libjs/wType/factory.js create mode 100644 libjs/wType/object.js create mode 100644 libjs/wType/string.js create mode 100644 libjs/wType/uint64.js create mode 100644 libjs/wType/vector.js create mode 100644 libjs/wType/vocabulary.js create mode 100644 lorgar/CMakeLists.txt create mode 100644 lorgar/core/CMakeLists.txt create mode 100644 lorgar/core/lorgar.js create mode 100644 lorgar/css/CMakeLists.txt create mode 100644 lorgar/css/Liberation.ttf create mode 100644 lorgar/css/main.css create mode 100644 lorgar/favicon.ico create mode 100644 lorgar/index.html create mode 100644 lorgar/lib/CMakeLists.txt create mode 100644 lorgar/lib/bintrees/CMakeLists.txt create mode 100644 lorgar/lib/bintrees/index.js create mode 100644 lorgar/lib/fonts/CMakeLists.txt create mode 100644 lorgar/lib/fonts/Liberation.js create mode 100644 lorgar/lib/requirejs/CMakeLists.txt create mode 100644 lorgar/lib/requirejs/require.js create mode 100644 lorgar/lib/utils/CMakeLists.txt create mode 100644 lorgar/lib/wContainer/CMakeLists.txt create mode 100644 lorgar/lib/wController/CMakeLists.txt create mode 100644 lorgar/lib/wDispatcher/CMakeLists.txt create mode 100644 lorgar/lib/wSocket/CMakeLists.txt create mode 100644 lorgar/lib/wSocket/socket.js create mode 100644 lorgar/lib/wTest/CMakeLists.txt create mode 100644 lorgar/lib/wType/CMakeLists.txt create mode 100644 lorgar/main.js create mode 100644 lorgar/test/CMakeLists.txt create mode 100644 lorgar/test/test.js create mode 100644 lorgar/views/CMakeLists.txt create mode 100644 lorgar/views/gridLayout.js create mode 100644 lorgar/views/helpers/CMakeLists.txt create mode 100644 lorgar/views/helpers/draggable.js create mode 100644 lorgar/views/helpers/scrollable.js create mode 100644 lorgar/views/image.js create mode 100644 lorgar/views/label.js create mode 100644 lorgar/views/layout.js create mode 100644 lorgar/views/mainLayout.js create mode 100644 lorgar/views/nav.js create mode 100644 lorgar/views/navigationPanel.js create mode 100644 lorgar/views/page.js create mode 100644 lorgar/views/pane.js create mode 100644 lorgar/views/panesList.js create mode 100644 lorgar/views/view.js create mode 100644 magnus/CMakeLists.txt create mode 100644 magnus/app.js create mode 100644 magnus/config/CMakeLists.txt create mode 100644 magnus/config/config.json create mode 100644 magnus/config/index.js create mode 100644 magnus/core/CMakeLists.txt create mode 100644 magnus/core/commands.js create mode 100644 magnus/core/connector.js create mode 100644 magnus/core/magnus.js create mode 100644 magnus/lib/CMakeLists.txt create mode 100644 magnus/lib/httpError.js create mode 100644 magnus/lib/log.js create mode 100644 magnus/lib/utils/CMakeLists.txt create mode 100644 magnus/lib/wContainer/CMakeLists.txt create mode 100644 magnus/lib/wController/CMakeLists.txt create mode 100644 magnus/lib/wDispatcher/CMakeLists.txt create mode 100644 magnus/lib/wModel/CMakeLists.txt create mode 100644 magnus/lib/wModel/proxy/CMakeLists.txt create mode 100644 magnus/lib/wModel/proxy/pane.js create mode 100644 magnus/lib/wSocket/CMakeLists.txt create mode 100644 magnus/lib/wSocket/server.js create mode 100644 magnus/lib/wSocket/socket.js create mode 100644 magnus/lib/wTest/CMakeLists.txt create mode 100644 magnus/lib/wType/CMakeLists.txt create mode 100644 magnus/middleware/CMakeLists.txt create mode 100644 magnus/middleware/errorHandler.js create mode 100644 magnus/middleware/notFound.js create mode 100644 magnus/middleware/pageInMagnus.js create mode 100644 magnus/middleware/reply.js create mode 100644 magnus/package.json create mode 100644 magnus/pages/CMakeLists.txt create mode 100644 magnus/pages/album.js create mode 100644 magnus/pages/artist.js create mode 100644 magnus/pages/home.js create mode 100644 magnus/pages/list.js create mode 100644 magnus/pages/music.js create mode 100644 magnus/pages/song.js create mode 100644 magnus/pages/tempPage.js create mode 100644 magnus/pages/test.js create mode 100644 magnus/test/CMakeLists.txt create mode 100644 magnus/test/test.js create mode 100644 magnus/views/CMakeLists.txt create mode 100644 magnus/views/index.jade create mode 100644 perturabo/CMakeLists.txt create mode 100644 perturabo/main.cpp create mode 100644 perturabo/perturabo.cpp create mode 100644 perturabo/perturabo.h create mode 100644 roboute/CMakeLists.txt create mode 100644 roboute/applistitemdelegate.cpp create mode 100644 roboute/applistitemdelegate.h create mode 100644 roboute/main.cpp create mode 100644 roboute/mainwindow.cpp create mode 100644 roboute/mainwindow.h create mode 100644 roboute/models/CMakeLists.txt create mode 100644 roboute/models/appcommandsmodel.cpp create mode 100644 roboute/models/appcommandsmodel.h create mode 100644 roboute/models/applistmodel.cpp create mode 100644 roboute/models/applistmodel.h create mode 100644 roboute/models/appmodel.cpp create mode 100644 roboute/models/appmodel.h create mode 100644 roboute/models/apppropertiesmodel.cpp create mode 100644 roboute/models/apppropertiesmodel.h create mode 100644 roboute/models/commands.cpp create mode 100644 roboute/models/commands.h create mode 100644 roboute/models/service.cpp create mode 100644 roboute/models/service.h create mode 100644 roboute/roboute.cpp create mode 100644 roboute/roboute.h create mode 100644 roboute/views/CMakeLists.txt create mode 100644 roboute/views/commandform.cpp create mode 100644 roboute/views/commandform.h create mode 100644 roboute/views/detailedview.cpp create mode 100644 roboute/views/detailedview.h create mode 100644 roboute/views/mainview.cpp create mode 100644 roboute/views/mainview.h create mode 100644 roboute/views/newappdialogue.cpp create mode 100644 roboute/views/newappdialogue.h create mode 100644 test/CMakeLists.txt create mode 100644 test/testDispatcher.h create mode 100644 test/testSocket.h create mode 100644 test/testTools.h create mode 100644 test/testTypes.h 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 0000000000000000000000000000000000000000..626dd9364f12c6036ec8eae38c4fc73735b9ed23 GIT binary patch literal 350200 zcmd?SeOy(=+W39VTKg42Hi(FdvJp^JR1{P!(w!n=TB4$YFNukXNl9U49ZM=oB7CW= ztSqTxMP{{4ODa22S=kATO3TWMOvTEMp0bYl!hXK90HaRb_wV<4KF?pz&hMU?Yp%KG znpv}E&CK4LM2ScT-fWVbnVECp9M2t-#XfBx(&#Z6qcd-4T0BtfW-96OG1(I)hxxC1 zMC`$pBI7O^GkMC$2^0IbCx5$$tA4`d0fXf6ckdHXHR$Khy?jn__}UpeMEaD8IQq}M zX0i8z&&Hh;Id_wYQGRLh{L62g^Fo+N@>UV&*721m)l!jmcN8xw zS-kwjQ>VoCpi88%bMaO4iqAYVW~@kBp@=z3bl({wA5ZI-H#;=_XCnqRUUheO8`Bz| zdi>1YL6>~CD>?xS++8ml6$fZWO%WkxD*bM!$HAqk(Tb2Yc8>?*(Jb&^i z@rgHZ+Wt|~M3v$kuAa*m_3BsuVGx{SzX=tuw0DT?(%Qzbv%F}0BGYUo;)Y0PHyLZ6 zDp_i|6dD_483dc%B-XY`3Mf-UdIsUkx;*;Xuny+KMKAy+K_M)FJjid+uffJVZN~~r zB*zsak5HEls_kz}Ayn(Q?Hj3f43qgfP1#p$cy7r{kZ-hfb^aVJv%=N@-$kvOo!MhCw~~_Vo2Xg+-ZM1aE7oG*kQ7o9HB;nzTLJCbD7~R8WZBk9rER=q6aNXW zg>2)9bk*e@nP1=CJZdhpy3w%gO`=q_TKDz(^2)?dF zHpaRZ`g*j&wa_-He+fIDie}#-gT|YkmRakBNUZ#@Yp7K+z_B7k6Zn5{LA^oB6k*R+bVo#S_ z*11->tr%$vBKb_6odi|5`ShE97>3_A*ZIxBOwOlCHpBHM%oe!W(sH zOlg)g4t0J6`JR@#EB_eU_5_;j@5p25RqAU@sQv$4$atBQsvA=jxt?TFE#Db1lyQ~qJGxwC;EAIqK<0efs)Z_dW5+*gn5)sPAKJw{wI^ zgxzQLdl_L6vLNc40j<}*z2kaIXCir%Y>Tbyo#(L2#kM_?Yx^ef7V=u_nl?$0Gg$O| zQGx&6slS>!^_&qw*xu>Y_d%c@{c_!hj(yBuBk0c`38xXd(PM1ta&QS{CfI(IBD0;0 zYU!Vd)_!vA!PX4==9D!~A9nO_ZZqv1PPqYuy6u^^ne_ckYhIp??L6}-`uL#4>w1`H z{N_cng?sfX?7kSpyz1r}x(Z~fYgzL=Yp#igw)_nn?^^gVC&VVnCy_k-@g);d~4t8FtSgZl4k>vL;)S0?3tY<@;i-&vpA)H4Pf zNq_Xis;|v`OdA(~eipb0PqxNdmV8PkGXc_m^)jYmuiqE`JVzoYg zt+YQb8|;iH+s9m^+hjI+dW;Tic+GsxHT$$R!g&G$Ptg!^0TTJ@o$ zK+m~)UeV9b^Wef3nU-5~xcyt1#<05}>1MxxIU!Uk%>B~YmL_xU%cPga43jw;l^OU}eJ}GN5h9K6q`>?%@S|~O;Ad+M z$J&m5TL#xa1#SNYP8$D|R7m0e*K4w~kf*ORsA$3zcpL6%Nsl6~*A)lppUoDn_BP5R z$Xn^gz3~O4xLD!BXZ`nNYg#=yhz|%!XJ=5MYxaj-0yyCe$>yJLY^79avo{D zK0RO0)A}B!{3}{UUQp}agMHa_ou_pyeV)^LUM1eoafw__*x&K2+^L_>ly_>mtxBev zck)awk;}LbZ@1|=1)1#uYYuYiwS~?%ms@k{WoPx8*K+My+v_!*nDH5suC0j!|{rst*?n0}-OX;5# zzpcxV&(Jzt+iJ)6_%VfeEqh0vxB6P?{>BHd>uL#ibwpipDPIx}&(}zp%b{Fdy}mxE4RB>2YFREB0ITj}srPP>&gX-JJ7T_br#6 zdhAecbW3vP>=IgU-f;!+_i!>hFfE$#iv%^T5Yx7OL`4Jy??p} zZ|R?D*8Ya^(Hgeab+%vju+C%MbGCn5`nh%9_+yCP0zLlp{iny!e}&EMzFyzUT#uDb zeV^!Y+kAbbwV%$0KK5SR3(i+%hSMZ`T=E^a$rSsA+*=)Fit`N#caD*EdN1H~T4xdS z83z~Jf8`#X%ssP5CUJeow)R=8zpZ;n#!&`SZC}F633-M~KJkm7kg>K0YLM{?^llFI zyOy2d3{$@XGXWccETDYgvgZ82CB(^ViXhbaIu9NFI>U2K+Y9)~XAC#}*o;mG&TB#2 z)o7W9=eHHU!@B8K;xlbGspBm&KjTC{Q!QQ7B@a-^V7r6!m3L_WB{IUM*O}q6mc8IJ z#=Vka&yZ*A!(@X_+cKpW&yj4~d6H?r9QiUCs^y#oX0dNgu~l;qoaPz!wXCwa*jvnm z8u^cFV+?U+1O;5sGu)9sj9MPobiA7+orpcC8`F*JST%Unha>m2+T!*E4EkSv` z{{uok5pHJT299m~%qyrzdq#w}FEIls%7`D`7oi(ESUt#Nf{#W6j^dDzA&34*^ zi*-GubI_lq$K(GN>gPP|wazmCBy?f#|LjbY&oA`2r#>reeJ=fPA^O&OqD|O*R`ZAR zPyEzpO_S+6&huGcHh)&3&&KsxL4|!i&)?t1TeulS&!U*@--!~QSfAFrLG z@Z7lde}X=6IiI0SIQq{7j_UpG|Hy3WnN69a=xgq1CGf5Dj=;CA*P*pPTl z&^p@-`!nCUK0RQh<0rzOWMs>n+J|xwILes07v6_=U?*G)kMivJC--)+Igd5XJf5eP zesh`s8V{U<=(CVkIezNT8T&a7$v9^e`(f?_>iVu3dH2uy$T?4DIv!#S^dg)s1FW+f ztBrDmxzhTOzcn;CDKn6%DXddUtn*?~dUcJw`4#w>XSWyh+0}Thqj8a`u|9WM82Fm; zq|aqrxsf)GYeN=gDxlohB@+rwbcXXX7^3(sS1dLHZ0A=WyxigPsL_@Y1aEz}U#^Zq&d z`AoXAt5zO%XS3g0Asrm=%2r!f(dY6Wu7mYF#K+#O&GqMUotca06Y4$*)wXEUgY5Z6 zvj^$mx<|I_&$!Xw?nvNVZI|S7kGj|!7PgW`h@3?p_ZA) zCCzq_IrLuno;BwK`nyQsbPr;HU={J%o-wa!`8r_H)L)U?ghHvVtqZOi^uoQDkK@i&Y; z#OGUScCfVPZ{RRKmdiEtw>ZC755t|UA^qk;=Ly?utm*g2bI@CZeu=BE+U#1WK5$M^ znGR8(!9>op&b9j_-Bv4?vIo+i$bw) zT9^CtJ$B$FO+DG1bA3zu&k&yG z5?b>M?c{qqfrIq-Qn=ZeE|)=;v6i_?vz&XpD)6m2K)gm);4sBFpEyev@fP&wA7_{& zPUljGu1E6)`Md(5ytV4Kl-K&!XC|$>e+c#G9H;LPIrTl^`-G1-+dRWLi{2A8{S*1G z)OkHzO8jBMIKp#U>OO0ac9o;gJZph1E7W7)b=x$~(SFZ`q$hz6HJO~3WD*W;VFF=4 z$U?5ect~iW7vTWpI^7LA!H^b`k@qI9%a3ov#mIH~kA7sg=;*pF{T96eq=&(XR*>ts zu1EXWTHg@lI^DLeRy&0)^0T&e`Y-&LO5QZcYYC^KHyoCK`tvO22aZWB zqD2uCTRbTpi)QuUB5aaUz6E)s+-NQ`uQqQqE6hss5%WEJ2YWaB+xGA5$GiQr`-9yd z?*3~`$Cz$0qhl_NnHG~5Gb3hJ%ndPHV|K)R6!Uq^(U{{gP2Ny%4{uLzvNzRxjyK&q z+?(OO*t^JkwReeknRku%dGE1Udu)f;PO&{=d&c&Uy(l&(_Tt#G*n48Ddl)?&Jwkhg z_vqN8OOKcyy?gZSk=0{Pk9l!MoF^_efuoy*-~>-XL_$}O1eDh+WwuM zh<&2+lyPdvsq|CBPn~}%+N0aciQXi$GS z?X7BQe`HJhFN~cWJA?MGKHL5<+TXcH_m=kO_b9O1@BNGRXP<5V>V&F>T{^qq3(ye9a0CsJ9zlu_=6)3CVw;On~C3K zeKY!-k>8B?CjFbi-}L{c*Eim8LiV5B|DXNG_y2qUzxMCn|JnXM``_EYYyVsOU)%rc z{%!l8+Mm0B%Kn`FL-!BfpSpkG{{H)h?>qOib#oq?Gc@FlkS9Z`L!JnEJmj&E4MDqI zrW)>ipGt(zV0&rt^qg z=RD~A*7>uPNu}H(E2KhJ$!fV%R?0ncFD+an>*OJMkduRQzSDK3@qqE5u}-cx)*BBQ zw~P43%$S~+J0)lGq>0%RF1m31xC_Q+jmaFHF>2)bz7fOEOF#FVVQE8$3?7&~pnt!l z#9loU;^TV6#zaMgc|zNVv1)>Xoz+wxZ@Q%GD&oBU zT^VuS8Z~`lF7Xu^ad}?<4_5p_D{kv)B|=EV#!|xaN_*=a?a#cXuwryRKB~>_f=0!S zniteBNj3+yBifFbKQXR&vq~JUtcZ~~`kc*%xI%Qbv~={G0)O_z+|e0bV`KCBC5`pB zkHZpKGRiXPcZ~8oEtB2_w8}4ccsD22R#et_;(jtAl|*&lb9c&Y_26 zV|6#&QR9$msxJ9MeG1sIdM zNCj_PlJ>te2S0SBf}UaF-eGZ=(RN11jm~fRcTHgwHG1jjtiH_yAt%@G%b>@6b6N(; z=*`IkC^09W3${RC*NM6Q0dd9th`5o>qX>a^YxIK2xmJ;8>1cn%C_ldfYANL(Fq*Oq z{m~VAq-htlt+lQtA=4BxEM~-5Y^&DN1TX3mACckR|_uZx5+^(^H zUmjODFD`dpo*s7eQ11q+<3+O=D>=F2CdZANI6arE8k=Y3=nQSnmN5DcX5wF%+5xur9#K5xD%<8pG_rrLBC-6XMOTRqp(nf}baXZuihu78Y` zZtZKv%(L>mm7DltV^YslK$K31na7$Y^bj4<$ zPv6Xi=P(*8;>H$K#7)jkx3rm0Zs>Zwt~*@Dsc|_Y`z0~8jocik$|r92sq)Fwb6@1Z z(p#RB`<$VSQTZeDHpi2f`=XbJh9xz$R139=S0}WkNhDmBe%BX$BBfTY%@SDYxiv~G zS+f!{C39vP*Jh1UG<!VXm5cYC`V88QYadw0# zHB6?c;Vt1v~XZDFXmf1IE_R(cWjnJbLj?O;1>gcma?d=X8=pOUU{>+%r{mQpL zGcxAuhRm4v8@_5d+F<$`QifzUWJbmO>xV$h59;ugBU#^1`EHO*`S$SPDUDe&<&Xqo zzBuozDPJjb%9rPvQ}&sGn9w~jdkpK%_s^)VneV@@Ua3uwc`dtV%=UjI#spqe*)_#A zr8Q|xfeD%q|leTpa#>Zqos21I;UcFb{o7pYqo;96fLf6EsS+-`?n!p-+@}0grjnX?+ z@v72Q#_Cn7c2)fjF~Y#D`q-jhe6>}>~xQ9kU6EuRKaGtIo>QXmzwsY69c}2 ziH2|D;IvHN#Dv7m_p{a5Om?PN9s|#2s;@GSW*ViLDsoWADPbyfif2&h6jo(Zl&F}P z&=H}tLzjixLPH0HP6%BTx+?Um&_Jkj1hS){d^1K(5LFtf?5aks-kdYJZ{Kk>&Hzt% zXZ8%gD)%Q$*6+TF)BTQezf74vBX_e>EAwt$u|h_68|NQ1IoF@xEpME^fQU~=N{M*7 zZH|~Q|-b(yte*g*Ziz4AGezK-3{CiXc`h^QVag<2Q3D10vO{HEGA zroLA1$X)Dd7V^H0_XkCczZpOB3(!e&h4G1b6T6z*Y2$jefE|jceA%F8uoJtv#mdF( zq87EaNW6lb%wuvr=T&E4_L~AfNyzCfcz!!}?v({{C08=^boaoIlFBz%L-=0Lt7Z(o zKf``aziw(R?94JRGhQ%^Gj}7olRep;{FdWW{9Iw4->UQfMd-MRZ;M9Q-qj;1@Daaj zxJ5qa3cW;caCT_D#`E3=gEK~h^G1_1MmwK3II{Sps0gZ>KW*R_W5PLxxQk&s@cqM} zc*p_HAKN8Eok%b?LXzMxKZo1I&uD_-7(WN6OqdG_0ln~4Sj&&ssW)N=zwa&*iOk?D zVCZxz=jURSi=wX1J~$xK1zA^QU6%l(ryF*R z7U@Mjy(!n5bf0u62K-6#0{Kal=|{RhbqvT7Nv59U2EOV>J%gw_WsOK`B44YV$loO&ekFFd@p1}8DMt~d2?w0oWmlQM8S9D1f!F6HbU+mk7B~4mHpqay{j*&wwQ&f6EeC8V%#26sTh6=L3O!{W*-i-a5@%v`V-GaSauv6xOG$@2M zKz`XVkz3;-hhr-n(B9kji7cnTmZw7zRKafkyhOFg?UcFw2$N_uP)|iMYyk4_p!^-F zPylNH-8&A5R7OAsU~2{bt;m5=e!_r1E9*s1+_EYe@_}-zcEAad)!1LX6#RglJL4f6 z%Af}D<1YNT3)x-Ry9>R$8bt2K?%lNWZtBCFtU>RddXak29d|8_p!qwj~Bu* z(GNR$qE4imc08E~^&%Uw`BXY!XH%8P)7g+O@{9|x@oWTO(@*H%DzX{<=dk-+t;iN1 zV1FyJ=h1(@M&yM8k!``yAW~DsS4Gfyaj(crYeZhA&X;px9bdypg$yVLo!$wQ*?vOg z6%U~M3T0nG|CMss4o5`(F&;|64+lhEje=~zw^zxpO#^J!qF;MlUxvBH_3am7AW^tFvP=5SOYr%`P<0ePKO*Qf^|?QvJ;=)i3WUm zr%dFZ$lgtcd{_cx7D zzK`tvLZ|{{{FC}LC51?g8Ta;EPhI7x@zTS4Tt|u=Dk9k^S36zDWk^ zIRFO>;kd}5#UhRPa5xxqL%X%WDimE$yr?h_l#2>a0`fW(!7))0*y)I!NE<8x>~zY922oLIfS;X9 zMRlQES3dvjS}iI%6>3Cv8!xIm3(xNR*b-sWyBJQ3irva07yTa85r^$KWbr#hCHO`4 zJT59RT~u#u_wkDAnl944R0%ZqcZ{S){gIrK4DkTc4M5Pj@ zqLZ2irLa@fU=QTOR#8K+J%sW@cEbTtL(v_IUqfp|rP+Y`_KRS`=uL1$R3`Q_SHodZW72_gV~}ShLIIG@Iv{Fn5)?um zoDg+EJkXvCieW9(iW+AF%8f%lj`a9wAU>Y_@!R2;s0-7e66!@=ISF>>YRqXX-k1Jxn7`5E_r$QoRyjBcd)Ny>L8Wb0O)XEGQRsMJd#aTGRlPD;Ds<4%iI`;JBzOw?iFJ&sCJYDi;bt zr?FR(4f#O2M5j;lC#~@FY8PPpnoU5sg#7D*AsvdK3XomDRn*@qMJV51+>s`#l04Q| zs&WJD7PZ0#$W|tbTIGc**a6sET`THNY~DrLyORJrYiQp+9#{&8Mcs?+KFZzKz@OeK z6m>s!JP-`)L_L@e__wZ3)I*y@u|`tsb3|34Uv)s#BdbL{O1+Pw`*-Yahyv0Zu(2Tv zDEn9eECzgdY>lYLT~I0N3G6&U-V;j!`4c-uRa3V5u&5`ovk|?GIz+Z{CKQQ!3cF8{ zzX|y-b}imx*1^Yz3f!-UGd*hg>9pvrsKr!Isn+M>ysJHTAuc)^P z-##sBC*|M4=09yf`d!j>r0ej#Zo8;mn*iJIQRcnFa6%Mo8}+_VRDG(b4_rXrhlC%d zLlL0&(J@iGsdrBz)QS3dhp10#`MNB&_wE+;>1w_*ng-bWoO<@H6ZHl9U!E4#PymEq zqqDzE)Hk_+jRWW(D28fL2gy5BD2jE7I*k0=t)jl8%=gKV5649vSt9BO4`ATrY!D;J z1^IwpJIc4K5hIxL!H2~NDS!qs+84qxF+$gh;h~&o6QCbP7`{Y|4y9s5pc}Cmuo*$! z9jnBMq+BNfe2YqkIx#wD!A>!{x=IvwyWdMWtffEeAPAqmLqRt(tdMqamC zI3Y%N>~+U}_jOP&MvM)%!VWkthBqE&0(E<-k9Cd_OZgtzPzE({OpLfh$c1v)4#?su z8;>l$5~wdB3W}i`8pP;{Zm(1zo`_EGWY{f6pKQn%qc1l5QeVguokMJMvOseKwb*EsgxfqK>lFz2a`W|C+ro2wUIHTUW}n0C>0|OJ=Q};+Dteg z#;|C}fOUZ0um&;CA^)5cVw_t5*h-HAbkd8U3XX|!UOc412G|Oe8%`a=kq<{cd<`5I zW5fh>?Mf4CEPAVvMGq z(de;WGBWdldd6e`b&NSIMiw@+wu><~2g+ct7#EBO`eYn-#!+s(7b?YI{bO9XNsNo| z`=VMgCSYg6ZZWd4o4r+xiN#_}vH|@xDHX6Y37eDli7`1FPKz-G*;MRJO@?)1O!GmF z7`f$8FGgNEpp%FF>6Dp{{pknbxEM2v;FuUQH;FOJFUH06#l`5&j)pa2F6p_X=lUQUPKZ%ZFUCCTpSKT=h;eBypnK_QG3Kuoqc8%fdjWo3HXiVCAvP9P zi%~?n$PW!-T%H4UVq6gn*jq&V7hz`+J{MPsaV2)Hr0iA5Swk3CQKn?C7>g-$^%60z z*&)WY#bPXp2IAMv6yy3N4z>lbv6MQOQs)iGZ#X8#jRkO6jAcGpD@JJs)QWLa8q|n! zGd6GDDaI{jVwB-q*(Nb=%@yM|{JU)pVC%N+P!HH&zLh`i83m+ouY@WwDpree2Vo^; zS5Ri<5iwT5>f>T?)?(aMD8}9RvnE}Pdt9(jjC;q6abLU`Yw>682Egw9lz#xd2Xn+& zmnOzT)bkL&J&c|8*srp|X)zwzF2~`o+kh48Zn+J660A9VB7B(V>5Mb-Ydp)_`x&V*qRTA#dy9LO2v4AdS6%y z`1is-F}C>tpK7vz@-@fAcyWgqFQM~t1faJ)8j!tGCdNMsph1jRu~nNP#%t)jhV9po zy`Btf#CRhS4v4X%T#Pr7y-B$@cZ5NDLJ_QkIzaY*63m23sDT4w)JFs5>kFY2HUVw>03SX`gnYp62gq1M7$0KyLu4Of z<3ntG*dWG7!H@<8uo{qmgzeoP@If(DK|P!hV~-bdpbY%5PmGVT{V}#b&IfFNTr0*W z*!?5}b^!i!wqoop1?+rk1N1+|?@!l49ia0WK7WSo&#>_s!Y%6}OJ8G!DW)b%CuFOP`vRRo~_74omJ@fETL>Tg&L zwLsagsr&0Jpzg2z&>+Ts4`6Tq5~v31{zibZ-{e9iQ2v_}VjMtr0NH_Z*baxqI7qz* z)1eU7K%E$eY>)=X4y_ZTk+6}xM(i}=$KiOu&S7jH#{a{o#rQTE3SceNi}9Tce6Scc zL4z3IN5Oa~gBrlrku)d;bdKy6;|I$9fb0kA{9!HBiSaK1WdACL4X{^?qaMhBN~jUz zSTd{z+I8%J82={!-?Z!BMNkFU{t??h`d|rA-;WJq{1gG>VGZmP<2dDxW0$p|aU7lF z==_Y%&&YmW4A}a)4o-;iOFZO4CDg)kG5(VXGXY=zvjdQwK<`90ECoMc^H+TN)eFdf zEd}JiB0uQ?>N-jNCy|{zAO`1T#wqkpl|wb`gd<{{M)!0o;KONbpRR&>G0q4eKT`zA z&g_O0Vl+jA4+;QVO`Bk!7=d6w9-z(uvOt5F5&>CI1gl{y?1j@}DlcR}A*_X+fZRxh z0zhWei)p$b39?}^AZJ}{n%KA5AQ5t*5^CXun0EB-`A`PcPzT4vbl{I84GLi$VBd*- zXF3!Ex=!r6uOd{Ob@m^=y-CV z6gC0!F#HcohC-lh*gi4CDH~4N@HK!R9fBbZu-{=dY=^^QM&NUV4~k#|P_|=n#TNq}CbGVsFzF{3COg>DqOQ8hpvoxOmpb16_q=N)iF%r0qw ztV^AkUDJWOqbVPq4EPhh4ydb}3#h9bHo7eaq0}=JTSHgFJ~7jZ#2khl_A}-=%?40xrM|RQvbr8VitKI7sxBZH$Iy-FUQX1`^3B=8IWIrFN>&q(IzpA(Jv;ym^!b_ z25elF1eIcz*q{*V#9WO2;;mv{od`8zUPE}zS~0IBycU~Ff}u*x>mr~`%+!B(($kqHo-nIZ$N*UOU#>V#k|c2MS%U=4vV>*^m5Y6vAeuZ%yI#IFQ>lSu~k7{ z#db08@B(3FnwTq)tt=FC)oL+U6W*Bx2gJM!n|H?pKCQ7qy_ok@i+OK>nD=4#KI&LY zeQQsP`2g*I&;!_5mkK+@d}t=%!^70^@NO~J=ZIN_PF1a#k5s}wF&|BWdNKcwKYz~! z{QSF4V{^lJSOh9ErqRcM9jx2_jobXz%enONCfOvFM-uSebvXsd=kH(jDUDZ zg$&36+VJFJD1*JiAE1ILK)#VSZp6op$e%*~R6O9zQ`Gm=8mIy4+C<)_G?)pc;D-ZZ zKJ5bhdpZNidm4Y()0j_F=9wr+2Yh&@2+Clcn9rjBYys$agP8t!F`ui117dEWjxEHu z91(MCDlCP)Vm_Y?OT>J^1I17W`vAQcPK&uM7@{Ew(A%~{%o=oSyg=C+A7le%Yw*8j zHB^BgYM~w);Fy>%3V6T^$&dlLPz0qwzr45sYM>7A<3-B7WP>QchnIXnyI(4R#ZU&6 zdufN5FXQLS^!s*nw(k`4l?WjH3hnquHf(_7V!oOMRe=g6;L@0;DV%Dd^Qb7Izee=O;I40(Y zSwQ^5y<&cZ&PVvN+Xgei5BRkw7q*J|G4hX*f4oD?Pq6Vx8PtinHyRcLHa<;0-(LyX_$CLc;INnnkRL#PfcQb`Jcus`4~Tgv8SwWI^2YH{4JX7roDORM`M1cw z-2lhM{LTmX_#N`^iGNT0d*bXt%pkDgfl{Bh2H-kJpO%a}*Q-@n0eU`7b-f{Eruw!d@{?P}hl4*e&L- ziDI6#0Y03n7V~tmm}j!YY>I~iVg@!qgV>}}Y>MC3s(oTJ#=}~%nQ6k(8cM}x&w%4% zbL5E4>4n{5b0q?Kt`lN&Q$8pHs>Id~onQ~-!zQtXpwC*?);rjyKM7LJviRnWxwgVPj` zI-Q2i=5~3N3RSATsq8+ReccS3Vcz9dWo~tqTV3K-Ic}BVR)gHChg(IsmCdbwbgTVt zwcD+BxYcuRwZW~{wCWeM>ZiI@e5?Na<8Jk$TRrJk_q)~YZgrhojdiQ@+^U~j#kf_d zTLs+es9SyRR`0si%Wn0QTRr4fE8OY^w_4;@v)yW(Tb=7xech_7TeWkmGj8>RTYceH zyIS=hbgMfo{e^Bd!>ul~?Dux7&X)erQ*QPB?+e`T_RVNN)=C6s`@z(2Vg?aqsvUlFm zV_};4mQmU=uHt+i({MTr2jj{sN*o45D`nTLL2bsCHl;BxTNzQ)blr-MHRfCD<)+c< z<>s;F7O^d`U&n7oW8`^Xp-u6ZE2Her?lF=+*A2~qVSYC_nmu=Qc)h_DG^ zMo3slm_4dfLX#!1FuAG3kwH2#`;gk z+b}R$nXw&X&A1MlxWOq9+abl&q#%#&V3uB_Y)uDqubA3&PtFxN?|gmHX%(J#MXp+y zdqwU?XMEY}vWv{Cn^qMy6{@wGB6Vd^bF8VbsA-kDQn+O!16SE5*^fxPB=LLMkA0P< z(Jwf>OPAigs(ngF6oj9aNI{esA?qZ)GpR)Y)FDF|fFzv#Rj;t_X2%GJvtyh} z)v4i1MQAmf;b@wgxv}&5m7AAOPWbx$r7zd@c_!letGC>j+yBQem#V7NOIEJ9@660W za~Cz8jtEy%60Tk1R>zVuXB3Wo@LBgQx47@CioJM2yQWcnGIN(qd~9dCM~$fSbA~1~ zCAk+H$IqRV7Ofs@ug9rf#s-c%cH37-JAU=tReEs^ruha(OAkl;(BP274*ffK?hq16 z9}P~89QZ<>C$eUSr!S$cy%`ejj(H)^bl1!<69_xV2!6vLQ6om^@V~l)@f6{R>(P@A zNEsBV;#5k)phz0uEK;dG<9awcMnt9z8qzlI*!}3k>(<}@_=dHo&VA5);Gt7rt$$$s z!w;{2z&`8388aqMn2|f_)Uol?FV4=MIc=hP?vuu^zc}#K!8677n}Qp@{PerSpMUk$ z>4YthJp95_k3C_$<$rYjmZu-9rrn%N1&-K<*l*`I+ynS+_SL@3R9Byn_zqpV#&+${ zEy_raOd1pv;tDzE+{i(_?4Di|^OAx*LyhEaQMN8!?5jPh)}ul_BRq!N^w@_^%(F*I zOm-f>6(6Cy00!u(=gRP51N3b*Y#1FPJeJxHl7Y$g9=+Nt-2tA2{;IiS^l*smG-PNe zf|Nl+J33l>qtkH1?AfcOi)>GBzHVIYf=`a9X|c)Ms_%Po+x|r_TzSr&BksvB9@9Uq zX@0@f{DSju`P+yK-n#C{gSU-Y8NBTL%=c?l9A`^t-PL-nnkYP#%p^$du-_=ETmvcBWz2tl3achPzs3Rj#U@ z2ZLIsKguj?$~7Lg*YPX*9zG9$Zac`d1@UMO^#pb0ei|{NO&9Qh_Jj{jaTsBq@J?|( zjj(0UZ-3_HXP%~9%l5Vg6+>d`?9XU)AUtS>boDR4^$`q`|yLhrj(|y^*x~^ z)_Bf1L%Z9{WZ$4*4hr4&AiJ%dO?9#Lu^CQRq)XZPk*HyF1+@ z!``0mscU}f=qu0MI*hp@j7fuNr(yJ1LMMACXFJtvXu?oCowrlD`ZXPT@!EUWUHeMY z;eM)JaJg;z{Ianpvy>5#tP^8zTc~Rmnx>QLCVuhZ=6Z(N?BcS!g4&6_dWK6LgaOvA-n!sQ(8D)w z$l$oa>ZYEFf4eyM^Xki1o?m|B=gs{(L)9A-3|6A7+`>4krORH>Co8E2O-Si#nZzbz!Rw!dGNc#FB zY%arS7i_oLOozk8kKh*PNfaLFrx4Hf0s3L1mo&QFv0?VX3HlPQQ}df%R~J66rrmE# z|7PRCQ&IQp>o*^}!L+Tr4EK4XrG2QYW4Df>VvF&*qT7duw=2mDcPfdN=+-)zrq_nksfqZ{>w6B4Tk}Cl zd3WQhWmj*xbLrCZsf$Z*xH9a?o$tIj>Cs0wUVL9BuHoB7^m}J~OO(03VNOTa=#D*t zMS3K7q8*OjeG^e=z1yDjWF z+Gg-n5!+DfdWOpoUzad@xM47Ds19tF_`8>RuCOrG&f!p8*AaB}0P7xY8D1@`24&r# zdeC-M^fQ~1So6w_X9|s5UwNx(wUHXqbl(t<`caK&dQFX}y3*}-A!*1Zcuyh|5O{W~qri(nsBk~iO}BAu$l zc?;8r)wis3n!8sI4C{_NyUwv@w{ShBMF@{Ey7spoW!=asQ9tW#-J1UM{hL2}Ul?}e z&JB-Mj=gb&e}EZ#=9aEkKU4o7_3l>z+4T30A3S^itsD9eHGa9j>HO(@-X(=CVQ?!+BwX)_^>V!U9QfHh%nvm_9c0t!K;FeAbT*ks`s~BRZn-;Ls$F! zyXBd_cO#nbT}knFaI*3+SbE{9v&|~R*7?Uh|2nN4bnoO%gSR~J)zqXeQIpgas=Vpzm`zPzHyxWX`}oKAKYsU3f3JVm>i-4Sy>}DyN*`Z@ z%VxKW+bzK%;tp~z&I@wb^tEqsPOlBPr3c|#kkK*D6Rwz=ZNYn=%iDfX1)phW{@r%8 z>4m0>rZsP<_Qn)->;24bdGt{i?w`)Ao(ISjU;n<2n2;_V5}58H-60M~^1w)UkHj8{ zSLcQHP#qjSdYB$h^woJDr`hl7wsV@ku&v$o=XZ^@Y;C(n;WjZ-V_TPO9hy7N!<}X8 za^mp*z{7tlx%J=g*8lsq#mn#gvgzcqTW`N%*{yNwR^0x8>V5ZWb^9Bief(y{_6S?o zt=B!WbH@|cZS7=>e9;Iwy5!pHmR)`3^ewlpx}oWd75YBPr;XvXvD5!U+Izr9Ri*vo z_qk>IWO~WWB$*_W#26qUg-DT%3K&2oAOZs-!9|*d2#6qJLQ$y#in>xHDk7j1-4U@t zL@X!@E0%R#1(j{OE3AvIN^e*XVJlF7Z#>Cbu2(+|ctJl-wa4g3ZA z8r)Jrp)J>ui}iQpIK&Le;gEd3@Qj3y(}jSMQs~$J|oD*|* zLWaI1yVd{v@Z?)1&-B={Zkv3;{+WAE{Pg*^-yc}NaKS?lHC=VjmEz~>gX$g2)`fRb zl&qs~Ci#=k+tf`vw;g`=;kCcJd;zBk{2W7cF4J8U#XZnB;09MS5CcjEoB(6U!H*g3 zSYw<6P}PHzjft!;zyA8`(v61?w?26IFq;>CE~73pG8T1=Q4N4CtuQq|_K1 z4J!>$xRi+vVw1=+=Qw2C3L$Tl4}=p!bG*ixxxg!p9jTu;Kj^;$ykIAtG$u0#W5#P8(MAlArASXzb5OltYPp)5Y z!wk@WoC5iMjry6@BUVu^;}rABMO}uCt*aeCJH?{DSJhqFr(oUk<(o40gh>AQ7;+@6 zd|fsTo;9Ig@5&kd`cJN_>DhC@sNT~TEx*I|=7&emTnq(>C_JYA*?0?hrW)j*x+^{) zY%IR$a)&V<4+Mg_g3vwaxJVgNCue<>kY$@~(8meSWgjO39)~T*CR!z%$L6W*)2HC$ zL?1ycBez^uS=sgDL?z?U1V)KD0zv=^mNG))ssPu>CF(R*GGy2Oaq7&)!s>iYI;wQz z7ztEVS4a?lD$4spR1%TMNfv!xw_H&kgRms$=f%V-r(4J^2en|-D1$U@y0E!=wYq)Bzto5K z{Y)>B`(Je3i*1iDy5)uqqsRSb-jNFe;=f-~k8T<@YQWsNho0X=q0PISRl}ylbxXg0 z;CJME@MHDnUtUu`eRS~1hLL0T(fGemklqp6pI3K(HE~|!mkVbv{s6WpaOn`dhv8f{ z3Ql26yoVi9jVKuu6A)8286~&NCXP$kY+R}7-sPq`H=S}*iW6YV|lcMAK zNQBm_6PRvP{#WGc1!{NtegBcbX-R;jEPsQ3+9B6w3zOr$tR9mm9F{XoIEhWNloPdj zLY~mLgeT?^uXK1w>gypHznzN5gX7LUE&;u4cw9nur}^j9Su<|zxLcX%a1NJ-Ly)b% zoDa=y)CFN8o0tHMzsh@fDvOEzmcLQIJKc6bghTf7rY*bG@7AwT_t7PfJUmo=TwPBy zb~e+BSAMU|SGUdEmgRksewlu~dg+X|_McUmkzvp_@D|D(er{eD?Fdi7_IT<9Jf33%#9wR_$yuT;QEp38%I7;4?aOR(WPHfx67aJ{;B+ndQ|;IZByT% zf~$7#eU=7(Nki$bUEBN2O z{r%FDgVZtNH4W;^dp}oOwu*1jNSgiVjvljbQ9o5rt3Rmk4zE?4)zI{LyJ&D~&J?E8 zu?o}U0iz_zCNR~q)n+$>=F}PKI2flr?O(>bc?QlgE-{J@BbkgwGqbO-b!)NHE;}{b zsmV?acB;2iot>gs&(xPrj-dQ1(Z%@=%8nIw>-Yhuy3FE%RX^M+YDuX+cVe|a*cHcE_@@#Cz_rCDb^qiRsRwCKn`qupSzSZzwso`r`84u(3GDtwshnX!nqU{zV z!I*_31hz9E9%M|Wh)m-eV=Z&Z$XA1%5c06L_x4~pp8B$Fi->>A+fHCj`ilG8*0+AG z@3m62r9gKB6&EamMl6cNwzEdt*(`x))ut)ew`HIBwle=rc)iw7gNU*r8Wukp6hcmW zMkpf^hVz`o0bbZEWw=*GXi5jq{ABM~|fp-miMW`yb^G@JuC zBP2&?(h2@L8KE^1!s|L-KE~gjj?jk@O7fBu^m@I#(gVE4WxU96UY#7F>Jt%qmls&W zU-XMmEJ9XZ;KK;LtXDLc7eGa4B7`CbcwJ2q648IOACFKC@4pZs-S9Pz?+@LYbvBAI zlsE_XrfbN@3#DHQH34SY0TyagjZbV9U6s{Xi+n1j9Hud-Aea#PQZ@7j$_R7!B6ZC@ z>dK39C3)K!y4_n~Fk!VDevqD7zv_24w8o{DZMRH&wRN~MzqPdYz1bH$;gf#P@td75 zj851geU}Ori`^*8id}JJSjixn1W;86wK!;(gPI-GmfeL_K~YmkY`#Th#-fe5$^& z8EUyfpHQEtURQrU^}YHNS^s&O6!ERk)#P)#=*ll3;?H|ped!A*V7jP(07dC?CW(e{}#|BFN9f=4dv!+3QbpPwM)!)*DD*|fC46#5-nfL6;jw}wS(^$eTF7RK2^zZEOeYGME~lU{5(KjYQ|A;i zmijys2c6{)#dlAv?lbn9hhOjeruyZ>|78l*kJYC9yBh5G_@92;@;qJk(f>S`3Ztit z*BluyC7aD-_JEvaK)|(|WhoG}i5?tL;LUW#QgAl~sUb+wAj3Gu2d9ttw2-gi9M`Qd z#7j*w6M&(=NHLc3a!8~D_lXaGN74(A(W;-G+MxEP_a1&y9N6}PGXJB!8$OP-JtqBQ z?)npv@A+w(cml)(kwD7S(&l~{;1wS!wl5VfPDt7HPrX~ zsL<5+_{jNrd4KY;_vFrRElhh{2^+=^G5wSDvFxP$-OU*b619D z4p7hi7*HeLIv`7RBF#G4zV*Knbo~u4q#XjBo_A>em^PJhqTCqc6OZgwBl11+pU;Hl zKd(>mppSwE7GqvL!oYYJyVEE;Whmcqw1eFQhvB0ZAMNr{vyYm5)Zn9fAJzFN3idzs z1;WEOeCps}4JvstPJX$`mC<3ZK^8yTuB!L#JMiM(qkC7Y|MA{+YO^%Ib>(YsAAU=k z(7O8RpB8HT8B9;gmFSNNHibeX3=|5CgNg|b2vJL&MJ%(_TUJ?GET=3=sfCOp6gV0S zg8tm9`y6N|0i;t3;HZ)^-fVmAAl);3I4wGevncxW&(d);hLsWo<2`6AlUn2L(5W!} zkJsoTlM{J1AT1loDjDq-v)L|tGnH^?S(w5tcQ)4c4AX^S$_$ewOh1R|n=pMErgy@$ zCrnGikA$~|rP*Pc5~g0^LE+J1sW?obFxkR1xh+ip2-D|bIuxc?!}N5R)`tnT-WjGF z!*p$!28XFnm`cJF36nKUtzr5$On(Z~yJ31ohZk0asWD79hiP1xt_)LYxF#%Sg~<^n z)PIuKf0)<5GfW%8w35Nj3yWhobnh^A#W2ET57R|w!t~EDeHNzo!trVJa+tPw}<_zhQmaK^4AlmgWW>0_7m#(x7xZE25WwN z;fIT&=5QXF-jW)Je>!u0E3_M}^S3;A3(3-?)(1X$Fn`8siJ56Mnx&KgzpP}6zhQz0 z4b0nCNs%pPtHEvy+XvglW%ift-`XYF?zLCi``P8G!|fC7i|lLcN&5l&3HxcgskfbC zcCy;(w4FY)(*Zj@ZKpMMnro*Cb}Hj<1^)I3f7{I8LVm*AxScBPv<+DoD>g*CkfP0A=H3idL)c)O8iV^hc$BjffVCvk-k^G_kzd&j00 zsqerH#4Dt$pxd-gj#o0463f9w3y}{RkKB_IJxb&;-ADP+ddJC z+pcfRV)F=&h|?$&?TWV>h~O;%g|TeHL>dr2BL$^eNwP@lz6h=mSVXqSbWEwsi$4HlYUp}2)AIjGk{f`z7>w$KS3G-;tt7FuPY zCJW89P`!ofEY#0JQ40ZDwh(}iTj)c*UI4G*kP~<>;ub37RSO*Q0IzBh2N=%liRtz9 z)N0veA-vZ51&B&sSmuRKSg0jlVWFg6iT2*hyMdP2gb z#i4m&Unxx)*OGO3N_8(QrYdIV@=>n*#FO+ z*)ITx(m8$s3iAtiSe^1HtxI_Zu=btmNP0?dUo^@vE5R!e?JIbD-uA01TyT|hIsg85 z)HZUyVaEKQmTB!<#K$Bsl!Ae(V9fxTjnPnb@#KZfJu zX_I`(aMKz*me^BxzIgF`rU|NG!sHE@Sb~gwf~{ zYGBmKn3e$xN=AmuWvxpdL8%KkT%nHB}-rq+lvR z#JLp!ibW;|rDsi}=?eF}tAXhRwh@D|2wp3GpNlbnDPa$`mLix|E{017>?(eG^odQM z|M>5Q*>i5OzS4~r9eV%5-l5!nmrodDF!X<6^l#QC-e_DnV4U}vhqfdQa_>dchmCeo z?B!?GZgoSA4bCYIcTT!@^oFYwvRF1@=%{+OHlnZy^OJ?W6u=CPjaO$Y85#BI_WYpx*>Oa)4zCLi|tB>A22)w~sOYdNF&K>= zJ8THfKwToo+vr{CJ?53YUZ*o^XfQMxju?)^JJ;Ye)bl@E@W5z>dymB;)g>%`aAG=@ zO6V-;?X5Ae7QMrc25HY0q5LI$uMnL=H{o(ZWq6V6-?e_g`i zG-JBW{v%;(2~%^JR)wi4ObubG4^v&3%EFXBUOSDg!#yHcde+wJSQastJA+k3UA`hN zYa7YCcFk=oG9MkUZaH=4%-`wrmmI6^UHFKB{`1m1*VT4yM>0POk&Uw3UJEXLdc#i5 zCko7z*hhH>IlWiLC)+)+xrm}q_Q?Ui#Q{WtSs(63Aqb$tnxsco{{@kVJ}hmC-8xStz5rV`bD*MnlS|xs001sI-h6Wpt{H zj_?mA#s{U?BaJ2EhEjawG*dUh0B6ryPDVMH9}|>dQCAf=KQj+Mw$&=&xLslJxttPQ zwRIZOm&Dk!A7nr8o;!i;V#RZ}zxDFL_hxMECYt1@4Zo{hc=gh|X05ztQLQ?1SyO26 zQ0l#7@)R-=1TS(;8K1qz*kfDk8|p>Un~V0{@b>X9_D_I^2x!ZFSmp1qH-kcbytmKg za+{29V`j*M$8MufvSUX&kA$ctM7u(CivMg6(eV(aA2x@mA*AtMY#zWIYS;{ZkDD`6 zn-s%kWDzGpzAH5>d}6u)BVF95I`{=GSsdycT{fnj?op_K|QkPTzJjL8EfaOf6ievyp3K8gw1a zsZOt&xyoe-f2E&vDb_YUh?GHwYY@1540^mInM^9tXP)`xxZLZ^o5(-#KKM4RPfp=K z@n_(MEQ7?k+zy8k=d3ZTb?mU4_PJ@Ln*=vaJL#q){Er$pwYzDjn>O-4r*R8B>^CsH z!-R9pO}pIG%&qZ5+yvlKUjAZ~Imuri<`Apg)a0fHH|4m=!D~cGyxPaB#@iwM;c?z- zODAB)G2<&mh&o1}=IzrYioN4cQ?v?r&CWr_iCI@SopI1~BV0NX7CCS;duPpD| z{~~R!MnAIDyv$HLS$>k8351OG2>!x4T7?zyN#L?AFeM3A#^TF%TaLZPF0vov?Co~h zVb?x3+LfO6_|+q7>+MbUW_yeMh+R1bhX%X$w=CG55K`l*m&fg=>}I1#MvH86z%kbc zGKYA9nm|wJ7l5Xm5!6Ty{21WFfR$y!edN1gOq4g-&dBN1-rR@U^oq$9xjrU`uuBakg3~-JR zguR0Ta^BD-2_qV2-19rbHiCnj)a#*}=B|uL7j3+C(}T~AXqdH7+_7QyuI9EC($%kC zsC4N$WX9<0Z@jtwxpx_5+%S9RV{I@I01vV5(sx+*kT5>p$89!Sgpef^j=24TU#Uy@ zopy&s@EwUzON4es=oJ6i9--sh-l;uo<_6I;%3m=rP+KWGrx|~aK3l*Q*2cTIxHT&fG-Yr` z(90OX<{+&Kl4f_Q4^mx_%7SzZ2AI?;rJ1I*u%!FR=bp=b|MJ~Gzo$R{^v$bxZ+LXY zvIieuCT6QA;0>QkF0o8KseXC<-NS$S_>&`yHbHh!N1|_6az9YW4nZNuWipusb76r? z_KBM8U~`zl$P&n8heE0;q*aB~P)Iq2)Lux(3#o;fzF0@~(@@8k^ki~s!(@k|eCPBRpY#&95tr zgXPS6ir2|^F&Dg)bFUVgb>jUNFzd}dz~O8+CS|e5qaQ4Nc**QJ_dK%Hi#wqFVvmtG zWIv$v{>Paf^unea$B2E89y)aF>jQsG?Fn%)#_SOgVQGLRPq2A?4ui!hJG6tKlX8j? zQtVT|Hv1aC>2uv@*k+PT8YadHVtpEBNqwg;-BYk^qUA};Yst1leBbqiT(cXz=7qw9 zcpsx7&ld^X1tIJ+$R%CudB{hDFs(??DOtb-Ad^iO61pIAj*fSsmM&D^g_^ohO&7v* z4OKX<@c06-#cbAqE4n^{+ig-o1>hKQ!d4cRQa8~$UC3qZVaqy zTGBY``uXb?4m$eI?~X?LmH}{_&XI>$N%@W?A<8G87YNixoyH8xj?n z9HN`;1>B9qL`(1nq4$_xUH3TGxCAzc0BbdRl{QkNw^zx{VjZuL&CUtZ@0;0rA%vPB#%NNcSj3rgSg@$quPGc{3WjkSC(dZyu2E@5zfiqiRyE0fLtIaGr4CGs9qeV8Vx6yDL^|MjbMqV4qHac#j4{db7M$I-_b2f|y zFiCr@y&KL8YcD?I#k6|I@aJJ(WBU0r8{zq28{xUtM%8%nu8r2&DE*@5|JIRoXSJ?$ z?x|R3qf#3=ZSXZsnJ_hFvrf1<{Z1dR9mj8quBT7lP6uzgDEBYwv@saVrWtB#$WtNS z@R53Z%XjvR@{4}lhoy;M@WySk#Qg%bw}Cx7gthR}j(9skEoQJeG9-`LCb=Z9$>@cq zz+@t;$p`-h$>boLOEUUq`Dvn`hWlxNpL+Ny2BPC9*-xka^cO!J@Y8dC+T^E){B(~F z*^fg)sb%oflpp={m7hNJ)0=+U&EXdLX(k69(5Xg%YBd4|)ve*xP4UwRKWWtg;Dnz( z=1l@-v!9muX}X`Np7$=sJ9QE2_>hC34s67kej4njGTv>kpU&`C&8+%(8O`<61YUck zpTd4R#T$6nPf6bVBL23eKc{w|(;hsi; z3aCt(#Q;Hrfu0r2u~_@ht#KX3xN6wq0{9_6^!rz1=E4_`sK0;i1!K(h)oZVI%Xx2? z*tWIn*0wS%d~R^ZAZbin=H6veh|_9#5li1-{jD@S-pu^hEKsjA3!J1F%~r*3TV$tM zcIpT7*95x+Kk1mA{M=CXqn%FJ>0@q8+ij;!4DKHLLw0F`oeXxrz0f|uKEkd{GVtH* z&71a*?SHYG9$tHc9g8*{c@j$aTItU^EnMo6Zdk_}VV$_x)X9z~<*J{n zpY5ml>gu-;MqoRruBLnF6}6w(Ma)pg(35Sa+dg1tivqr+6>9?Ii?@4B4woCYbqRS2 zf&r7q;R(3Rh%Et2623o33xYH=NE3oIEJ&9IaX$pbg5(a87^KrdIuWD~g0w$K$skg* zQ)l=Qo#6c(ev($;<4y%0>eRrm3v`C3T|ok4x+X}AxI+*a)8Rqt$9dCeki0=$^FgPA zbUa8O2I)ZX{{&xsJQyFHf=^eTt~p(8y3WopqQ(~kNC;Ak&NAa+X^@=!VcLs?t={f)J};nebp7}LP(nNYwe5R0o%TRC03$#R4Cx-oMA*g%FWWb$ys+Dr>s{*A zEhm(WE2IJc+^g1KHgknK(t5AqKP7Ti+qR6NFYIrK&z^a6`_`-ZUdB&pE${|8QsS*5 z^hX4X6@s%3Fm2T(WCyP9gjP@ReQ2cvR!UlFla==Y4EiJ<;hoxLCA7AR+dEQywB>2v6k_}}1525rtF zg=TV>@8JsdQ>ap*7}wQ&sL%n0k_v57Xc32*piopHFHF4(O+KyA3I4i4A-ou_7jEIs z;+qs&qZjL~P>JqlE^4YHO%K$l&@|oCyo}fF#uj8)i zT7i>X*97=|3hm^&rY5e5iYrvA5DGe!vs}#5hjI!r&f4WV;pl{3o!_TWN+{*ZDP|h4 zaJ{fuy~=pkPcVLxz;NVV9Y?HwMCyjJ@}0mDD4R1wK!VI6nPO??zRbg!k|VPef1S(( zj$~%K^78V3NaTfG&L0vkWOYajm6|2;*Er%~>PMFP1oj``3Xi?!_Q!w^A~kRYPp%s? zWO8D}jYG!tS+wx(snZ&!ENNV%{^91Sbi?(Fy`uNt@igJ48|VLah5FFUnR25%^EUP2 zrR(Oiaa=1F%XLx_Wa(*fv&j<(3a;%5X6a5A_Ng@q%=8_I)bigMUj94Y#cZn;;+}YM zpIVRAs%%f>Sch0QTBT{$M*P}oJ#0N`ZMPb&akIVF3hObC+i)Hz34M;>@&l=)2b#5j zX^o6kEYdl^WRz-*NV=l`>Vfk|T{iQQX}_&(T#z%n>w@XMZWD{Ugt``&t{fifX3M?y z!3zt8_IA)t{^f+V3Liw_d-X-y13C&_I_;0QGhPVhe6wJP8x8Bn7##))scYc2xPa%Q zsyEXxGhM3ZqjJNIQqM@dd|nt3pRr~_(B&5OXI`ytg_b= z@d(J!U^AKI0z^6%7G;}l=Anr&3l-hTe4=Y?(`=2ljrilR?HIr$TfE@HT3b9Et+mmWS!qv>F3LO$|3?FJMMISe|on9$idOYJl!^ z*vfq@;)}U|=ayH?b*^2j+6E*>y!6uh2OfAkZ-;N%<y?U$gC48V}@VrTv#&aW?l91=JW(XM<6g!IH zm+H&el?b|h2nZ|Km9WT0mYj=Y$RVs2bAN$unynNTssg?CR8|*N2hiC-b-;-98peRJ zh#7B;Mb(AXT>YjQ&6iv?Ve)ULFJCf!{N(z&VG}0aFn!6&o2N{ic-7j?Gp28T>egGg zh|esYK4H=g!|KLQslV-Be4cW{RdrLQOq{;_wkIB+b=#&V_}aCrSi4!kKR+~(Lqvng zVfFd_L9ZE+k0zJhYz_uIZg&8=bsTV4vD&Od6LKkEI4oIl*IL2mv_);r@Mrf54(Cp% zXgcL2r_PNoWzLSM_J7=R2-v@k{b7ej3aKGjsg*@=Sk|PjecC*9|(A>BmW9 z3%6@-(lh_f)HCBbIpq>Tx={BKZvSlT(+oSI?U^6=5^P1vW=o79@cdtttWLq z_TMV{~ndE{f5E zed!*skbNn$nk?0HM>X}XM%u+SW^sU-BquCpjGQ|R=IXvNIV6N~hbFQ^f&oa07Hi7E zgRmqjZXwVUJBzu#vSD);ThGSCEQ$>IQV0i}1IpppW+^}rTFw-;@O@%I$N0$;nxxda zNY2KSUsUs-h)9O^gTuoC;EL@Zg&zxlId6WSX$u}$KjQWY4LAJtgWrFa_n3F_>cy)s zo%`_qr{_+opLUeKT(;}x`Msyjza1>s-HqOdhFo4YpfV@_>RCg7^H8vC{+Kme?_QQO zdf2F8J+JIl&~<%7Voi4Ro)OE|wZ2W}MZg-Vm7e^~|xxGDxEL+I0 zB{Q!`MOIcPKy%MZCc_5_LvSv>>4hiSjy@^Bz6W0@4L!2*257efCs4J}7_SRPyL2z? zQfP9*wf~}?1<1j7Fi~1{Fi~FWC~hnk9mP5LwX%3)aeJ{rD1}#@v$UbKsdQE8sZxVk zDlRQ9MREY|+X+RVR%1tzm#tx7jbY=8tDXGQtD z3%FXE`#)(}yrEC zJvw+s->tX3a^&q-PuEY6&b*@WUh#ES@E~3J>T6WD|8@1b{aYz_+g9~YPvLvZ7K%N+ z?&yf(t#zxzA8k-h;XJ0c%OV>8=gyo*OXq zvZ3Y&og3$`emIiv33ksLI8>Ggt{L69bk=Zj+002Z?-b=eLkIP~!ZLOCym{)OUgf6A z_A4%VVA`x9m&gX}T+GGQ!U{Qx^wTE9zx9kq6_Wvu%o#2VxlFRhZ3c#&9Ak4xrP4I1 zk)=~ac94|up|c6404`w_EJ;YN{z00h?x0~?@MCz}LUk9_J*7TNLs!tvH8dkfU9PUq z>huwO6wiw$eh<d|X3chr7Tc}Bj|fJA)E_Z9-MOycs+zPCK5(Vb^z$Sag*t^@wuAzdr( z0sUcl*Gl3(o8U&G$;_bS+ljQ2akFD*0<4I}Q>T}alQT}8wSS9m1bZwyjkra<$B(*v zP`^>8yX}w7Z+!5TJ0^JPWwByX|AmA4HT3E`cwj%%jf>{q^1#y3*LFpIRuaNuk+=Z; zDi9XMhXgY-4N9Kfos%QjEj;4{oNP1f8TJ#2Eg5?<-p-I}GKOS`@Me}ugEhe+LCGFO zGEJ+@Qr(M1~+lRy8G0xqKn;|nqR%RXWg}fF7G$oaIbwsZC|`* z$<4Kw^yxp~65GwQZ@+%Tda2 zFXd#Z-|#w?!(ZWM$nn{TW-Sj`EG}!dC=#@UIRQh+P?re#z3}JsBJKrwSC2>ONR(Ql zv@1$YQI;`6<69ULol>rxWf0|4;Q?}KQbFp2?#azr7uWLVZ@j(f{$%XJ%&bew#*9;> zsjppjbB;Pv`MPb`(q%iKFl;??MSs(L?{yRQ7Z>7Q9}?=+pQIlk4>(v3LdD<^u`Mjv z5eTwFCU41r1HYo?y>j@Or%08yBtUickDo#nxjt!T!>6}y+5g6Nu|f&|{JA0j=g+0R zFK*kqZz9uo{G0aDZgH*4V7lxgxEa47j1#88%U&8=g=s?{Zil|_YkYDGgX1NzA3L2$ zu?Y?jf~7U?Wa=2FQ_K*J%M($tPOL{Bs$F7>h^@*qDu78XEeWM^fTxg#p^f5&PX|+U zS5@Zc8LKN;UYW1e?|SBTFL>bYl8j4kEV}Nxw&&&R4MVRKcTbRpYJI80o}7pIupqKJ zYBrgSNQ1=^GYX1RfzQ6e@-zMk96p`sv@2PDMlRZMMwttD3`;6p#;R(}m#^;VJ0Hyb zM69?`ylLs|C2d(ZvbIF*vt{B@@DM$9M7QuAN(+eeP)*^Wd1tH(fjChH2Nz?pNMDxcAkA>K$tr z-uuw%74QT^pUyeoC6mo1lO4E>C@zniC7d}qCc~kWD$|rkjVhk^e9vcjvnmi>tbY*S z-$KLGU7KlG>*w3pr!6#OMHbR&&CF3()Tj@nKLTHXB!;V$`OZRN?%xDwk+DtuS%9S& zp>BKH4#PRvNw$5=u3c|yut}2Wg1(@t3X|eu_>?Mt+po(DGo|lQzBryKI!YbWfa;y9 zDeVHy5vzzzuB!_FyO*)L2J*hFV7-vwRN^esdf_q%w;}X7Hx7rEaRP$q>jmUEE$xFK zID_6Dtq#oTLUofog42aOXyz+{RngpBAsEbb!-*Zv_YqTWJTG_sm^_Enkt3`$QWFuU zMGi;ANEEL3z;z_c(GzQFDkuFM04+!p%j(L$R09v+>WU0wDjSxThzKhBs)fiIHuUme z=L}O@KR(uuizQz#|F0>-1`O!=w-z9v;_WsCGjjt zECdb)dIYF{fUXYA2#9%s>jR>s^^xsVS5*~5P&5Ht2qLz53#YeUXs&yU9{4@TF0?jNtub*DcULdCFd$3X7L2@ zka?NXiMd0v++a-CqJ2}o5$Qz_w*BeNx5ORti*07{<+lD(v^DqPi24zIp`{2*C0~-i zt7%(+d6=wT#qwxj9tzp|3wIDSN`mbL%I=jNa+>+Nvo%L95n1JUazP4DY5*79jU*3B zAv2F}OWK`+jfn?o<4;fV&%@I-5)oo?6Ku-gF-Ts)a{+w>wS zT-3_2wdZBP1?phcr(G%4%jR5-iQb`FoAnveMxnn;T_z!c%Q&`HemIqF>>+h3&p0OKA_X3k=7?M5%7=3mgGx02Rdr20G~_Q zf)r_jU@;+YIP{`MB)Pn`lhlcYgB`|90c5!_(|vTk+B})AqwA)qk5m0bq_AF3gOKyK zev-P8#!XQ-sm)Vpyt)DXkc7+I-&Ia4FTp1 z$5}XYZW)#Si872{u`=EQZ$K}g;fw~)a7JsR9k*a=clC1$u`{W7B&^Cf1RY|;jTet|YHnyBY| z>=BQ6ECz!y174GXpyC;qu&|Vlko*jYM`kL1EK_Oc6!2I&CZjR)3nT(49g)x|oo45o znV&s`lt%mL{*#Cd*tCh|G#rGq?UDPef^im7O$LC+dZqWchBgZ7(Q^2#}X?iu-^UHx(SyFuiypf zR`>3%La}^L`-92yDk*dp4xU?P79lGwr(3K{cWd`by?};;8vIec4RNu6!-KT}b4;OF z%?#+(-Sr}=GgB+`YP!s$!VjJL)oV}t@no+7=Pff=FC$D9zC{^MyVz(Xv1(8IJISgF ztyl-{^0o{!^m5>{OnSM>N>&bCd^_Dm{eY8%OWXfp*bhJRBB4^~Eo_TlmzAB})oshG z>`mmatgLcZA#07P&|TQ);;JGt4Tm#ibL*PJCpf_#?tm8y`S8s_Xf^T0AN(zwMcacI=Yn z-uF(cyB6`>%a$QF6``6{Hw+p%KCy1Zfu}b-&B#5&PxUGm|BST(GsAg5Y);PmU638J zYaF?b$3c$AwJALQ9B{y~PzX2{-qU^}8P2x3euc-K%gmw%lws#DD-#+X%F+vT;Bio4 zc+A@X9y6R|GD0|v0#Q>mq!-|{l;KrSVfd)^X{zuO;5pCFdL@b3a&|;3p&=E7Z)teKw(vh3nK-~MxXKA0iJ(oXQR!16Mx%ixhR`z)UCC}5}X{`0!U#dx6c zd;qqHzvB6@3&bkkf2}W5Aws2iJ`9&k@F)Eh&&OhWij;4-t$Id07I8T37Prp}r`uqj zBxPPu?6&(Xven~p7!V<0szdrQ=J6UeT54m~N^3vd|6+AphgqS9Fn~=gfP)yK*M!uC zH~bs`QAJWR&JqSowRDpVv+)UZ6~QH1;c!&r^^7@#edKUpJb{>FCote(tkk{f5=6RzLQjm&AXcR(pRs{qE`4jl4W|>7s^f z*Q-C+l|E3>Ww(r=t_qm-MN0);!`CehPYN!kx6*XGLW|rXvyvAZ>_Dmh~u*N+8 ziLcqP6s|Iv1GpLpU|w;+o=66fBS|m8+c>w(ESiKe3|Cp1z`#IAFQDP72G4Mnw*g!| z1$c(5!5|7`QC2`Nz;RWBXJgRd9~J(Sg6Exz+M`|01J7_<8^cuL7r=AeW(B}4%Hd3{ zM2cWa(1#y`KKwRtak#Jt`CTrQtX8-H$OhS9vzyF25(2Uyi*{U$9cRheN!XDXLK{gW z1qK!v_s>ekjwMekAfWc)+Hd~*i!E(0OJBFWv_;zc!VBu-_usEJzVHJ2CJ9~I|0pNr zYoYTCAsT94{3?HT7|}sth&!?TEMG8cLlBh1=EP0XNN(&$gi|vQgE}4~Ed*+p{-lZL zDX2$mRE*-WRGbu(yVGCHQ|EkKr!E)sa5c_yRv{LObDTzv;sE+P3#c z9r=90HDdRnhkI)X|hWOi$VLEpe$7^T zdHJ5O>@eBwph9-P2`dh3G`lz|EqH1{`;4-%f8aMEZR+_w5Wkj+-Q)LWF%}K7LXV|M z*}!A;Qc-*2$LUx-M6n~g2M^HuG8MHay`@L$VV$&bvH8%(HI>~)q|FE4fP^DVO@=eI z(RIv+rW;$2a$GkiE~CjbkneL%IVQT#)>~_&eI^#P7Y6EI*eQ)7tpA z-iBG``U^%&cpF~H8#znYrM2;wbQ^0S0d}-exu^Y5va;7Xyqm1nE5A|Z>urb|r01F4 z>{&6ir~Pm;6f&lcdW|QzhULFW*S|r01N8$IY~lW7uU;XIUFCF>=V0e@3D+H&RPk!S zGF%QZ@#n5kR!V_XqTw?7F1^F?OXD}vbe3>pQ)qf9E5&rA;I;ahe5KXjPKQvxM*Gn~ zv@oh`sGu9cI&1Z_y~69qUSV=l`mBgrqB>Wj!E5#ZORryCC+1N>NB@gLk@NNcP`dtg z)I}dJ>%S`$`5*gl7SjF4{d0vBDU9Ul4V<_CON5V8{bwuxQ-42-~G>P>R5JM*;0B^p(M4RFb0P2TK($? zbdB>xoQD@fDFz$R99Ooe*RcBp()DkU>HzO}mZ@9zCw)Gvm7g9Qb|het_a|NV2AaoV z#r!?$_sRVH&NOKYpMORNc)x+?(*Uc_f5x8nza}%h=baly3$(s35tvO^msxN&tI0@S z$L#Pv@p}UJe!7tFjdpI=VDuvz#eOZwE65Q7`e_cl;5l(P&p(K-@nYc*XoJ!2vNG_2 zRYFy%E7A#GtDmhguODmtqXthoIjBFcDDT4a)X&yetADKkyBV(^PJB=x2;~=@r+&7+ zynfKLsuVqA)#nuD!tOdt*=)9I)bK&uP>{+f+{1z8}WiNOdM!zb!hcE&Um@X8CBh5`X^^4nYnAIKmm^9Zu zM#flfJ9%Oo?OVHcpVb;EslZ`=VxWQ zT)17>7;;Owro4DwU0!{j6whnQ6IbRTOoTvl*=T~eJU8O?d50!4eZBxH(6bTYWfB1u z61%^OD|C@&D0QVBF>h_;mf~_?y7%WxJCqs={D0xabawioMT?S1zWyPS5G8mLqLJb~ zS`wmd@@__X*B}oOY$toLPTk+>VC z6P%c^f*`^(Rp?f5$J)7%1sA(>dd}JaJ#YV1ooq7gyXG@Q~;dTyO_raASc+fPEFn zbfk>^x13NY{Ti`S>36I2psfjKS8-z}7+IlrPRAc$bbN!B)_}SgJMVyTu$em?X15 z$un26Io*bQ-vhbNLYIFExJ0)reaYu~31V}Rp9!>)t&35(lHSCehS+W8rfDW}n#xQf zGL{N7wyKJono$?Kr{q0O8$qAJv$az-$}rlqzo z2jEBQ%J5_$OQFYTL_C0R%@~KL)KlYW^qll4W(g6(i2p!1@Zlis3)0FU)dX3TYYvZc zopv%vNBE=mAngp&Mh-EJ=Z%T$;kn23c-&P%8lsm+-0a0DgYtNNm_y)o6A$mr36dj7 zc&okI$E(%~67h*l#<-Vt*~gmXff)timPn z0>RF2_(Ap|S+ZFXbKb&Y)oT!j&Qoxi6o@@}#5pxsg~&z15%s={`LXSg&9SO!(%bxDI+kTpbOG-eFwsKn3UA%HiSRL@mh zdXjMh%Y)C-kFvRvarX?yuhsoC-L1`)Wlwus(qc%}%jfkj_>W!A=Sr*h=XO4;kRRHU zCY{*o{q0oe8N}b?-psg_GC-P^rYqHD8O$qmv~2IOdLjEz4BSI;+&jcUrH+k`V-Bgo z(Zu|S1TyWRXh&n*jp$as=4_5S+5`{%QQJyJeGTIbxF>;64ufND-QUp`;4TFm)?3~~ z!=cJZQB?F7x$w0*kwtYtyZ~;iqS>h6{;v!J!LsLsfgitlx2p1`W2~ogmup=6d zayXbn?~R4aams^K)25KZJwo8E27E6NC#&t^+pTlftl5W%?yLt@?|LzG&7;?d2iu-p z!`g?`fb)dqj2no&&_=6OhJo2;2S0@T&_m#UU{a8Ot=&$@eBPp88(xG^Yv~<#B=5Xa%kqgtSeQkNQt(`NHa?uKtAaInwm+kMt+e`ordGgX4d6=B znsjHRYUen&9JY|}xje$_#{L9$vvti8a>A)9Ci8SLJ26o-m8aB3b!**R?V{uB(opA49JPt{P9YH=>L$Nim7wUGPaV z7PDylFvD&Vp2T@msXPcx)y46sB{vhQI4`>|upl!_$ifviS+Za9xY?yvGWcH}YJ;SG zx|^=LoLRQIM3xgu1l-Za&Ls`o&(~YXOj=sR|CT%r7a|j< z=)cY|G&@DvrOfl>QfuA%FjfuwSn~^EvVS?F8Ib)yPU!$O=My1vwqJ;*>%SWCTz3y0 z;AyNIlL<8ggb+O2HlWqdctu`6WWv9u;CcPoLUv(?7YX{K)z5fEt^N*~pVtqcgXmed z1g(C?+iCSn^cBNB-2cTKVen&YIF|M|VNPwSHuCsB)7scb@RribaPvc8QXtg`IBM<) z$L@Ig(YRSz4q6svK9SjqV8PYoP8%*_3@B1=UWRL2A_G?#dEtTJbz9*GheQRqR?>tk zK;f0-Y0P7;BwP=~%&oe~HSLxU0SbO^Ok<2Q4gTZo3MlCIJDlGN${}3l@$1|L>93cq zR9{tB;;y{`H%^i6Z~a{SYg?vt#hGctD!UAH{Lqf0lNQhrx`O6dQx0*fWqG8meuc}S zPOD7q2Ac-v+E@-84Z(B13v3$wbDdUSxC>H|AyIT;W@gwab?w%LvNYb=0KpB z1^fD}9=j(S8GfDie5L_s`HD{(s0sEsgZWpcW-X5!--)`@ACDnP9pM>SEZz9{19ai0 zRg&?XY{oCVK*-!j0m^*#Sy+vWK0ET!$LiHbD4t4f4BP;ZpiJg88@PfqRAqcEcmy#3 z2bKVHdkTc)6f??rgvB@qH*;LNhQSN71m?TNXuKD~jdzeYAf;TrG~8n}8+S+nU$y#w zM*eq3vzeLs%Vao^s+;5KeDx}piw>}?ZUgiYoVvrvcU>~*P1T$3Go#tO&JI0kfOQ%| z{?=q>j^61M&4$K8e4p!fc-`U>JOGvVvR*^8dIp+Rz2jWo;`d1zyP}OA+BoSO&061@Sf|KQJXA6B~mchF|Q@a74HRD#=IUOIR3WY4Nz? zX~oJ{j^di)A^dY=@xEfC{))v&?khfAY|_fiAPP#2N&h}H1i}RaooQ$_U1m4#D92(j~i<6nY)a+H^?B(@C59SbHCx%xl{KL$) z3{e3|wb?W7Ndt- zuqadg>t6Nm6wq$N!lkb)w0kWb(C@_3b09{9+v0IU*lo941L1H8`PV!St2Hazkxf=1 zn_N<+%k2UqfFLCV-8c}QG%iDOB0|aR_r~4Go5<3^YllbZeV7T-_5;>O?TQM`p3S)9 z6j|k6=9Wbsa>G+6c1oWJn|B>cnaJ`d&eJj|zAef>A#LK_!5=7RA(gQ_it3StY`wT& zAJONj5Xgx#)oKQkZDKfF~CPZ=R9pNK16F{ zEtZPoC~qU133b`Aj!|&D*5--nY_)N$r+vb^LRGv78V_W#ZwNF6P6dCi|dN(iyMlYikpjf6}J?dSX2l*H}8ghM3H-? z=XZmMea&PSF#lhH`R{B?0e5D+htmsW^1$A27w544vGoSMh_bbahRkU%8MYQ$-bQ}D zjpjJGm>ABYjNcD(i^KLzGVC*P`jn3Qfc6P~sEjW4(c0lM63SRCXa_c`Gf8a5lq+iL z9Mx>^>>T2@cbr4J(GEMen6C_BZv=Fw0zPi74dx@u+klVkWWaOJD9|HJHnf~pt&Q}W zUIKdg1m{UE=KWHs8D-en%lc&*WI;0Xl-7dd;niU7*7EfP!DT!N;f&Sb{~u}J0UuSF zy?x(%@62RMruRuQGnq6JAR&cN0vU<|DjkFfXb>zQN=FdsB2_>{0qF=*6hw`JqLj6P zU9r1=#kST}SKU=1bMrmty>})P)cwBy?^_pz;b!i6+j&oU&T}A=X_P5bXvWiGE!6L) zJ;T2rzWl9Pe3XB`U;-oOAHSbsJ^cF-Pfg(aaW?@BW6{2v_OJTAG@tx?q35wz_}uUb zwKyywrB}b0&y6ZK&87Oi%Y@B%p3gtzwj62trNQD%ica$nT~Bcc2YOLt3S;7}02N^9 zR`Y|6y>4T#QQOx;HgPp|eVt?zZ8mn+_M=V2=UB=Uac^#Z?^@mw7Jq}l1K4&c3_vgr z(y7>}sX!dpuG}&QF*l&K8r3iB+OSaTDj_#$?Ia+MU`B3~a3f)%M7TcQ&TEhhuoE1* zY^imdkjLb;x?HfMU0wrxZ%z$i6|FF&1uHvi{Sm*1bdMudVYS2FgEVwxU_^832+A2J z^T--5m@9okm{695G80-K8N!(~_m_!run8C>$jmm+Yhdk^*R+N*%`Il;Nkfy(Wpo{p zv$#|1Df3mT(TwzN;@V_`8O*;VKqKiktx`#A zUtxA(_EmL-mO=~C7J7%iz7ADHIk<_nNRN8ig6|nfZ5nIy&=6u#L8ye;<{fB>1_N{$ zgw%^hh{7$087kE#@u_yHy3NY>za*Z`?%UnD$?@R5qQHJ!Ie+itCzYM3519JU_JNz% zDaTp2jqBMRPrV`kvuCe%9UPZ*S-3{oKWBfo`-!=~EAm3|_bFE^-KSzbDQ@@!#%RIr z7#g=5ole=1nTgUeFSI&?Q*a`ll@sf2^D(bvgdb=$%DQCjQ~DVBB5El!%9!!7VSLG< zac3_bjsAn8e3sZM9ZqDh_1`fa;nC-_rMot>Ek{44HuJsTJTYs50-e*`w(6iInT(b8JQKvN9gD31+w30D|#=-wcl$^k+s%=WY=i#75yT zWp41$6mmp=VO2f~dsw6hxoCMO<`IOS+m2p~X~Sy<%r=$$5;EsvwC;r^Pm^O^MJ(?MT!C~+o9nBcald@{wexIs=ZU%QM@YgQSvAs?O=MAUI~>WCbZ5qLSrk8xi1>atb@dB2pn@VG$mJ;H`SaC%$H)^7B8G z|5?9zIIDi0b!X$1dz9l!&FaV6ytkAUGf)q)PRfV<9zUkcR1PQy!GScYX69lnEp#++ zNcukd0oa-S<3$-pV=Uy!mF&6p!h!&FKmgohu8del%mO?min?Sj=Y_m1I4_{K)0Ujf zT=m?jt+FF7wPuv}f{yrFwEsa@VaflE(#ULIvZA{;4^y6d{cYv+eQW{-;o80ImiFB{ zM0$07tlj(aIpvIUPDv=wv3!i|4Iow#C`2@7Fc67pmhS?8{1>gjgiT zgHTD>@5~W0b21C^JpkZ)>`tJD!uqhuZU+p=Zdbc!X~7>}+5t897vtgTIlX}v+N3A% zn{0-$0NZ&%FRgpV8yC$et;{RwUi}w6wR+FG&2o>uSKl{@6%F^Y6*~JG_?Ah`6Lh%V z2mR*3ja3uR@d_aw_PI`wq7lU8ok4W=sjqWNI)t;5p?OVVw;%+eGYEPdRMoUl(SoGb z)}gLW?^7%4l6qOKvOA$w*LB+T?OgFSs;NuY$SAX;`nvNEQ*oUr41}K8py^^%yz5C1 zI~-`T;PpvR6p~~znCw&pL@)+J%VNOh?L!(4$Fnl80tJ6n_?4SS>}B*D{XnMsj;&8* zK<#e*R7srJyo)VW=I`3Ywj6tt9YM$UPoKDTDN`2EUJ!*r$X$MmyPov!rSU?yF%q$w zp?9r9VSyhy*Y9PzLl{{n6$yaXKT`tRsm&LF22hk?3;=b9+l&5DcUB7(!#w9a>_XD-?71s~%#xk~1 zb28)n@R&NW?2{K?eCO<&&yoDVvhaQyPON=b^oNHdECdV=SEeB|5_KZ>=M34hpz&a~ zkj85kJSj10I=ajta+F#Nuqu+==|FB8Y&mM^jwJ5No)!&bZ+d^+6+i!U{!67N8{7Ds zc;CY9JC-k7KY32A@>Siihlc-SFM@L>oWd^>{|YGY+$O#A_Y=>)_txt}pCCO_hqZqQ zYj1{*3+ushAQ*LKwEf}DJ#~VSyn1jKvF2)H2uX$$$*QIRh?iDda{RYWTHpApRNlB& zZnK$vOP>3=d6<(cp-cBbm+CPms1ihe(gU^=Iop#RjfBxCJdDOd2yY1W&|MDnw{~2p zRMX~0-D&yd&g!UFUd|3$BAG_2+I@PAcgSpSSe; z6|2nGSeL7kjO96ScFN^c&-N&=J#^~568kNT^by#MvR2f8enG3}bBbu$*vU&ZWwpWF$qp69VQ z@5FjLvEDfp$FbRnb>GFX1`OS`?YJ)NQU)r4!8G~Dkh*q7@VE`J@u-w6_#?YcayEXy zdQq1_v5LIwrLW~L62ELs^jN*Yz1guv_dT2rCY(SV@EP4f=)@k_0Xmb(4(=U~*JtEG z8okwOa=FBTbvA$u9s=v7Nhsyb5L0G)a`NbSKrs>~-^MBoF)8MXRab!h23lR(RnfU7 z?h2j>ysb1S8$#=|!*?(4f3YrNyX4Aci_V?VwOO=b<2>^DnE-5^{!0C5*x1A4ZDT;E z7F!&8zu)GNT9-r%uBwZMY?K8PLf}Rg+@X+kRb5DS85nC-xyF(U(QH zCht|*J@Y2zeWV=zt-Lg(x3XOcPMLFvU3!)cV>4&xByU!nITP0U7rxdxU^2-?r`b#| z+C@t)yV-)OiVQg%F&Q$w==Fu@XBPIXy5bxRkUZQ}#lx*>s!0Q-1Se)hnIynkuva-B z9(h0Ff~mKMiu)(NKh4la_;lj?y4uA#mAScmIS)Z!7K zJB6g7t$J>M?B_>~Pn$bG!D7@KD&S>m{Akn^4Ijy0z3|r5`C6a5F~1&exM|ic6CQip zeY$~NwDH!oW_PbV`Hb?!=2^2gi-!^w4X;36_{dtnF)wPdp+rIbOmMyy*q*e0U^%!L zq8Oi9XdU-Zx?B)4EhcoDs>h8g!AYi{)xxB-&*=uh{-K`2_zg;GV~%IzhScK)nStFUJJb^`3*;QC)I!`> z!Y$Km@8dOC?Iipo<&%5R4g#j=pu{Ujj&E4my?c4bx#FHr5_?7bvoi6yJZ|+y+ivGN zWvuwO#OB7&)VTp}-L zVj1Ma>1VrJu7xF&)`EHi(OOWz;=ixOZIdQ7+<4;!YXKWjB5b7#7@VllB5ySnedJZwT>E-j?pC#9A9lH4g-rxbiOcBVuCHK@KuX1Ze!0J zd##nyNvHZa6BWJ#>jCrbT%xxdiwzyzGEG4fnj3jdsRnKyD9SV- z1O*+EtM~ZIftmja!UdIP;SlTl5s@ss`)up+efEr>eYnf` zo3WK!EAQd9`c*lNq#UmY(MiaWQ{KNa?w z-GtUIN!B!Vlk#GeB-^PEv5c6bloMKTBcWP;{Kn%qzpb3weLw5;)_pIrttAbgHjG{N zopO#Dzg&LJ(8{Vu*k%9och>8{Uh&^f>u(fwZI!o_-<98#Q%KRNzQ?neTL<_!y2o=p zM)cfqID$qgJBQi8pk~#Z&1es1w(F^=5^gC}T}yFOnut#;#DjyB><|ZWnQD@jS4#!5 z=jo0c2U0h`L3dZMo+XJ}sG(oH1FOU#u5H{`*c#asH1#V9cRT0QO`)!SUa2kFULxsW z-QE10XOo;(R|swWd|*0rNjc$=0hX2{7FvPQxG#E?Lv@BIe!)`6kaWSDa}@n5D^8CzY9Kk@zi&T57Qmw%b6qb7MD}4SNs68 z*@}>M*+QNNf*KuyR8lN^QO%AL)u27og%}_+f;923Vt^?f3RzRdiahWgW*%6w9>;%c zH@J3n`@;MVHFN&5kI=P19XDywym(CkLB-ZYB;IK|{eUQokA9exeAAl11I%STs z==f2#^Lr*8-obV&KkwNB;>)|%?^aHV-zx)k3+{dU*p_#)8XM(1XD^@_Arr2KpVf#n zRt#)ccD~V*L#+W@m*g39oJB>EtLlnefCgbJILR;MWnSE$0U$z7Axt}_U_B;-Rtk#eAl4K{c;OSdsOUwJ*+&x2iqF=%LJILJ1AZy(ZwqYJ%IoI zUH3M2NFJfVcR;egayl1_DmmYV`?HB<#04>;vmOE{Dww55epe4TI}M z4G2go?)fBZ6`jT8jjgl8i6t`uJjs9B4KdN9XNU5d_SUqXh^v)hIu9jr zuSsVYVedL!nTRBT(UT#-z$k`Dhd32QqMZW(2!e9-Iu9ZXu;kjxw<}p?l{MuTcU`bT z{$lgya|Z`rX5Z>qw^TPJY1_`neDhlwMOiK%vN7N}2uJL}`Z~MY?iSD+QxXtPN`_;& zP0JHv>hM()f~?v+ZaUKIxk=k>3z45Fet1ApRvmx#iDPGu&6+y=nSBTERDN~O{qcTj zLF4L^r(SzO8mrtGn0D2k@jFiim3a^Rwus~d)N?OG~bjh4r zH~g73d(yiBr}fHIu8vK-X3zL-Crg$4TXfB&F$`<^R(BufIEF}ULC9m#=lJ#hsLcqR zaDJZO0_-SEo}gqxRKz3+povP(bjqkmX28gsAQPyZSV52oK}Qm5{}c7-q1^biSBi!B zBhI7O>9Gr9LU}{k{q7t0w*N)!`{>c{-}~VECmY1RzqG&i_4kzB9w0FbPO~9X&703& zrF?eeD@FP0Fw3q#yV*QhdEiCm1La}pHSYiD3czC=657OlpdZWhgOoVigK)M77hU?+ zNIw&_ArO>j7eWUDPs}++nL0WcxE8DJ&lG?Jv zn^!0u7AJn*yjn!29n8n@=?~)!JLta25cLqrCh6hGJ4qY_3{-Ij)LA`h!@Iu}hg8j< zNmI&JFiYYtse|$p4CkwqFT|sbvssq%sW^^ZykePhWU;bm#WD_?p?w6WaSP@r2pnDk z;;DrX0->$a2*1 zd0{mur_k0ouY|Fk?3cz5rE?0cZ}w{C+r~!W zembc9aL@gZ_g4P(?c48ug>46UFNVC2b9rGsqkL3`lV>uU(W*-hWqAVL0HUd^zRu)! zTfLGMSA>R9o z^P18Gk29a?6@zrH@k92DIR4A`R-B%|Y`q`9pLJvI^Q^ul`z$OG%Fur@*=HUM3XoMxnPT6lVYGOuud)DS+zB_K9=QfjOp z0!zOnE@kruZ9X<|*}xSWoR9dL-o4|!>F)u8E=`pVDs$Ex*)Z~ft7k5>96hn%^A#r( z0e0gf>YVn&oQAO{i*T=$;LnUid6 zVij(|X^%YKfBB(hqbpY~WzO!`Y}$X>vXT97>Q_=cu+t@7Rl&zbDlck1*LYu7F9G-=Yb+Kuyu-Ln*y2<@FZ z_%8+6=OLkIJP%Z=euvAT*TYTB&hojafWZ$Civg?QMmn{=PSP?67jAQP9iaE!h`*q! zhHo5*kIoJ;+T*xBWw}84_1ojWyyZcz4Z6?ZT_XzMHy1LN7jZ;dndOq=0+gV~;BNS>g)y7e zgY#xZAV_5nMZSe^<@D8tNNO`5=!a%upvGVjwbgopp}5V%Mnnf)w&>d4<)Kq=Xua9z zkI(CU!^OMjFS_l~C#1tS4C&d%nOj`mV^H;mJ-jjdfZ^?Gi^HyxEz9OCVk7z~!(iVL zm(k^NNbZYTxS-G>p5-&?bXak{5Xv%J(f0`8Adl6Bo57Ci?X)NhWKQCgusy>GTKDv7=;^*fo`L{3uM?mhxvWuBck#KlC!r)N-vUKlE_0RAsx-juV+WGWLgNjdKD-W_&$`tx_P?^S7 zD$`^=dtT|ZQF(czQqQ(;WR>u7pTJzq$Ev!9DH9rb}wio~2QSw;zd*v_GXWO(% zJ-Zjre#tSuuq>#?TSko@$)*g`M>rN={k*UI8UGydC&`(t3B~h$+1u*#P2U+&QwsS| zQ%K)K@~Pj02oBY&s-O4OK3@v^ZwLPzXb<`xk`d2iX29i6c+xzQ8FFU=H}yH)&+x94k&3 z8~qLVIlK#JVLYuP)hY`AW@ugz&zH6pFnd9ypsXNXAP*?0FPKy?zhHa8p#nXj*MVWj z3tDo*sn}}tDBwLk{zSIQG})q2sw8A#b6A^&Kgia2p=`ih?}cowFt%+4uN8>@H#s=o z|HApHN|PZkkIN7=2fcqWKi^&`gSbri`u|&o_GvO`^AIwJqJJU7ALpTmFzJ6M1N1!Y zUmx-UPFMil;=o@9EG~n?;s^zuxT2h>h_L~TWb^9L^&O=#Eu)lboF+Zbr5RLk6M#w* zV-ZIcZ-U#tTDn3jBV@yWJb(UcCHACRFDUkS@TT7cy}GPAUM*FN#9K`7#cL&>Vg!&xlAg`AfMY0|ZcyCLt zan5hdIc0D=(fdT4UDBjdmo7MuT|-@KorNilqT*X3u&E1#@$rraKthvqzE@aZ7rNT2 zFG#kS?y5woK-_5U z4Rmb87HB)5E@0eHgdu*@}rQ{KWfEh@v4Alx=V2R_Y2r=F$NRWWk#z*$d$B3}ji zURS`7!%SG2ODi_#fi5xc;Wu+0bIu++Zl)hb%yv<%c;x2ipL*$~sr%ZB8S(@Ac`L*3 z4eY&e$nv>2uUfcG8Mb16@X~?9M=q>-@A`570&+9s+_=#>>kRGpH$Js+>o)1<8T&^x zyzs(<&tpGdz(+4C&tJ$#wk*jv&gsa&H~vLKI%Ak>V-V(q z-{(j<^7}K6cc7+(({7;i=g;#qMb8(i&(k?!Z>V*(QW;KknNU`m zyxdjUE{ETr&CeBmAAXL$kA9B0m8ix?LiwxI8tJHHIjQ<{JQhRqK>VA09-`V@lOe%! z3^{o%xEgyRE^em#T^z!{U*(r0ODH=VrVSvF`I)H-P1sFqk?Sa;=h^Rg9(K@L)o!v{ z@w9!I-K$S^yhyH#X*{9^3Dxp7qTs(K2Ex&8wf^`J( zdieF1A7QZsfFg+pX|X7WI!xXOU227|*C~zyH28Wp?&}%RdjzQ+A4&oU{AmDy2NYL89)`1@zp+OtZSaEjUpUoF5qH(dEK&fnVg|2D?k& z(r(191FRCQ8T4}0lFH!!?bE;w}{ zuapBDiq3(A5T!(tT3$v$>J*t#Iibb0SqHOR3@M8kVW|l;=9SRLfw)czQzYa*gytdP1~47RnyquS3Uvz1tf*UG^)w|GlW2 zR6RQDd`~u2xkG*;YfOhL=FM03`P-oulkKb-!1|Q;>Q!EPiF7IJWPi+ekgpfFA2*e*ODN$NMp%-=u!?`~BEY?k9;CUu-Xs`vm)32CiP- z1mUgbCJ35)Q-vv-O!mEMU(gxm_C53fpgqmU!27=N`pJ7W9RW^7{(L@kY<{2oqKszVXM8{B`sVu$ zVSYZ=N3`c-`2N_Hu>ynU45_+R-D{-d^XPf;4e@F=Uf{hWNV!Tx!EmG@;7vEp=q&KL z^Xhxfh{#;f{Ng2I&5@?}8)_<(ZZOR+W>r=UG@c@DJk!+ih}i3R19Kl~ z+S%ZCFn(<|%SGc!>xqEzkl)G2lb=uH0Yl@{4Y^?ppY7(KQ{PA9QQvn4we{m6AH&P2 zye`q;v#IY&=WV6&^yc!N5j){MG=98A%op>s)i-Fotxe;|r|07#pC0ope3Kjx%3LrW zl$O5QU_l3O+BGS;noyC99@ezaaJTZGdruvY$ZR$$jKiRIe4L@oFNo3moY&wVMyYux?-*bimflK3Icnd@2YRl)w~w5xu)C;`VCG-vf zlB?-B=Iw8MeTHh|i6_@59H#XF4ek>SSpli#&(Z$I`)EAs`_70oe>e|#8ST+y4Y@(} zUFv>=B_}rU^-3u_^jq`Oro&xkoY1X%+T5_j=JM1i;N4puB^|azgTVY{{L1=ltq( zrE+PNW{%Qx7jT>4xjWS7EP}r3gNFoj!o8j!z^OxF`@dsPra8Y|>0|0^^Qy1#xAl^t*1o zkc<3W)EF__va|I9a6SSW#z)M^W+T6d3?0fJ0WwEt*osm$g+YaZrR24xL0)(^o7q`a zo`Cd9py?i-O_dA(4Y1$d2Mosq45Fi z`o#B2)BU4a`Ez}4D_U~rfhj-kxqaD?3H{eBy7tLwb<0QAAjHmuC~(DX5r4@;ekhV_ zu{t9WqhJk40`gnAR*OZ-_WQH5k>tp>Q^$0wUL&*SuO!cR3%aVV(jP@fI-Z(TLGZT9 z4rTeH$0x;GMH`gAGY`wGzvh~2vkr#F-81X>(JAHe{jVwSjM!8*{HhtfdQH#1zT3K* zH}1Ugn#)H-ub$mu?_*o%wc3XLLgzY-V;TPB-PkW_zFy-W{kBzPTXJ8I^DN_Mtnl^7 z&rvz-Ls_{l?J|XT$b?;pO-rb;aueoTaA$(I(rxm3GVLb24%JAqSu#2Q2)U+=CDJDv z;#xJTsR#@u9`Z%Cx6PXP<)EjJUcz+ueEkl-f5@D5J+B^g9h(DhuiUv)R>UE>g28I63+kr@@Q-DbP-_ZTJ46s% zV9wRh$$YU=Hb1JCiSWiL!2V3(`YAj@61QZd$7ks6L~BN|10elTCd4#23{Vw%6~b)b zpGZ~ZyKi~rBwICmSGg!Qh}~-Duk1bhk|+9axoOsf#0N8%Y+Sy4{nA;|^Bp@CbdB`t zQdxWO`YV{5`Nj5ETs@1OJn!1(TXOT@$z7j5Hho|H2W--3&%X8XyQfY!4lV1J?H+Jt zNBB9!!E`hHoI>17SH{bc%yP+6j?dTDo+n8~CcCMkQtyhEL`&-Hf~b6p2BSf+YqYDU zKv7|R3dNEN79odEVWZ}=#~}y2&P1&uGNAVrL3eZWV3=1|sp)Y~vV#Y0@MaWuM4bEK zZ>#3sxkLHpKMCcB{kJV!@WtPkE!w?m?bbV&vu;OrY<~QaJ#3Kf9^G?0Z++M&dym|4 z@`LwIu6W8V=g%3ve7(F-9z3M(%6YRU%+ob4y>;b=g$t-Y{VI&PweC%fxl$MqZxzhd zXUaK}$<)q~pOaJK!va+Wt+1oe9t>A*t|2P85i)XXsC4d-{FhM@kb;Ma2ql{wEKw~a z2MbsL08l;+S55g!CSEjW`M^GXR?MC^Y2gN?>8pPz8x~HSKYK-=J_DD{S#kfS_3Q8L zvqBobbk4xJvB^CTPMUWxB8N|}e*4R>-dghletU5Kq=P*t$L0>4yJXRxWm|Ua+&pXr z;x0IA-I^G$_jI8uIjk3$&IaBP5pA|Tg5E{N>~WX#0^mq(@Z}3aeeKGsHB1dxHib08 zShSmT1%YZB?-y^q?QX{=ly^6tSS??^dByqr7tQV4Hvq>Bcdx#QmCK}p z70Vhwm^8K5<+<$&Yipp>xUJ8*tMj-`u!$W7Hw*NW+62R?cczSe&Y`0E6vWM<+WMOq z;vCR{YIp-NvJV@|Q^vj)?;`G6^fkaeTf}24x3E`K-w&UohXFqCt$hwV0W$HN6pw-9 z&xj>eND;h4je(QFh8TE6j%Y?M{VV*;pE88`^B0b*nvaXe_vr{2bh zPjiaAf;2CO)#hx=5$!o;Iq@7RhrhfJBIwj>;~g_mG*KD|_VV(?WOJ2xeec}dT>G#7EX zswxn)BB=HS<7}~v;oGWao;uaD^Sos(ht?_5v!=3EI?So*vQx{suU_nzsG#8txuPTP zjiy(XVXO;uwguA3_FT&L8WV5dHc;ubhr>mg7P7t~0jYDRh$jTYE9CXU*z%$sw~*}wjhCcaU@6lJ z6Qu44+9dP`{Ay`+soVSyc2_!yOkvCuYhib(n?waBOXV%>t`x1zuVcks&1RSUTasL+ zrPY;AGxN+t2UAwp=SiX&bv}Uqcuu|ywrE?S6WDO8s+_GtsF^MGn~b@@qBy0_HDOqr zVY@#hTVz|n=El)++ie{vYm3SwoR=iVKQ#?7vuJL#h`73C)T7c|f>h0uRw9^mC})N8 zzdxx%(%548iP1loY^EkxuD|Wo6KvI-EmiWN^va}V8*narf{fE-)lukYO)0vlF{`iw;-$$5Ckelw-4bpuB{xL7kPZsn? zwdAvB7+iiEE-stP4t5HY8I*2%8MsVPQv?E5D`4n8qXG4827?QDQWUic)T+4Rd8j@z zLD8C$bYFVW0S^z8?!f;6N{>oW7ORf23ZJXOC|0=IqVQIaPAwJ86_Z!*P$nyX+u7|Y zvG)CI|7WN2H@3QQ$mB%();eRL-R~RECkj3pz&0m-zd^U42`=GpjX7dFwq@lKd)DBE zZ=Fw^W0SGWur;s8*k)mD+0Y9M;{C0enI5mvl$9Noq-?>FXEu2=3JXGlBPIs~kKGgZ z4DhV+Z1=qGIqS)=NFGmCARYjmD&Vr`nbE1yW@VWmwS>C@nhrexk*}&lgqq}wF9iMe zS1TxGOM^}B1Z~XlMbL4Gb0SlRZNRvx#kNNLgI=AWZ$M(h;(fDPylvCW9tSF^TH5yelt#m^60SJSUcV{KPX{nK-k?i$Vo z^zTj5qXp_*fS*A64myD5;+lBJ3}dFn;_>E)S=rg55VA+nW7lY+*|0}sxb`%3lr9Fm zwkTK(bVxSppgA*iz$$2?P9Jj1@nVJPilBz#U{b*XOUdCTak+&^4rq-Yf7tNj**-Oo ztUA3Q@`O28IaWQeeu3?A&)Em34S#MGGqz!6xvf{vuUo^aKztmw1BKS*^1arcZ4+%{ z*H5r)ellOVSbFKuHR68LF$|O;W5oIYv7c+>VL2lslj+Syv(aipmlmqLhF>X~0ev<* zMS!)FH(gTOlkP2Mh(t4 z0Nc5Q%i^sp4pDScTL_)rq?awAr=BYVDxAn6mQi!Gc!lfRaE!@N9ao(0@;qKk{l1tmx6zAu4 zxnwlP-wxyd9d~IqD(!NDf-hjs_GP2$6+~Ad8xRW_z>NBQ;0f}<{L#h?y~S0z2Ew7e zT%LnEcLE3}f#t*$go+yOnw{@DuzSz_+2xVS8X8NxeiwDxp5L?IX4OMl;VB((I+oRQ za_4qA9eSN?9a$metpLi72^H9@RO{>oqBUFR^ukWH2u{iGlRQ8zOF(hjGV1HlKO|Yl z=7eT#zGLW+MIBHgD)$Db<%v*Pfx*F^uDp~DInZ$1zTjhd%y_90C3sMTriQCfIv}ZCQOh_J7;WA zw=6}>E|iD6;3!d{ics=c^^oT3)%V>v?|9Y|!FTU_<-_~dvqNiUFJADh^_~N7KC}A8 z0_AzwD9Gl^-{`2GFB=$|aq+4Qe=J9*bDCpbI2+7nwiOj-#5@6Bm*N!!k1YmX`dAFv zg&=DEoMJ9m>RZ;QXjhSTwo|(oM@#)b4@=^dM>Q5kt*AL4WQ@RB;~(TG$=9Yj+vt)c z2O}qDTq0+1D#nq@u9kF+A4q#N4n`0&vZ1|xKf3s-6cZ!qidLvo+lp~|Q61D5*~jc` zFNke}K~sjsZHYv)%wD2~Ko=DNckMnzBlAOvFX!MP1isbcGJa&kDY1!=~=0jI2;Tk__c8^is?{j zwi3-lE!D3?X=f&F7$}K~ND7T*0EjTw%I{W+YjS=iW2{?radN?|vL4C-0 zuZs`0WY}3C=L6Lofg{2iz$b2u}gO?B}M$pW=*v2Y;OYBzh*&1Sim zxkZ~BAO?W$MG2FL2(=$oU(m|9y)+vZbBUXpXi`)0M8sayzf8dzXf{)sZC^DY(OeB9 zz3qVomJ|x*;wsL~E%tb9{$ksJy5eGeklCgDe7_%8w%^+d+Q^M00u3r8xY)tO6J&1J#&I&d_p*9d?xbp}U*QWkmip#HHPW+xM%!A3>lH!5ZFkC zC=_Xap2chRNp9G)ZuojoJ60=d#H=a|5x2kQ;StaySx14HBexTKL9HC_!kU|XR97%+ z@}ZKM=&CwYSCn?@w`lR>`;{k@wZv!g%=UZ2iBG5XX!!sd}BDXq9CCRNE|`>pfH1)IIS zJ%ED;3>~Tq%E~XfbpiQav*2;JTX4e&1}K%-e%LiRcj-ER*EnHd56e!=-FJx(ki+%a zvmanA_#gGpz&#fP`(`0FgVWA3ePH9r(94-}R>&^$4wgQHK}HaR69Q^PQmSXgcSy<^JzKdoK==3Qd>U2i599aWafo7O6| zRPM`8u0^xY#ApVPpQ!dPhm9G84hOp(L*E&{-RIB+on}walNSrvd|;*U`P`DjjY2`V zTW)uT7Y*3qQ8lZ8j_SlYs8U}It8i77E~jJ#@eXHDsAUq?S;EcZs$jJ&>dO(OSrhks z(PP>jb(fSEwB38Pka| zl?T|+#^ukwIMG`85sif*KLflcG)0*(EZ(+NBqPIZwb|NAC{B^um1p}0B5T?T;j?I~ zR)PcQNCy;|!z)lbrmd>(0~A_gciJcule|(i0y#q!#npTW zi}-R;0;}?K6o~KKx9f}Vf1WgR_Kl{e+A6oc{AzLMV6ofF{(kgf_U3?rhDr8ulV;wqtwNP%%I`*G)}~}r5`X( zFV+k(-Ao}n+wRlb^?5Ou$7pLAn}M$S8i$6&8Z4Q;^!W3F>mh9BdTyRzTF;&P*N=s=8$) zEt$cj9mDqwWLBkxK8t}aB1J=|2cK|S{7j>UPbc%f=g#i^J6%+Jkxu3#dO8_16my&T z;Lohy=(yi=S?_rp`M^GH4iqw>3--t%uK(J{sil-l0Ha(mWcMK$=msKKkCvSpsVv{} z6h2Se8jxHo-Owf-hd1dPi@z*bHExRaiR}0Hoc-~K&mQ>1^SEt6{qiMZ?x#`&>b|~ByQJqJ5ApJkjd$?sj37tN%gQSDiw2{&x z1Thk#`oVBe1S1tF)GeqwvDjt!6xb%%Mcf+n6JUeK>L_4^7(`kIJ$X=xe~z-`1)LMN z*@s6KJwi$JLeTtBIuZmxUa{SdNtLl%d*_e&@$CH{W~o%pxyPSp=4&3EGk9{Bc+KGZ z;yvB{`}G+)t$fW_6wP^XJrO#8vhdnxzZkx%*X_kUTXihIZZL9nbUx|M=XP-+s%Z0a zU9#-WvUn{;LMyl6&{>UE1Zb^xBP?B`R}5eqX_{11IaEGT%dIKCk@!J{sgfIDEn={R z*k%06Pd}~l!SYLfmCN^}<^SojZ@ea%^;gDNSP?62k?{ZhByM{tpr!fMG2kq`%Q<6; ziP6BV&(9a2X);R!t$m=0@iC{~X0jC_sBAZZY1`zLl6)k5WiSBM9&8>#elw49k|wO8 zunOU1ewBLC2O)h;Cs*D18j691x?S9<^Q=U1avUG?#K*Rw8hOY<<#g&TW^;fL*+E1sYypH%0s*6Bgfn9FN~mp0F+{67OxtMM+2Fg2l#z=h zS8JLit@;|p!?c@*vH$t{{P`c1y`#4V?wEepCQ*Oy!X;~L>{q{7i=w7qP}cM*_!)X% zdHU$`dk=1TKs_Jna>8Pvx+^-h&6QVrOt@Bim8!f_Zz#3e26850OQe085AdJVUXA!) z)?TUesQV1_7(u4)D&Rc~hGOq|lwv zS)+v@kA;UQ3wqC=_4m_3Q_cdPAoBp z>gO5khI#`AiRhK%HOI#e4AS60FbmdlP7d6b9J_>=Yr1jqhd~n39)?uXdct7&IC}D7&U3!l?TF)dS2Z5@TtGgn$M!E zckex5{rJjJ!|LxC*>BLOm+c$&?OgUq5BG9)oH_%>Ss~1g_iASk#XO~@+CcR}F~D%` z)KM0+9$Z%%jRtdC4znN_EO3DS_`eR6FDHhFqI9kyF4)BXk7M-#qA;+SF)*0T;D=-xJ?2hnUeg_`p8n5gPrZpHee1;ee~YcRog9%`(>Gp5 z=r?=YN?O&4t9P>q?cogI84BUAco6H2NNAqm@?=uf#0Rc_Py~BS(BsHl46M@=SrO0l zCTTX4=}l5G)Vx@YHEK=?(YjTA*sx*4LanRNnMKm!#y*SIX}yJJgUKOzx4(^hu@Ly> zA@SBl2uL8trz-_{LHpclZXvP&=GKcmtJiKv?$z!Qaa8nl$8%1K^h-Wr*vdR0NCec^H?MJWH07*)#6zs~Rua(YPu^`K&GQk&)o_=NO-nQb})sceg@{aLt zkM>?Pc=_BJtB2m!JFIlzm4dAE!RyDd3>G3D+;O8>yF1rQ=KG!fE>6t4?f5mP&VKZ_ zu}3+62l=-j(3ep!5T1zASc5yGc0zg=U*=T)JV~qU(Mavz04* zUW&{Hnx?Z{Zjin5=0~sg_>k7g0GTOw;y`q=8+>i%)w@x)YISB%e;|+#XJt!zP+yyw z83vd+B-3tDBh@h9lL=5busolZuhN;5>xX-YIB$HFg__mQ)+jfhKsKf8ab?21IX8@- z8yomUB>CT3U*0wbYh_BbwvQ1Y?f_Y+w*p-Pin5RUyD> zf+IwP{ZVL@h!2vJgB8-9V!$~`jsb2s-Lp2PW;?=SEgUTIxXIJX5_D(w>dnN<)^wN> zt*wo<@hz$tL?au}ZjrA|thQ_Jln!ey8$G>MwbxT!H2obe$1xa}?@ z5Pbo^TaX+EFr77DEo3EVmW_Hm2>z;4@ZpGX>2# zKlG~jMq<#EiIs=$7U!O;WDDo1`xl8%)U*NtI+R~mTWdi%vqOC_svO3!?G^wQ`LR0QCcv5%tNmxO2va* zro=+|5msd1?OIj8yjGo)VO*|4)OO{$Orns540p_Gl3Nu;;4ws)sL%FT5QVXXI4f^b zF=Gp~kPym*^s(w;LP`R05OpOS&_77)VLglz7gqVQVrZu>)71&oM3ygEUQ04J9#r15 z@A0gYq8CWc_c1CB*hBDl%wUeKjwAM;VKEx*j!X+RyfOg7Hh^;1qR|6-OX6~Aju^va z#j29HF0KLL)Z-r@l|25*4O6BxOrDI;FndEOU94oW&!rlQ3Nyq%eqb-)Y;%l}VA7ea zz!+sngAqnhuPSZB&?J!Q zLONo1XHurcY{)>`FaxBV60Sq?aFU_!3qYp%V2VQUgjkMZ?1k472VZ?v9Q4}Cl`Ey# ziWOXE(nM{+90e(gZ4Cy^sOxvTvkcrLce&81$Y7ATOO7^vS~yb8?jYGgzBv)xb9;kN zR|SQt^`02^p%0&vF;!i`Bkhjg;6#TOiZ=|YZm>tfLH)uj>SJ#4rHr!D>u=SGvl89) zrDap5=n^ye+5xPNI;$sPuKR`F@qBNF!y-vGluBes0Z=|zT*#ox4se>;;EGrr5~zB} z(Zih&dTkypB`KS&A1)o^%Sei{eP>1|CR)V}+6wWCeGR&?ix*sLXxOLRXQ=3WlUSG7 zBi1dtVa#lHcOvp$O$7?ZXpL`2O*4AVCfC@Msk2gG3#*~|%FQ@3(gUeA?!-D)SC?MY zM)|SXD1HhXo-E9F-BMR0K$nA`a;Q)5LWh zf&~FOL8h(+SpGT59FGX{3{;xWqU~T>pA`KWkCtFnc#MJP+8v7;#9x%vProa!@7i_r4^Y~SJMsGN>hAnIyOmnde7`Qy_ z&sMZTxnVymau^I2lc^{y6_@xOS*xcgxI&c8F8?dM-mACkJDoEKUr4EleyoD-g&vfgRLo@y0TO zHZQ^@kyGfj^bybixOQunUnL$#x5eh0K^>YV`oqMZ2P+HW?1NyrQ@ismo9vwqu?>63BLBTc{w%ZOd?A z>MJy84#hxJm$yYu7Fvmio%rp+PcdC12xW>CJLZO-cDtnXwr(=bH75MeSAsNm`9(9u zgEZfXJLtcMwkPh82De-huEcja*U@@3?SOssJ;s+y*m}Fo5D4UmxwhQASO{@8xP)eO znn$41Z09euc}}1-K3&`dD%I^x7u zFM;L@u!ZQ-_tU$!j(1kR{3FVD>N@)_D=v!CKLPbZMzSUTiG6Js+QvQ9heEI-(-B`Rx7X73Q&m+ZhSD{fdgdoyyp2B0Sw>(QHX@Z zI&uNHgwx%16+(~<8YP8dh_9%@IVZ3e)zKX0g3%al%aUCYe)~r8-o4V4IBZ(6Zp|vQ z*vE9cn4^5Ie1n`b82ptV5|sxJA9_q0{$N7`?rNT^0FIgb&Zuxhypv0iJ!tG=*JmN+ zB}?G5r$$a56#RQUdNg)`aci?15Fv$8u0=>adK@!7rjZ{e^+53mDR)CT5`u=I1>O1l zlKBG`IDj0g^xAlQxSFF5&ZD5&?Yh%SFQz1hO?{d=6Ew^a*D8TSsb&w87b^EG9yM7V z*5pykloNxSTRmW0h}Y@9!nnMMhe0bFf)U0%wt@mrP%3T}@p@yv80J0Z&4xb9_WCgU zzBF@96FsSyRggUit!ibyOOvHdU6_j_qv%Q@6-P)+Y5JroH;-HN{hs^3Uo`IKDU*hx z@zbp@9$BzIJG5`X;g{GgV&D2_o;-AP{pS-We!l+bp(mfI7n$;?^1*jXV&2SK<}mS} z;5+R{dkJTZ`sLa%l5AiU+Sn~_qs@lm2p@v(7Ac%##iCffMqG|YaDV6lveVlcGD|G2 zDFjyK0$oQc0{_RDP(16f=VOO?5iZp^z@t#;0M-+gOz`^Hr%%7eR_=Q18D-oD*H4)| zF7fC!<0p&(hWuV1bAhPx_j0yKnd=6@enJ7K?X}ViFTCh(e9ZgW$s@04Nj-xvlb$GP z`c5pt;cyGZaktLpf;;Fz<6pPUjFzirubjLNIX91b21zc&qe~G{Po^bt*@^l3{pf0t zTRY++%H>X)A$My0+sxZ0Pq_Zd5~OlZHi(s}_vv2x!~2B)@Av6mI^Qn+ebRIMeXxc8 zgYo$V@WuPgX2u)=eb8d_+iY%d?R$;#;5wtG0aZ<%RDjJ_B}Y62Ff++`2aT3c4xM`` z1v7Z+nudG6A8-xZ&BzdRyLnV<$f_>p`E-|16nE<}$0$OkXv<)L7GNjA%SJ0C?j>na zA>0mht9ezNxE8c<;66yD$|udh-up6@C_mZI7)$AW{;q7HeLPEt4prINegs)_bRjW> zICCguv|_-`HxoZi%_gXNFJ16r)e$hj6SzQz4%O{Ta>H$vZ>6qjq9p1D_Q>3&)3YK~ z$8&kCLUtUQ)*#qzIy5-|pqTHhl(&HZ4c*+Sv5q`8zcaScoEyc1^PeOvz#V%MEAV|0 ztY;9qgMT0H3%wn!&1pn@7D<`W9MTpgLxJa&5k7OUYj2ualb7Fl{;`pmIQGy^eB-rG z&Og78^epZnxeR+G6EdN9huLJb=+Qn)K%T~=XFxF)ya#J@Ve&%h{L@l!jBx`Qfx1MSj{?^#O7QUokH>5<`@8~_3w%SfXt8l~LsRqQ_4CAldRnE=3jCwS5`kUA9fcMcQ3vaOie2`BvSs<*?;lpm8<=&%)DM;K#YOC}QX6<+{++vU zj{A=oc~rqHVg5V9_90v`Mey37n_p$fYOO@yUVUC(+e)c@bva^p<)K#kkmM*V#N{It zdWrHDjx_FaI$$IkMfFor0qs_&sl)CghlFC=e}y3C5tu^Wsl!7%GQi&Rt(pJV*x@f< zKQQr@j>WloCAep^hYkEMsNt>~@9(^AwPmNyfFnE)b{=>^#eUH5emb{kN*S;P#XzQ$ zJaa)eSmki%cPjap6wZeHYP0~ZOIpI(100vVe|1gA_FY2j@4BINyY782-JlF}ZTGhx zdcC~*z+=ump>gxiPj7gLpW&uw@ZKeOZ-g@Gz*>Zvz@j!ou0I+<#w~)LRG^a&Ij|KS z9)6fW^P0NgQvz|jNxes^)$nKXaJ2~Db|uzc-N_AG>&i*4{rfj;(nLMEQu^qTLtM7T zUMnwdct{mEpY}o1Lde(&=aA10@Z$k5;BFRl1F_fzC{{X+TqN#$t8)V;Hx*l@qCGGx zwAfIttYPDtOE2x-uJy2;_xd|jcC4vZhCO&#%3O49XpeK>!N$+89oE{v-G%o8M+IE> zrI?!#V(|mx#R#Cn6UxlUEDX7d#N0rpjF3qlY;QAg%+Pms9BmoSk*vyNf1b7-Iicz< zgmkHC;##AWa>cwp8oDSImIA|^*N0n|wkCNU;^B*j3_RXvcApgs;yqcOx}p2)%ZFd{RF}y^W-Rr~CG2W-eOK4-Ti&7lRsGv_!#N5!HGw07>hZh9b3yE6 zHM`N_%A@z{G6vS^bXKbwROnU_Pzjhq7&f0xE$c_p120Hcn|f&4SCn%OJVT7Sa;cgx zTsJDMor-Rb|KE*oQ30By>sM>#`z+Px#Z7kwfzDCvlj)Yd9u)YXtBKi&wkGs;Z7o~& zcHz^7$*LmYnN}RfFAA-X36;WC)aLb6Ci?#^-#~9Zf0Dmh6oRn92SVRwVGRk{37Q5` zr$fKNSO`56iVAZx0zufYI%W?B-BOm#jm)6ii>8#Sp+pKU4b-LGnOybq=|I^8{ejTX zY3@1YD9~9;Lq+E>)`|VvFkwQvk9EThY~8|p6l3v7>T1YCXNl%!9kT(gZ0z#nRuA=) zqcb23!8qDu9Hl^Xx5^7SGK#>d(#B(v+DPrnauHO>wS_!I88X5n@Np4$h9cxJ41PwN zwICk>zcqkNLrh;R8kVD9HRW>X2M%5XY`I|g^O2QH|(lu(?)GOc5K_IY16J6e(j3l zaCX-|GgiK#Bxp>;vGN7RLhknzENIy^9yl(V$9lg?8FM@8$Cn$j^1g2V@CM~BiW!F zYmV+;z?z#)I$)o*rj?FngI`@`pv$E~8}M*!)7xy>iKfa};eU?93VUAs&*AAa5>y6{ z?*>3vcM{+27pPB;(dYNe0LKJOevj2Hqr0_%T4I*hqKGdUh~pc(!sYeVGGOdt2=o^k zBptTczwNa237TfGJ+iu7b;Hn zePXt9srtR`U{X!td!}eVL=kl#(_wl^ay0~Zs1Bx-9^pdVh7kw z8Nk^d7L^_^sjkZJ(doxT>m=`hcoe}&^9~?!7HGT!bT9lL*4_g!ifZi}pEG59nQhr@ z>V`B(0tpF82z9Aal-@$`0)kZOC3F-iN>Kz96-BYoLa|U(u8J1~dl$Td1uR(aRkE|+ z?>V!Z0(#%?{eS-`TV~IkIpsM|FO4JJaYD>e()Gw|#O2mm4+`56-)F_MD-8THX;I{o$Tt^GdTD zV()qGO3Wb<7Oe(wcTY?ZMX$l0Xh7^kK$3tx5N{4xflTSJfS(s2PZIcYUAOLprjxeX zO64eDQdheg5Zg9;*cVM3@yeWN!%fGBJD_bWkT(2mX+Cmx&!~^MdJ{IU7trkD(yvT^zf(a01Q@mcmXas9$ zwF0g3GtGG&o#m-krqbD_xYQer#x8c{ihKPS}3}QsVt%9?rVLiKH|tLsiQ^l0>9> zMwb|T`OV*AChH9=Xb{lzPjG1$e$lRKm-#SW`1qZZZm|sO(H}hF8{kC~g|bkh11>oR zuP@P;lQKfvNBZfYYm)Fq7;|NlX-cCczX;O-f8kw8~Zp z5~v~@5BKC)&_c}0AC+S1=z<-nTS3-cyKHPGtbNJ{PiiMWh?S20fVJ3Fr)F&UiC5~D zjeSac{`0!!Vt0Q6vp>d6`q;05f2f9KnlJ^riUcV^RgvsM@>(30dg%oaWRFBngzBfH zcXFoZr=L%k%#tA?J^|k0&IBkT6B3}n1?%Q?IuP;Vh)SeUmK#1z{1Z7h@k-K)5RvQzdmi!;>Dl5q_wO3?yf1b)?N6pV$#?-a&Wx|ZCw;W5aoUHc9h)0@4lo#-^M*Ubc{qR<&WJfsgNA0AZD5K;_=v@~m+Gbt{q zes+r4W(IfTG-C=7^N`J&j1z>a0=2g$rY{wUN)pq?103`{;wQSU+Zl@caL@)X7nLa7 zlDhWcnCsc}Nz<3KI{MqS7eD-C@}zfO`0{r#@6EGO$Fsr1hQGQAuD};KTIYZXedFsn%G2liR8DptGAD2f=Wnbrw>bn91POyQ`vj=L)@7Z4yLzb-6M zx|zMpZeLbesb#(cB~T%j$~}oK{b`qd{~sDC|i! zMz}tDL>d>MIxvj8VmN%~KK3dOCn>jdKktv>N0I~^0Q|g@Eq;GoI+DmYP;>l=+?vcx z0R?&SAP1@fDr?mioXE$UKZBy?a1QA|0IpK)8(pX9Z@P;jzN&K&1DCLiv4E~v(cel6 z^LjfL(uMpPdOQ~kMoa$waey7(dD|edRV<>5)*v6SOSCVQXWtPV@kvQ6IRUN-$qqQg z2&t6H3|t&i&H!qhC@0oh#$KQ}qU=hB=bc`wmPC6!iU3pLp02vJoTxS@*EF~21QB!k zm+S)tEhD!|T0U!e0~^7*Y6mN|SD2SsZsaF!pSNX;j#s*4F6a1MkS8Wyv8IS3YHzz; ze9L;5fUg)|L`2%C2%rY5#dB*dQxEuVuIjdDA5*9LO7;PEH=L@+O!+l>>+$_uJg;Ul%|FIiD&=V5cA6y-xqg<%hee< zwOsSrOWN(ot+rZBiTdV;kw<{`jLuL$qX~0uEVLBv3U&6>uirF1J+6VPsUtZl$&rKT zEk~ij;BcWvN2}z7MjlKW-ZG7R?i>~IH>%SCtcz4X&B?~eYivtWF_>AMP(;H(_3hLr zuM0clTxmw@q_w*1%^^FCdz ztsk&6{qD&to;mVJFSa~<^zUP-XvOwx_XQ%tOUu7n#?Im1(sfa1()4hjXX=Qhd6KavrxJkfiCd!5>2+C^2=Hp;RlsF+Vx>J*pGoQA4N=h* zu-E{4meI(?1=SVBICwptEFqzhy|B)17xbfmm(cucpp{2Vp_ z?{F0`l!++S(V^1%ev2jDmz(QW9nN$nIGDxYfC8yea()6d(FsD1UPId(ayf0lHnupf z`iUvbG4+Jg)m#Ku^Mb7yQiE}50b*7Lio&j5h$0>V5mWMe4HAN~Y4U7;RG~khoG-w8 z)i#L5SuNiyiWwquv7fbAMD(v=)u*U_+w>Ll3fr`bOjah0S zn3`JLuwnE1qCa3XwrrIIgc=McsrCKM5tf#l+q6eHs=QcSh|h&i+h$Q^s01MMG_FO| zG3%Imxmr2T<;B?k6ON((IRH?68uw(qPW604yc*cm8v@p(EBj_Ge0c8K1w30@i!NO| z_N$if)>ULpez7034&JwlhpyeUaX?$|=+P_Zi?5RgJA7OJ_Qh-7s}}nZ!!~X5h;RBW z@2&^2^&iub_=05Q!o62{0~iI-dWFGYW;(o+6O-I#vm+@tO(|+p2-cv`*`QI6@&-ai zdQZq2AQnoZ&kCW*ny7$VpoB}&^FYo+)t_-Fs%7}tYOtv9(_cHQW^;eS;@ZHG%)|>O zsl~S0`ybQYpYDL`PQ}_v_I52Fe!1DR6Z`Eu9dmq=(xCNz_7U=o8eP>Hx$Ndfo%=@+ zEn>o5kW2o=8EAysq752CA(G;X_nWh`{VwEXDo8PVk*xxLUvUiuWbA-U?p2wto`@cF z&NAysY<1;;PE_=Ih;g!x*#6!qh^!}B*}l6fy9tqX8@K;mA|dV1kMD}~R!(2*)pJ1q zp0SgBL;9dr_dat|i^()E)SraT^h?ak1X>Brk5z%b)B;>*f{Uwy&^KUuI64}j+^Wqt zT*oUA(gNT{xdIovt5T{6w-vvv8YCVh|3#wj(w?f%rF#6C0f9-BchUwfrei4E7I2D+ z+mMjxL?k(i&3cR$Q8F_arac~m>T~#P(40fh-J1R&g%rIRt;)eOm=L@GW29T5JuXUQ z^uJW+i?V#(#^uN7uh=qg>+-oLYk!fIjp_Qnc~2I2D2T$NWCdvhmPGjvrA5}wL}qqw&DLul3MjY>Ah#e zSN2HL^jGN;YpuP+zJ^CSf9J554P9gv^7;iS*Hcn3nCde+jj52@1UVV5w;<0*ZYD!5 z5mmHADNHRTJ8e)VXX402N%C*B61i@p2C9j{!AhmVnR0jAqQf74Pfg_&&c%|Q+yi7;_HSxZ~rs;aE~?blsuxx?jv-et)-1(|b|m2>7) zDYuO@jFg*nk~(4yZe^F*EAlUhjcpi;gP#qsnbqS}%!1i*soacu2*NjXAUP9L1LJrI zIriuXJ4UH*lV>PWN|%mtNlBUAx?{b&Obu-vd6RL5@va5hG(4+5`;;w1ol`f>NO8Fs zvwA&>!63V@@TYw%)3a!aX)e*HaYl*wYu&k*2QdJSSp_{U>1O`4?%j|-?hu|fyfc3q z(|tVRgkvm>=f%S=oM83)8FL4W=NB@^GV~iwJnexr_WJ921|w zI9x*Gklzl-wz@rL_^8^iK-Z69$T)Ga%I;+p1h3DX52X|=Bhtl|GK>KO!LH0`oL<&+ zNUrwj(yi_CA6}weYP0B=X_!1+N}^r~3F zzbiB@VUaAcPM`s1AsDh7p|la8$FmyoNytB~hak}rBOR}YaUWRh|l?8Wp+%WWBKCj>9*@(^g z1u%$t8SIk9ZgId-*jQo5c;1BKNvB;qg6L!)L3>+JWK7i}(V@xkjz$H3Zf^@HkQWtBt?CzZpPTlBT=(}@V z|MKxUDeUrptS28k_|o)omgURWuH0bP9^|k&MtDH(B=1JNCBjnxt2N)#_rfTLG8#c_P`V0OOC3arGM&cUZ)R( zETRt?sqoN8K2eicAKZ2>?s`HSyNh*VU3Yyi{S$peeQyMLpSW3h2QZoza+*z&gp!j6 zM0S`q4njXswusOR-_7yC*cgiIwvWp ztH;(%or-3#tYF{ecf|opZ?q4f?vo%ZOk7(IR|VWe)*hHiFn-wbk!SD|F;jjQes#iY z@2XlAI}7gN1G5Ek8nwwAeKb$p?TN6$AkU#882bp z+JxA=MXDIcR&fi3QAi9SBoN_rCS)P@F)G|}rZtU^Gx)~zY*%TKpRF2WAA|qpW*>ZT z?bQ!Zn3M_oq*2j6+=mdyi|{5mjV5!mmcVRHwgPNus?@1Pe-C}n-{u=L< z{x#m)SA+J@-=FC3K&-$2j`uRTmE=aeH@F7vG2vn18PUWv$Q;3t!+`QWOu~{NNn{0q z>S<2)xRea09-)O`>xx8^wq@SbdCyF|bV;9Moj!1aC8V-6%}R|vj#lDXTT$~l&>&` z#-T*yiL{w|NW&$uLb@u6PKlX;{{_RkC|{tSBvsPXLe8t9FUZVA!1|>{2qtK-?2!A8 zq@&L#-{CtT@7uk4%a*mfuy@;(AnSp1Ckg(L1$`+DY<8|GziMVqbE+B1iOf<+@~tU%O8HW2sk^jDdPq7h{RpH* zNQ3i~G|ngz)YpvSGs*J}l%aMYis+}pk!wefS~rnOM&8`K+0Dulx-jPMHKUt$>(;ci zS5LlIJy0w1S$y)qMZY5Y}~MlL|z-9#4$N=jVU~L1aZN`x7$Q@u} zfZh6Yfc-1L-VLx50k${59tp5}1FRyzrp0b(A7D)aEF-|;0!$9DtGxX=-hO|q{WAW5 z(E-*kz&Zw4N$deafa(3e9bhK|tTMoM#s)Mqz{c|SZ3B!I1XwV@)BwW`rJ)&k#sxkm z+_RV8voXMy1=y4T8y;Xi0<3*(OlXhB7h{cg1lW4saz=oS4zND4mU8UzXdt3R1@EhM zfaM2RYJfQd%t#4e>2DbRO|AVmt?nOhM~$lgYB#3Ob=yxv3ANgPHLZpGLJrAi_=$`1 zTf~vmv1a)E~ihon^H?l%!8~2JzMAX>DD1-v}>?yw7f5~VP>nc zvEy6feb&a=IAd}0J}IDF1nsd386nkRf%gwwOihS9-ZTiZ0v@fKs&uP9aor#-tcbtH zp`hxaFuln8yeu_YyR5$gPQPMf#01F(yq^@ZipU>kayV@Yvk==HV|Foqsa9Sg$(V2{ z*%8eg^ma}+@!RxO?OPy85JT!Wq9u~lP~4@w#c^bu#cC3PK#)UU#&E5QD4UJMHlfli zP_52Q!$3_wFGiH4!CxWze>wvpYR>>{?Cw!c5s;}5!#WNtmDT#jN0gL zf3#TpAt$eI=Ptdwtn{Q6CB`=q`-u01{}c`3$(M$cuJ<+y8?(LRrTF0O`7Asc#?G@~KtMVc)^NI*{K z`VE3MzvNH8Sf1$rPO^ZFmLPuD#8dF?iShCr#yGyz3#U`DtL&P5>`}HFY0X7(_11M2 z+X55Sk^K*(wVZwHprZA6&RyrZ-M6K8_mVa}=JanO9-nwezuV$kmd(s)m>NtfxutCU zqyeKGTKzb?hun!8*o=%n)q(eKuif*jrIZmOl{J7?Q9x zymy5cAfVo*3SDxKoox=8v_B*ea3+Qr8RhYr+6yz$PYb3#oanD8;dEimfOQ8D!Wh*WfwbyREWmXG>_2r?1Tjqkt;=HVAu%&T@*vjJ zAexLatDrS0N;spB8a+dO<41bf`MsW;HhlOr?I9M}z59lHWdV{TZZ?pFQCM>?YJ5O$ z%cMJ>?4>=ldp8Tvy{~Ki*cYgvYaks30=vMEn2^P>LRSUUf@sBO^!qZ4*%!NaYhN4m zZmz!{h?I#8QmDuu|7Pv=R(wivX*TZHzTUlik3I#g^%}(QBuC!md>SAk#;5tD3a43L z}M zVAgX;hNDnjdC!b2W{}UYSp9Ti&46<9Xf zA!1~89arN5y_+=NnK+6%({8HWWp&45zfvvQ8Q6DL z`#el9$X{2Nc7Cf(6c0sagX;zDBp%NVU2ugLnj#t}@bmu{+OJK#f%d<^?qv%(KTa4C z*;SyyFq+JybQ6)IIiD)+T1l zA<=u05*vE0`#qFY2G93G0@vPtPur_MKLxgW70>7N1X>$(X)?=v1dt?Yv#R^hu@#L1 z8JE?tV-@=XnK)Pv>nmvv z&45Fv3fq9^nugq>kz{&{RSenHPEb;sjpXNO#TTPdH6t<#$8iYEChD2OYTF51-O~I} z=J`5 zs+NEU;U={G>V~!i_eO0K^brPIOdr9tgU}Xr<$5dipd_0sNK)buP8c|*rea-hR8vJF zF*!&zpo$^ZLHk~Qm$|v_$XZEiBP?Am0bOw#bH}trXcvCUkS_KyJO=t=trXZ=E`6dM z0z|@3$xj;6mn`9Xt9>dTVj0NCV;3Bi7P-R65A$iz1{VpsJm!H|?b+L%<87mE?gBQ( z{eXOE&8+5~Z)!Q08@E{Garr%Apt4whmZ(d*V0m$X23E@NaT!*<3Vu`yN#$3ql)UXPbdr%%a)r>0<2-_8eiz;2Dry$fLyD1d zwAmHKi;ha-JkX8+6Kjc<)VgHq)X(%Av`TjKb#rnbHdR266r%t`a9?eT!ZNf&XFmc( z!7EmR6R9Xg5dn7xScYLV;Latu4>?qe6otJrg@yUoL!X4rtOJ3`mGXfY46=Puzur2q z3`L<;rrB^e#vYtTi0oB3ogF9E~+W z5$sA>V=9QZfB}TSIc9|!lU0A-&;inwCnl_IvuYuQ0YDC{eV@rxZGOR;S94aQ`F+ai zzr7EbtRiZ*q5EQXmZ1ZN&a02QC!Z`_6}o5K6aR0|8>EdFo{m%i$2kt6ZVtE_!26jY zcpk@d-AMa7K8EHnukP9eVF%a0Ue}(}c;mi0s#mmctql_%i5$c!PYZbuamW5)2B|;=Qb35ycFw05k)vA7)7J; z#4t9QAv6*|TGIy#eNohEI`5(7oU~>$sFh|<~Nmp7r(xE@yPuPb6%;U=JI-!_je!mK}}rE^-slo{QuXq;s$1)MYd>V!pO*jz&XAU((=g_ zf?1932}eEXT({d6jm)cm$It<S|_h zEE6MT$u*)LQwlqz{ZN+HICDeGPD#maZp$7zRMVc9M;iLo&k+xemU`=O*;jzD2Fz?b z;1nysOmQsgsZr(PaF8l@cT|%b>Rnrpo3WrRX>nQPDAueU&N2K-4%5jMV;n0TeOFPt z6-c_rpdr9BwUzidhx3@=4){R;lc%$mbaKJB;PkRNcWSAt#GkePr%vg7wYFVb&qhTS z!0RhHWOrFC@e%|Gr;(X(HzG?2|MRoNGrndO7ty#&YThuV_*>c!9Xz5@qtZ5m+mtqH zG-B}3Y1);n;>8nhZr7zlhc4~APF#%fJ%{mihz!CWC5N1f;XJY5Mv6}mjUY345;3rx zECY{nTj7|~7sh=KQ=!)XN8jqzKBDF5qYdvP5%sGL=S3z$a)fr@h$S8hoW^^Iv4oJG z+khqfeB6c8+C=K%M_Q*(wP`FPG81?QI0Hn^;*=RGpz#s>4{Vz@Ek-i0@5c(p4=Bup z<27_hA>VA@-;>O{YA7bt9>AP#1upO-rbUJPcAhYymvr+Ysz_EZ46V#xsc^UdjRsfi zf1MOZbN^m@k+~zyqj+a+ZM?&IU+u+31WVzkwDDpe?Fw%vN^8q;ya_)=d0%b3&ijhM zWBq~0wsBmL2)WRpDnX?{k!s0C9Gxp58=Q1->_!;wW3GLnF+Cvs1oKuH^LHO|D_%QJ zi!KfmM_T&7netE9&6sHqGdCLs3<$N;K5oy#B#43ygCFEE=3Ym0eTF58{n(c4+lc+H z^_QPyNl-w+=oh&NGXEvo*bX3motQi#zh;)rM4@WbC~3R2q-x{{=|QypD)JdiRler^ z0R!iQ^C7!FOVtM4!=6;W{^NJ}ZQ^V{s&x|vM@|6_1JI2LPCMIUKW-Q8oDRJ?L}$qB z7&?c<96Yo~YgsyJe6`R1bz&vWSeGW*oI27Z={gYmExZ^xi?I?eH}0m`uXQ|eDjF5i4Bb`!M-JjYyuoVM6=mt0OWF>v5ZtCe35wZ zHeWgyEhzhGArI$^^H;gLl(rb$qO^-^qB+;UH!Q%LVrkqu!*YuuE63lsxJAA8-Rw7;GV>RbxgJ%X4D-}O*=j@Zp%i6Ct zW_F@66WsD{*6sSX%!evX9@g#T>^qg0o!Y-`40?Q~?S$ko1a?3lEcsSd5#T>BRDe;# zA&5l`z?#{WXO`FX8BJ-H!XY)F@Z<8^|Io3H|ejzn8wMiKh@iuAI zTpCYgn=A=q{)z{z;oZmFzyb=A_LO`UK2u*}&Ssng?_utDg=Fy96p#a*#nopOncQv|`0iI@Q-6A5K^+aZRUyqJg4}I7eZ%YqceMlb3rWi$>H;Qj<5OVw6irpk6 zr`Qv)-@tq+fokO;)W=}CphzUd6NAQ>u@dfk;I{z;q!W28x980rQcDZXMKH_>dn7z-u(OT2P!HK?3p=};}Xc6{e^wtwUI<8gq$|WvjUz1 zrz+vhRDP&{)Jq}bG@rR|-+ZnA59`+bK=;LmVYCu2;~eYf(I-&-?uLl1YAJLOx(Ypo za$$rpUYIJ(5f%$~3ik+GgzfN)*(E$38n~m|&oqZh4f9Q$?>90@b4=OU)mV7w$qGefb|ggN4;^02bz=9kC>k zXNxkuh-HHJcXssM6Mgqb-&yqCc-?mf5fS_}7cgUOV(-_tz5aXbnXzZZo}G#D5PzZ& z#V!&rPrATv2_Mz=vu8<97W%n_4{S3&48(` zwH=TR6~LVEb_FWXElF$0kfh9xT9OFwJu1%@26MkwoDD+-vg5&s@+LwG2PNg9Aj#0doAOB;Wv2~{FcXH z9ZmZrh<9fI3)~Y6>Ogp9{nj))yJ=Zgc2mWmzc>fLVdY`C}|(c2RJ7VC|50UAwg%dUs%jx%V4IT{Gu4ShTRg zoXnex*CN70I}V)OA#xP@0;-G$D&kb2>vbjD8%UBL+kB=d{|tX|6BSWtuzK7e4IZ`Z z3gdXrDM}**b*jQ&xiX-Hbr|2Ryr`(W`?&Gl1{M|$>^?61dds}L=FRi+TC&TrPrr8? z)4B7QZr#Ro>NKWXQ3)=$EGde<)4Dg6Z-qX-33wQLC6f%`OIpAA87-caUW)pi#@B~U z4MhaJD2uhP4zPp+xTiN_2UkSiz*;*ht?=Z<;e%-(i=&8XL8*u$;WA2<72k%>h0lJw zd?@Sk-FK|(&=PG8o4#b`OfrO^5Ba#V!cfTjkilW14_$Z6{#zf0!kSLdhmtI9S4f_$ zBd8%CU>F4O64wD(gv|Hb-9XcardBd0Im7lqXId?x$rbKq!*ROA%!5bf~ z->@GU^k}ZHV6G+lTrCwQE{%}#fTd@lW7(MQP75k4mJihqe)pYrXz23GVu6@1ze0<} zrlSje&84%T?FOv50&j5iL1t7=+d%`0rL&u>5~Qy{*eA|}E>3#^@i;5xUsR(qN~@qQ zaP4_q>%^~(*GO_FUh*W|eOhw=AA$gQIvL=A6ji{3e*)EPF=x&k9$PdD;U)_8^Bnr= z!uy#<$)0%MALCjB@~=^5NBd^l8C=Vff8{bCih;t%-uazkHj5X_%@mkJ{{8R|!Vy=Y zBZ?M4)7>Za3TX$PZ`;p0-*Mlp`yYFF>$DBtg>iR|>?cjs-s^m`Y0~mjCy$+(J<)R4 zD*pV}@vLSP1E!4AxM>0W&<6+uNXOjbfTgS^)gy%{;EQ6txZB&C1Ps!R!Ck10VBB9T zsB7L0@mzA8c9mkeuoeM4<6FKKQ^GIOT41k6VNH+oxGQhS?5+;B;^9wZ*|;&hYRi8Q zuYz5&KRYkHpsd6EW<^TrF?jZFJiDB)`8@3~UBjFY;Ti�BewIm~1i1qDi>|wQD|Q zspkX*QI?E{SxW4+S6^Gkls{G;Idtcr%#2%`X|J+7*);7#R$p7E>HMMahy0XS!udQ8 ze6z3jjo^-Z(jT{niR!7g-D+Eh8(@=OeU2?yh~vi4+hYN6>Q@j=4bKVPHMewTL z;r#0fTrirc1gg_8*|5lP)*w?Joa?Hg!r>8KcF4n1<2p%Y`Q{q28S6BYA84tdJ? z<<1g1Lb+=cx3Zh6BFNL5%$g+jNgSQHEOBRIW#YSu@S100B15jcB*B}R>naxV;on(I zdmNj;J4?cM>O(mDKlR}iuIuB7_9t5`H&fxqLogzokzZzCz$ZZ=-);E#i`bf&tG&LP zXF8lcCBK};GBULbv=I{gr}m1g4Q-;lelyf-KtPJcZifREN)j6l#`RG936>KE<}|c6 zh~OfIKpUKYChFxG^ZNr~Axo@8Nx{Gj`~K|k!a?oB&8+Qad2bq)9T`G~c*U#0 z=j%wMB4oaGa;Nr6A4Q~=2S$zUL{|?C9!Bpc4)h7v5+Dkqe4 z(Z1Do9Xk~$F!`hGkgA9+=(dopY4c7mz#TrX5)Fmo z!En=6g*~L~fz?ETuRL{d*zW4Ygst2ef#;Kg$M7(6{LP&zj=%G_cq36|PS!9I`Nn#GJCz>lBYfriM_;m$6Lb5<>kj zft?c=c_|m?4#D!a#tKtkKf{IJ&il$0zK`~miM~ogz8;@;>u~kD`4hqR&vsYqjQD#U|EeR**s%6lJ5=h>?yB z#TkvUk#-k`q2on@Tv2zt7(B-N0`f$>qihCj6;|w&NeyILOHQZ-XnbohZRClS;P3o=c2dZRL{i=9qm&%N4 zsrqmFUaI!TPqWnf)UQ?JlK%9IA7AMvrA0P=T8W?jttxam8?A3b`&sH?^)+<&wQ4o; zi~Z4NSM<`qRg2A_#;Kw~Em3Exo7KnFuT;a*e(I}umker_x&oJ8RTcW!FWTae`nzfh z(QPH_SX^5Xz3+F`lBIT3Lu>ls@*&*yJ~cF;g{UfOrrJT>q&}t|R1H(U33{Eg(4_^q4l)7P1YuO|ezj3`Zi5hpaZ6ER)0YUO}t@G7te?vUMsPOz;LJQja3c*uc^2R#4mTws`<`6JvLu$FW~|Qu=(GyOWn`qKWrFP=COjlasd(8V zoClCkje!v(I0T0%rWo3wzuN#s2C3NwLHJMTm@|+X_%RR($nWL{S_it*CqxQ3`RDTi zd8adQHgFy-jF$10xI_ z7Km9DuETYH=<9p^7bL=JF1zCVH77MFP(FYO=0 zcP8g6OF)NUQQ{dxao0a$+l~WaF@cL2;G#RB;X7D zF%XTKW`K3Z;(%#hlvJmxGAB-sr8C*9J)2S_np1PNXOzX)Y-`pyrJ-FOwgo3osM=XQCymdkKK~9} zf_#ZIXN*ptGYn(&Jr`rXK2K-&P;Lr}0Y&q43c=w)(JCdT3`5d0j6E??Ms3bvJZmSW z;L$_Qu`ZN#7gNZsDccL)-c16ISCrH1Wl-6fv_iQ~>Q4Apru`48JFEUBb`2l=pR#wd zu{$uYqu9Xtd|r9g^K8w(H{yG1K_FQ-?5znqlVL)*8>Ad4wUPXJrnCY{8jO%n-IFb> zz{2uT#?JywK4b`OP!TqU2f}eBAEn8_mVnDGQo5be=J)BN%{?Wyew9fV{`jK`3maWy zgUFvW;>MU~% z3L&3yvzRXpfKp8V7RY7SUXur3TP$6yI;629J|jSrK2SDe-wXMA z0QLzEAe*-iAz7^?8)dUeFf+kiVP0?E0VR}V=In&S)k!}DGW_-kVD%)ElvIFLgc9k} zX-IR)Bf^H`@@)CSHT3)EqB`fRh(B{8=KV09X`|Y&sFP?hiK2m_imKg#%y5{o8I=PO zG>wQGECW4Gm*a3SqO2apdC+rpKY*fQxTpb)7$uvVC?cC>O1>wMaPYk$=HGySZ^8HEj>vD|S5`xwOa;fXFq9XxCwUEq43`lx zY(_iElvn0l%n^6wu#BAcIiqtn=E!OeOs+W$c}!@bk}UR4N$KYF;pK`sF-y%FUhY-l z`k@-!5;n3<)kD4ZAzw!xYgK*nv7Y+rhy5=Xz5}RyNSRZGH156=6pSk3J0Q=Z6a#7_ zlFsIbML+E5{ao*de^|(nrlQkxH^DK(e?nVd(Dd2otBlV-+qB7{L*j)k-!J*{%cbAn z{{u4~I;8#QCpI}8cjAQ?PKcK)X)UleN-kduG8{5!(*;8c_9+=zuFAZAH96TXkn+lp zIy!P{`_yHr8&l<-sgW$jb@q>91Rf>hCqY8|PlBb-W$;xpSiC5bk1 zc)9u`jDR+u12Lks8bw9OSIV<|MR`n|Ht>N@&Hi{lcn^?vx* zCC6WTy6JOG_r7v`=`RoWW=}2s{yuhAYxwjVQs=6}ukB&)XpJ}huuSWVX<=UdG0#Vl zlQ>=I9m+`w7_0%S&*@7@b6TV{DI?RF2^KOX-exi+CM4n*Cb$J4J5UWhvj)f1NWfKt zZ#3l*(5llAjoK@8wEUAA2;jhW+iTx;bw7eCcD|sZpJRS z**P~m>1KP~Y=@g|bh9aLHrmZXZq|f1^&&Bhn~nd~%`V28R=U|vH(T#!6>c`e&4#;K z4>xP?W7!R_QD%_dxrKbyK{DqMXud`0^$LF;O)mp`!9>OPeA*G(Fw`+ z_}c9eJ;nC(_BwA`_7=gfi-F4{^shozvLGNhlM8~;F;Q&}&CmvOn8lybO!yqnNP~?X zCA#A{dzf@2@>69}Jp)%$#MndP&G`JG>q!u@gubDM;Kw{3n~;`PKhfZiGsI=rN0uR| zX45=2S&j3ds1}Oom{lt0TAj%f`Gr~oL)DyrqJP*U{tCu`EYrw=s@GemEH9!l&P~3` z*?Oi*WWff?mb$jyGv~}pAHB8kKts`A_+nunlhQo@aD2KFJ~?gK?8g$4@1M15&iAY4 zFfWT|Ni1Q-oLMs_XlF+5&`#gla$Op0{Q0S`Prmc+DNG&nT8r8fouNPTA-h(2sG%p- zm1;>!5?}@pP_YRsnVaJYrUsFCF&K164&+;O2d$z4jTs?Q>l&$cDBwTF-JW)*) zH)_iyTdZ!I5$rRZoQ+o5>j|Nti?;w;x%FNd`kE6VyDy=W&FFSJ6OnZV$sG#wTa#&p zapgozKZJdPmZHd$BKb=w1rYWpy`yqP|{CHfOTd$e@cp!vQCPU&{w`(0Y zXubH@wQB>lpJtr+MPF}TKNIV16N2EJf<{L&Rf7)*K~KPwRxjC}g6!jRQe27 z(VXL;5j6~qo+hb?RP+>>8Y|TZC`l1f%qE?R-?&SOv5|%s_wIAx*!I=yH))XvSc^I1 zn`zpGe`(>%Uu!RkpRo`6Z&}(u^ux9%k50EyGufu0*jno-&wzVebEBgPm}C`5@efprh?N?O+^-Gz=+?AvW);0 zBPLQS+O{SUJRJrgDozAB^~SU^Yb)GKV+n0^wL6)YeD(O8+3WpxH6P#O<7Ee5+BAK_ zf`2}CZ9&E0t%HVddt=M}J#U*<-f8rRk&kb>eIx3-ts1rDq2~sy?%QQh-<8?@y7nKT z&#RyIkTe{7ks%BU`I73@Q_ZPDf;k}zb&d((Wv_4~ay69diR-jdxK9v2PGCu5F}d=8<|h1n%MnZoiU4sf6>wAid&TFLQYk3{YSNXJ(yT7R#~wTwh^BR zr8^6TEH9Znuvtd_LVqx)cg6HU-Ahx&mW#AqC=~NK^rQ3I^)60#)+-y(K5tU@rm4M> zG72(M60?fCj%mwQh$Ccq=_2@~rgOOGc$Dj;ZwbM+;KCUWSP~N|64|;$23@WyB@)29 zkq17I=<=C;D4XL`?M_N>9!<9vj|8qHP=cS*d2~LDTJ_thd@a*!E8;{;WqRS-jNLAu z)aubWNQyZ??pIy0cHYG7@eQcZ?zNS~Lref2F{6H^UO%>PNTu4b4gk7A2qX+oU-MB2Ah)k5rCFG4zIyS(753JT=e3-5 z%arhE%Hs8Fml(u!Wr6sawp6>DEnqXmAGCE2M?oi)HK zX5q!qQZTiULaZ_b-t$(r-paaLS!+aRTiJvktqhkQva%^wHr&ck@-Sj$xOmRWDtIGY zOy$>0@xxgw+iPVzcpt5;ET8{|rYHFI$+4yvYPG@5g0(s`gU%yu$5>HC>9nhvDX{cMU z#5sHxKrY-*V`=N6bhTOn0D5eM8ozoTN)TNv!u|6X6zyKPYSZ6IUQS_Y?+e{ce3`*K29-+LIb2ezu;sio$oru9rWu)r-B&f{d0v9_YOp6(SqJ*)@3kaDu2eEY|H1@b zBUajf_P}m!?on3CERFXyk-9yrHU9)<7KXDytjEgRM_^rvPD}t?Hi0~wl9-#}G18e3 z;;pKhA!HPnAWNGc#icw(2XO1vP$me*EFfDI@6h^Y2#Hd*;0IOo`yJUTlocTH0Cin; z7Y|E0>Ar}B^!w@4&Cjl1 ztaTCRo_+b2b$5OL8xfjmLtAn}({R8NPH5Q31HYz;)_bSkx(4Qir3>OKZtF0xiEU}> z(G7n~cB1k?GVF&2p%*_R-Ju_%I>@QWT2$W*Wvk){NXc<=sQwX5bEf5|A#G8-;_#ue zlFx~nBS|&Mt7~+av2DV%Ir2~tJLN(;0%Ek>Eg3@gz$g|`_y-OO(qqbFr;pWcZ_?<+ zCe7OJNQl3!iJW;kr%AtCyi-?*?{>PR0~RYcDQ*>8bVFAEi$WSai22|De6! zzH5ut2(kX*H|D!X`{l8>zSm}HuXO14E}d_J!)>7L^@LkPuAs{z$6Mkv(uZZRy%|i( zpn>}_a8wc_$aBW0#*2ci%A(646Ii-%(JRMG_O#?=@MOs<+?e>%1Hw4H2m1mnMT%sd z>;rogaH+jtFHZz7v zFU|dZ&f=x5(>p#E)(Wd$pclx+8! zQTWR2tSxEO=7Bk#=iDwT<2Il;8Pq+35s737HLb8jF3_%Bx*8EsbT3XUdu(IRn~u&e zKk_!qx$i-C^l(`is@) z^T)T2XD8y>x_BnUL(+?<<#s{2=>|l*QMJnm5JBJ!Ur{9Ej;f`mVlj0gj}|iqzo^^5 z;g?c!xmZAlU9r7(j)1rA^s&<6Iv`8XOylD6E5_v$+J)RssMG|=o)`S=M?V|xXMR2+ zP$8!u<8xnEaFXDZEU&T6#c=fEN}LNIsuL|csa^j1{HcR?tk|%iW1e1i@)Pa-cfZuj zPM-a#f;H;P%TD4P^~E{*SndV?hk8Q4P$RpFczap)C<)M$nWc2#MwX0pZb;E@^ZQXB z#IFWD2Gnww3~^LXmNJ*~A|dpeA65QW({G)9z#xf#KpA()G=9$a`+0F1ex&LJSSu!f z9RBz2pMN|558wQX4fj3p;I89G`}bJ?$R@EErOrNZXye&^R-8P`*DNQvD)HkZ@0~J< z?O%FV`}DV$-qC&uKWBvIiwQn3t%-6EtnHLgz~y%PJgOkcUJDbwqCYMJ=-bcCFdm%? z)yJmCsZ3FsEm2^00YemmHfJZnBdIb&O#&m;L-E070+fm;6^@=SLKH-}^erI7uB)Ll z$>)V+`zWu%^)u;Wigw^dc4T+wc3Fx3dc|$}-PU6*GrfQix6Q|X43C$}SpR#vSeAN6 zKdyZwCWddS`hdeO;%AgRr3LiR!$ZvgBo>R$3Lg_BGmQ@v1ct*RCLyQSxd7Wk`NRqW zA(+l&hlHfOD!5o1_!hG*&Qfc4;3&ay94KiFJp(t>@k233&O;g$>`+FueyQJ8BsDsG z&!L~RUv_K1{d{o!k=?VzHP4DG!t;OnK-6wyo5c5j7MF$R?!HHq|Dm6^-k=>Ip2H@8pK%&_S3`J6{2kArEksqoVYd`DQD^Ik|=RSG#;uc;R; zOH&PcI3|dlpywpUOPwq_`eSn6N{D z_0X)_D@Eow68Wgofs`$ERI#AH92fBWxGqbWAi&E(zmrs1A_-EzRwl$pHCeh6H%7Nf zZ$@;RY{aYq?jt>(A9wjX`V;g!ggQDi();N;voZnWruGa^!N0gb-S&Jhxn6EwwEsx7 zeJW($uKn3BngfsjZpi-Uk}!g!Xg89wIBgFBM9%E zg46=A5rzUofz9K|1;-`WPzeGBK=?W1j1yI6Ba&Q3g)n0U3bDaF@RJ#QddQaUc&jVh z^qki9q}cw0f<%3-01&KsmXdRH)cfCFdH?axCjS2C(EH2xt$TFN>cxv!-&L{XZgy~R zw{D9YOkcM{&RM!?M%N+dPoMeMX4Zmbuz759%NaeVEf2qX&jVZ6-@AE>_;^XnPMx{~ zZ<;Y@$_be(6bVB@g?TwSLFThI&IlM)V}=#MAx+#snk<+HbxKZNz@6vLQ?j%3dzNPl z3K$UO`uT#w5GJJw$8=RO-9x%pG^C@Xa4GP648hb?Lv|Nu;oOopuN&^P6u$9wGXum?38wD-co=7a4a;s2D7pT8Oa-k{HSVc zm+Z)~9A|Oi&F&Upz>676NMfp<4bCR<8eIEng#H?-fXu-AioM11=6w=|W@a=FYSWH| z|Mfn5n)NB2t*mKSv?{(~azXg1YCX22-%Wu1Bv|5>E2q_LX(^k}p|dbTS`*`b7Ef+j zq_^f{B)f=&LA#776EO!Z_=!wPBFhNQ)i#_fr&RkyN~a&f zUYqLG{-cF8dn0Y2^xDx^eN}J9|MT#kH&UhCWKsr;LNDOq@4;XAg@(|G<-=Yt8ggy* zCYK6S`}37Y20GQ3?ST@a~h7wdO01^ObMG0i1XIMGXvjv8gqnaPwDs_!)ZLEQl=E@MjP?NKTPZbmw zQ%RwWD@BEZcpOO?jOoHeM-56q6-gE}ihhoc%H zaNeJILNNL`3*Zpz6es-j`P1yue=eVX>D=<|53X6g>5=A)*e{5fPk97c3VJz11FAcVuGIyEBD*|uwa{2co=Xm1|77C z)YKgmY625Jb!QY6$~wX#P?n2IUE&kFwaDtnSmv=$So(myMdvrHIX9VEueM~#woTU_ z*v{-*o;=)c;hmFTntAF2N$bqvbRY!5H?c-}@bO4?2*E6$&lEH@YM3qf(yi$zl9rxm zMFCBxmGe6K5}^c8EC(2IX<*Y7vJxN;xK4pyFN_#mUqF&-F}K5rSL^q<^~odszW-|1 zXUWewW{$sSS$tmm!Hdc!^)G(s!y%6r-oCQ^%$c(qzwN z>a%2Js&Dz#i|dnvA#cO%U^84Acw1qLTl`w?woz-jar7kT4(}uT?qHrVL$_@jYhA!D zKhw8+LtN~9SH&#tjsEN#eNKhgD+~6@46G543ed=fhzd!d$H+O0s-z|paykP6lD3Lq zzIuZ&4W1PM`Dwrzr^y!WyO+NSqiXfb--wsRs<15H-B>&sewx;;kyfr80*y?8|4u!Z z&1giiI@u~H!BoU)*_;lS%_SxJ{fUX*;pK^l!$xWXec7TC6JR2?W*oACoRn@Zl2-R)xaW{8gI zp<^{XBi~BB^i0f8CLJa!US-#Tg@sBByd;WA$JEr)uS2HKSao#xJA0};(pL{}k#jcA zo%e92H1vDz(W8TA%v!4~uCi~LzyCq$$e*8`{N&aRk5-ls=-+SDb8LYeUuC~<~adsOD-9rts(vw&oY6n{^dFf8MQGRk^%UZsJ8#{xiho9m)YGUn`AeeO(CR{ zgq{VYN$*AxA}XR(5d{$iq^byrpr9zID2N5r6%~|VL&S!Py@0*<_w+r;4*$=&v%5(E z{k`vBB#qt7z31M0?kS)1Iq1-~@3vcHS?+k}jH^}!rH-2}-Lme{(KD`CgoMTLJLc@% zEMC@Jwg2-eH*DH|S?+$+&K3+s7m7sk5JFFw?7)a=<0-97HR z{(JX+vg=Rv#}D2dIj34a&|%x=E%DlX@zrD3h{^~ifBW;X!UEbeD|v3^eb~UgV+C$Q zh9Crkh+=2wz}|0QnGRpB53Uu`Et!JZF`@ww#ujsj$EPVSopvt>wU?A0?qJ@C39X;B zbUyT&)T#R3c{{hVjOtOHyIroe%@T{x8Hz4nkFz4@R%NX^PwSR-&9M%e{@H@~%L(8s zbHV8tM1CdLVF)+^p|AoZpIcEJPN0;>H{_z{Z2f-C2rK6!qNBzS8{*^Ps_tNBBe7{Y zr@Gqe5HM9}9vR-k%AR~+;n|f>KQ`)xF?;v+xoI=fxtZ_u(sYd-qE zqFrLB?%KpF*`3vA)vs^=ZReHU$u2e{9{81f3p~^+_-obe6@wV6l=E|RrhHR%2ei4a z1oNzrSDuHye7KLx1LXn1W`n0@^9e=BBO|z34>t3pWz6q5y6ybv!kkj2JQzfd4xj~o zy#e7?5-;(bgKq>&?)O#0i$nEgsHQ`A=GVfqxGW0TfA62sAu_LD^!%AGFX=VDUtLzW z^;1U<88dnaGvzhySN}a>$70@!@ve1e$b*Lr?mu=`)#`8K#~0>Aat2&{MO78MOOoK0A!v3ui`p2TM0Ns{AQE6$wEy&d!@+=i-&sk9s9p6yl zak&)5-q@fJ%#Gi1$xmEox9k|YCOjRwOLgfbf|iDviHVAaWIF&@|Alp!x*QJWPb(Qb zWzoY!R!ymYaPz(K?58WRUhFSsuQ~srZH;%%tbb|G>xXL|>;)_IC-n#Q4Yp*^Wh1+l z7P<3lht*HJ^uih8hi={f*xAoJsEuCJMp`>7~Orv@zj<7xJSe5m;CYSUMO|0?ST1$DGi@aksSX#4ste6-&*_0DhWqjfcl!TTQE0Jb&sZ z`v

I4Rt->%i!h_a}~TovU<=bnTHhspIN_6Rynf*y&8RR!vAd8^=zso-j^*Q=NLl z4Imk=t|^#MJ$-EB6;mI0;0roa@Xc_h=HuR0gs?CmR^$o>^;V92$cuO~8XH`K$LWC* z_IS`D(>A`rEJ?YI4bmwv`Br+5bRXschYgC(>CQ(YBn;hPsKxi30#zg6uhq?0?^Rz> zcdJjXWaTWA-Js5UY~S3#-9SFqvmtxG`02OLzx(#5uf+E1&+1#OFI+e;JA2EzF7u}> z$Ladx%>ysI^ZsWa(LK^1=cXOcL3NAeX1iQ^zZLnKNPY;Bj8I4d5j27VP6@G&RGy4f z9dusmcEgDIn7sh@!-Er|wbhEwuGzli{zvj0tcET*xt;p2?|)7pFWckxtJM#mz3DGQ-$}hX{v-jBCC*MVw&Bx3Prr0e8OedN^^Sl$nBVYZL=c%aBX(S+#Wrd^BDT!2}LJ< zlX7+MgRP+=7I0cDqRy;yNglVA0a5{f#c6hs!b#;vlH<_C7Ip!+=|VRvxGFm0?T~We zo&0p+{xbt0JM>(LQ}}RmuM2`wx3w(9?z|vN{S14L31?v4?nTTaA6-}xV3ETff6yxJg%0ya0ii9ON$n z%ZktqTP6{ZjuMC5*qTr~``P7*UlRZRa<0Dd@%Zi6^)zhK=MP!Ce9u+AbERng)ibu- zwEDJftJrzJG2=bM)ZYJ<&Oqr&(v+o9WSsoYo8qT+Tpj}JcyWLs@7I7 zpw<>K|JMwP8j5|}3HGo@=o^cIPRi*7#zw$ZVk0Bi1|$J$ zTMY(Mat?-T3K7IqKq^&9gGGa7#4SlWZN-eAHKfjQGb#A!EM_PV`|hiszz4_l>hCw) z%4VzAArWN*g}jWbsBp!@w=Y&7IB7h+V^JWU9Dv=(h}?R0upJ#bj7O*Qz{y2GUa??Q zh&i2RJgLw$sPMueqR_)xC-(x1ON@kv;>i(=VD>F5ket&OgtctI!4R|u(dI!Kd^pYp(d6^&$PQ-PIMafc~K2zcPVIE=+I};s?mSi zWvkh-nr;k7kZ-3T1e#s~X-ct=dM0@`s88!3W`U18IDKUwGuuPxmG#8NzQ-5ozDP`7 zwd$^`#dXK`i-%`khB3fq#TW?VKzQY0vEq=|>oD8x0JviM4ymw6k9H^kC;OeiG23D& zLAOgdLrI-YUVbLAf57O0@@}gLHSLXaLlB2-D#OwkVv(VR);>Mdftn4+=g$2*z{Azc z?!9-J_}nR&2dwuwSkH;Ho(%Q?zE7?%YGbj!0GGPJ9*$bY^y>=fb@X#~AOAV+7ghf& z@?Av&;`zn|8x8pcb}^SrS3v2tXXADkvuG(PD#=dnF;)L4p26Rf%mJJcYv)vAiWpLc zDhrgI%27opC>2VhvRc`v{Ho|*3@NZt(Z3l<0wjvRk2~#xxg|-!cpWF-A7v+g)DOX) z%tM}_z1Nt_g26o0$QY&k2qDT~qXp!^_y&|>WQU||x7jtmq2A8yW>^B%yb~~O)MUy> zNJT_&J!m%sk^)$VY~~DOh3dFZ){Q5$Wb%p#b1ELX{P@VvKTtQ1Thn`b*M{?d ztNzk!uNst>BC@tQBX7^s@2XdJ>UmX7;f2#bnmsqqnXt-VZKO~emk;V3^}nJn6#5J@ zRoSKZ2g(WRAya)=k0@zNj*q3*40(}7u^~De($VP5lWDbiZ8^E9^|9axj&B$*t>PWm z@SW3-azPu?4}L#6)h~RR)7u0H#pOgHRSw~7COFy;^PDg zl(2!zA3-e>p0pf899LWnX1 zWSv1QnNo!GMXk}I zs$V3YP#Y_&$FIJ&bI=p5>D9S&=XNNhxaLQu+qY$lx`9pKvXR}f=X3cpb+tNEy?dQ` zy`jggtQf5fdnO)yYu}X%RdG3=3(lzF0nEjX*$j=f19hU&5eQgOAS%d4DVXi>qkz<* zg;Fg>8wHH%e558r#V*M}AsLz4WU$yRh9<^L>Kh;Nx79QZ570D@B%W((61z9C(aE7d z%lc|VM~|J$pxGZ3N46R;;+VKo$}wKAaAB;g0L6;VtVXZVr)>7^^zHLWn|()p;x9fX z_!@nyeVc)lm-Mnr#w1WxVX>QektS-Ku^3tbFZO6aJJ1|zaVg*l=@25nf5F2G-poFLbiBk_9ZOOFSyhhBR$?({HY8+};^9i40FjAtC??>WZ6 zt0eehHU=6DfCLOaM8MMTz@4m4(gh$t3+$UBoe01e5&7~Kyrx(Rw%Fjb(qRr0{mL3=bK z6?e;s<)yY_($|67X#-4eyA@IW-Fg$s@A9)G-I)lVdU%lDQ2V9NN^(8ZHRt?$UUTl$ zlP<=59FT2q%*T%T42-oy?+cU5CO9N8C)!K~ozL&^m;2#l+AtX#_MoCuFbTJTlklWG zWle9kXay2+6kdmRjB4Fk@G`iHFvz#;h&NZoR}Y>V?Ij-GtzK6dJ-dE$;s9H(P7*tw z2g>3I3eR)p9?$_ltM^@)ygzU!^ED{Km>JK@_r;ha!s^(toGh0=5XcVdC6m(`apmPj z&?_9Wjs&G>etDFMD6=#P(TZp+x;ZN2meA)&ddU$+#RjzAp$Y%aL9fj_afP} zGS$4dI0{BD<(DQuTfjPEmByiJPTNOGeSsa6I(qmF*BW;yrhpHq`w{6yRoxjc{-GLf z#OPcRX?S#chC1p~mZ(^yes$or>5XR%Xc}<#gvqZRP`_GK!4jXUqcWxzHi=6zt%*^R zA@U5nO?_kQOz!)-%tZ8_39z zC3kMFDJn&ait|uuiRggCUT+selHD!@1JwE^5C{gbltHuy9N!R+TpeY7l1A=zp%~?l@Hgvgw;@-oRpCk-s0W5 zQOJdmojCPX^*^KUoy`mzcJ4dax9{T*-L&kQ2df|L!;A+GH?Lm4e!KdE_pUvI)L&OF zSaQYM*;h@z>hf#5J+$qm`{&;m^6yyu{5RMNn!V0-gN@?AHW3smCKHNL?cif6cZjes z^bBVY+`pI#saluJ4Ge3>E?QiotFfURobIB>>IMRz6g^ce(*bk0ssQ98#&Ef)J*U}R+fKKW za3!5)<1n%bXOKxJgBmGXXaHV9%j_Z>*nL^quyOsWd-s`` zc>Y87EL-qeqG{A6S9=RWtGg7A-!1KK{#9}we`fPsYB(f15!1DK`kd{h4aP746F*18rRGr<2?T4WUdH%@>}-~=dYDoSz! zWH$xj)(|Iv&IV3^F}okOnf`xp0??iRBG!OvioymNiE*55nOVV|ybqn}=u87=%~>2| zH!h>3eoEfwVFq^8$}QDGpCOch!_i@n1L@S}X#zE0DpcSfvq@l`udAbF4qu z*b6qc$Ho@h*bG!w!E)jTkO{5EVFU?8<3X0YmVrp}y=e90soj}UeB8u^n6@>s?{?$= zltjn|;`&vcWFXtsA&iMtSRD?mf(1<*Wh0YZ&I(t|)##GSU8s$L17^dTKp3Z47VzQY zR^@hB^8VD;=H%6((HP3($4Z;n+q>CYZ5K)Yj4u@6o;d$MU`%9#^^WD61-IL1lZ~FxbC1JF3VKgO{3Zc+u=b0t$$kc?%`Q=`V_$2EI6Q9hjKhzrW zdhs&&DetiAEgPlMxAwwQ`D$ZxX38zWS%qz=pNesrp%?t{)m<=_K~&}RTQX!J14=c6 z0LcN1y`RAWXq7?MDT+?=dSw|77dHu$3r5NW0C%dgpq@9XIgeUZjMJ?d^aKaVzdZyEEk9miBC7YBQrj#Bagvf_R zN~7hFk+vY=wH{gMbc15C^oU-#a*+z-n)cYmS_}?$-IcL3T~0~P^!v@S(+QMzPIe}o zx$&8+GDQ@4TBOWOyTOjE6oP=*dqZ8TU7KA;UB4nY?{e8a=&WOtBpd;R0nl0{c>pk0 zoKBe7T4Dj;a;n6(<|-9i@C+Q0YRHk0#3La>9pWDa`Qpp#KP-K`FDv_u^<-1-R$qBf z9sT6S-e2GJ4zqX?GsTsPdO|+7?24`r?NqN-cN|f7J<4VcUa^5WzP|0NgeLD3KN=oG z&W*Tp&x>`mp*|8*@d{?#_Ti8a#_n)orYKSH z)&SGMlxS18zLJv~kMkfQrIU1H-2xsVQ6O7`FuM(pZB`E-P(M)bb*LXSHL-SCYz%51 zYT4N5|M>XGAAfyt-+8KQ8bMSq=?o;U4JZLyiP;i9wde~(Mt>y2`7hPJN zR1iL$RYJzfYIO&NtesiEX5n~bxluXb*3i|o9u!yT)N*V!6-U~ITfkbjKuTs5s6eH= z7*%9s-?}sD+QsWOt-E2}T`T_h^sQq*|M^|ngxh~$*Qn?Ib%9)Rko|eE>A9Dldw!4j zo_bt8rv9ZSunz}sXKeSpfi-v4PI&j7t5-n(5N6{W#E~6f`40(9S}PlI52bS!i66_tM-cjc>RpotB$_E zVCuvx<<2V>ojXr4xUQXi^S!9495CwK3rDc$)P4QV9zGDu1zmwTiaRjJBxf4D7lTej zG6(4egvT*14mHCd;5A2F;tuuZxZHVBbAP;rueVp7BroUh_XvGr`8v1TV9+zCOsC8O zuUPT&l(54sTTND^CKU^jh;vP%L6v#DC^cPt#-mUvZU|F{I6Ses76s&}Y3ZrH9z8q8 zeq(*^X@2XT*#oAPiHp}rPoTtT>P?BSxVxKmHG7p!Ywpi&QredRzAyXnc_QYg8-Z~~ zP&eKm{wjBBvvLA%cV3<&!ad$#L6Yy4o9wIh+{nXG5Hrz`P>tBz!Qc*Q=TwXX37;t)t}}I z!)ny3Wm`GKBp0%S$%N!SO4=VrOXrk7EPASuLrJ;A;)u*19`*YJ?7Yai??1h^XTL6G z@tledJ)eL2{c|JiyaVd@o*tQT_KDS~I>e#b%USPVE;%=!eW(^VtH-h~>YnA zqm>8^m{5th=Q!j5^M&hU!>u`4s0f!OM^=_8Ps)oT2>?mtfE3+=&27!KiYLg11LGEA z^o`04MRrSy5+G1cz$xg5HZ*dtSo%d~Avnf}@{q2=v^8Ub%FuA^F=K=Z(nlgKfEFpF z90B;k;hGkY7;ZAJlhjTMJGk}5(oHolH(shfe%Jc*&uzHWdUqKcuKjAPe>=bMKqFc?C~nxJ+?v#mn))KBIg*ZCDw~WM2S9~5GA)3~1&`=5yF5@B zGmzd{tw=MF;}HcV#B)6KGrdi6K{dG?1}G9#_j3i6y0A&yngzlc?LbnCjIxIJr*N4| znxtOWC3fc2(N*m;O6q#mMcRw6LwyWX-`IGjZJ}uzUT|B_Hlw8w@xDWRzC$4?+D z3u=#Z7_1!;`_T->!I*8RtKAl!|=(J~F z!Z;16kuA~biH+Wvj2}pwO-D^)Bc~_aX_66SFoQOblIVKM#o|)n4o%BA=+J1-mtKmi z<#_n<{n8W3y9dvvI`V9J5c2pVQaYb=YV$ll_7@bGw2Y6XODsI+At3)F3KbsN9_q$luvR9r~Sa%Vh$EY77Q zpr%6K9r)@f;wmxW?$|}uLg)7FbF$0@fCmdw&mKBkZiXC@BDE+TaApg>3Q!C=UBI`w zeBgBIPWj(0J=xlxY;jLEt0%jlCmY$5b?C`HO8rp#NvtOdDSEO{PnO%02|Zh}nWg9$ zH1|yN(QrbzHHg!ZPR=CvB~%CwNwQGBE<#6!7j^jj)rDAlLp4WiB-yXDxFhc}VQ1M| zExo?`Q}NG-=ylt5y0UfCcR$9a&RAEf(`}b@WvgdCvFq47Q?Iyot~7h;l~d$tljn@P zq)wc>aP~AY4}~mWKRsrI+Q7C>?KSCa=3yCP?b#Dg_2$3C;c|8mo7e2x7OH1slLeh$CaSHk|7$j=3897Lo9j0R>lm;nigmXvfrC!y*< z0WGDb*T@)d>#cH&1EvOvgf<(t`X}+5?`eJo_ELY^ZDd{G0oYG-@$8NG(uLS)HkuU} znG>A@+~7F_=n587E_g^Kqk;~66mLugFIw5&w4r$|5y6f@vd0~EVR=}YblVDjAE#OIZ9c3Yfnz-iOwFFiq8k?Tcu zlAdJk5YCTv!5Hjzn-)WXVs?JyVsRI1ga;{#*8!J)~01-+Uk(cgsiOmAjU>9jEiH6)nv07TyS0eKFS>-juDi2 zfqo%*9?GzW9o8o68&-*omLXQK$0G6s?}{o^47Onn29g+EEv;N!F1b`bn}$fHDmOSN zx=|BOS>q*4vc2}!`}@_4)F)Zq}HmAvFDE)19@`)LRW<` zr{WtGFqr|H^!oi+xIl*4gJGJR%x{=suI8GDKwHAUGbl!f*I=+D2T2hQo`5~o4R1BP z>XyMV3|%bH1{l{yCv}Z$10?OPjZd|avV~kppTCzJ<0Y|vG(rc);Z^1wsHOo_ z0xZND*$foX?PG^b6)}AAl9qQ+B0K5a#6(QefAt&)oNdece{zn12QC?ejRz8|#Fa&vwk)1(XL^pxr{irnjO7 z3nUe&eRY6Wz~Llj1T-?VV6&T0iKR%WflZ-AlOd4grcW0d@RP`|39*psvhY2uY-Hax9R9-eX*_-;%(ndW-ZOJntek(%k~QZ14Xv=-&HfEVN3TgAdvu^whaaWMs_vwSAGU8rPL35%5s zEom%~VkHYoM4_ajBvv9hCA+&=$pEiMMiwZ_MI0*-o$K7F4|ZeQ?lM^WxgDsH!tI~j zjNIjfQiEdCl3L2I#c|eNJ#1i7 z=X%R)bd&rdDo(wZ(g*v+qLFP&foER^P3y4H{GD`H%a`B*PJA_lZ+_t zaDe10eaYo|h44OP#4${8ZS zv`fN@6A$c@02DR3sBO=7o1@)2R>(ah%2H`TL89lOL@i`V`CDR75j%YQ?|*&sz^BluH!~^IQsgofJJz->WA(axE8`ZVj1QInVeRujj}JbB z#v;2Am_aSP$D>}b#?oOv>HaVIOed;?LEiW1x%j648yX+GrHkjcbd?wVJdR9nwIW zA~8l>np6=jDg^tT$2GXF_(9VZtUjqLywDZiVJBm#$Y%MTzn;fUptHB*^&CS*VUs6M zJO~VxP1i{uBnx}-t`+)ANmYHP9>%g)blH@=}LU(T)^-;nLeKwdq=V|TkzY~fDj zaMGS3mXcS8kZ(wFM!Gn$C5hY?cRhhuVZ(!hfZHgcWG<&rA5Qgz(3Bj0?nYj|woWHD zo%z|4Ux6e~{IGletU+&}qL)p1NNsvw-S+D1>h|~4riTf~dGi~CX3gLIz1qC^_dAw- zK7>`V>(omJj%K)>DIYRvUf&*+`1w$M^sz@DMGMi72;kYH@4QLs+v>4x>akU44Tl`i z!CHP9XICMtibr(V?N*D~Y&AGVo!^HeY`{4*JM=`Ysv(dtxwH?Qv@>z&z-~uv&#IXR z^+;6jT`1pLwR*+o>C3jOcR#@_Yp#3Z=!mfua_1{oZdK3yjxAT`zyq1a9%f%QPrHo` zxOF2OL((M*{IqY8w{b&*4vH0ftVTHyKyi>$Z(*prmg^L81|V5WWM5(&-kd z1eGs?R+mnj1K+0N;#dueK~PUD`a>0aJg=I}ssi-9q6zgxm$O@wsnt8xSvn6As|(n2 z7L&KX_ukFl^W>^Ov4E!t`8ehKzyV9+L>-h5L%c^as??bg3{waEHD(k zy*3991&#)QOC}VS4=5~9M52HT9qv3K30Pcbxx^&=Psy>gV-CM5S@%nt89KT}==>0H zD>CJYR#VQ;lO(~x906Yrc?i86640~rnWlpZI}iAdXI4F4b?D~9|53!biKQMU9%lKg zHlDA3_y+3IHhEq~?)Vl~$p)+UbiHPN^Vhe1gIbIHLC>SDI;%-sOY>e1JAMxAoUm|H z>}+!eX3l4w>lQLHa1KL(8G!|SvO1c5BmZ_EeqaE-BO%a+HUxbJ_#_U?bjy6p8x~++ z;j%eN3|LN03?LdiNer00b~7g>feTryUrDM1?Xd8>ifi~&O&dgx;2gGe9nfzX<>CWoy)F zT&FYvo1G7tc#zE>9&3lrHy)4RK+UDa60jPqPC?Gjv76lz)JnjMPDeWIa;H_pHdj^A zW_Neu_jOA1@kQhxQd>Km5)%y!$u^df782RhSaDKV5Znjq8xl*)412jqd|#6p$xT!e zKeUzIQmp3+$j$>J8Z|KKv7%~t|7b!M(Suyj7emGY*nr^R56DuM z&u7WRg>JU^T_7VwO$4F`R-Y9#4uTI&0sQDM;+HHk67RI&$xF1F3%bn>Lwk|_K!-Eg zg+d)7gTWTCi`i%%j_OMm25FJ3h~D@)`?u`9_r-JK2w9rdzH)P75$lzhBk9T$f4vsp z^3+&eVx>4!H};A9;;(@?(;pD$C9Y;bZHg3^)G^386TbiWScSp>>GgPwm;{jgUT`JD z@<0$kZ^||eYw&rp@yFry28T9y9Y*HR_UuU)&}k<@8z!8d0_t384j{#4VWPfgVf66Z z-evis`{!NXefzEt)PvId>VdTn4N}*uL*DJP`ld5vbr{>xd)=x&ebkS^i-s#gStQl8$Y=asTE1C3aw5U6FAM{)P-GGn`fHDPiZ;S-mTVXtGY_s zLd%0guow%m_;icZZeHR>QKcoq{FY;nfhG&H%NM4`I_hmUYaTk5qHnt_dvkS}nOStn z30Yn%%10D;NCNj7)Y0-)aZ+ zh?&snrhP$dfIMn+uWrB_^+HCqSFe7>I#~yDl&T>^`T=6zqhG&{;~VaQTOI%#S7$n3t11VI8Y-WvHppP*d)KAi;VGLWz1rNkrsH=qei|rJ*n23P z2fG9J)E&raP}GAyqM;SlL<4I|M7!XCh~(LkD*?F_-qu}~cC^M=*FAgdlN;|jC%a$m zh|->}pk1+@bw`{a*A>GryJ7RKQ@d>GJo)@{OUF%Je`faRDcAH`e)q_YcilW?Onrpi z+=9ivZfhnRq3(IVSI0r!a~#H@h)A@-jJ;vgIk{=q9yW2~3YUq5qf_4Q5HR}7sx^r?=IJlCXtaSY9|Y9~Lj zgSungv#hUlM)T9J?f|!Amw%~Wpgk7q%fUNmfPM+kxZEyWT|Nt{HY@;1X8L6pSm>*7M16wc(Uaj*x*k7G*;3c_hlGeQw3v1c{||M^1+4Y$j5eUm)|ZQ zy$R8%&?3vga;AWJ>05~mR65!zj48?Rq~?SSjpdP2ql$8X13aiQ)J#=!V(SWoQ+pLA z&iIgtfhA)GESz(B_w`pR%<$o->Tz$kWy88(J#fgSbyuA4QjeX|y|DT7KfmcvQd83Z z8?{-!?AXs)MS1zraHroG5Jr>(=9q@Th{0r_dUbHDU7k&k%NTZKal1@d=lO(`;mFJU z%zVjcM4uC*Qvxb16^Q@aDbtLTki&45o8>`Ik;~1e9Ieut6B$6sGkB*H;Z0FeDkG2U?Od^L-n6{rR0wg~G+* z-0-f4H*C%c7lpC6Ot`y7!x!@k1?ayZihA%BD;A)FEwY}8XfXzF!VONw$5=m!4vhPnk`!ZWv)IOsND;@a4fIBNj4mz*8E{M92(8a3fXB<3k(_v@gK_|;J`?rYbsju2`9$%;chuSYeztWy^MRiGR?ZyO zV`N#!EZk+Wq8EP<&99!bxW2o|yGSTMkRNsg5Zn)BnQ$OXvV`KIG#3e_Ch=DTho!UnnreX~ z527lKGO{&Bm1%GoEDF`p1MecQQIGz&S^bh_sVd9d`}np5x;50qJN}BJrEYts0BG)uuFI{nb z|MzfPB;qGGWMU1?u>T7wBI448Lm@%1uQsXI%Sg51uyPb=QSOCWzUZDXI^&k#A&l9vOcK^Ub+vQ9m-zynxW5Y zCqb-28^#Rv5A~DJUwiHI58rz`;cqjq&tD>2uJ*(Y`dPX^pld6@Kv)p97Uh)%Gf+8` zXO*iegL%%p@eL)pMLv(yQ&wauLctH14#-j9Hz_^?K1E-qm9I7>q!FQh>A=W5&;~aQ z76%CSJZM6w{R2g(MD$UipXs2}<-m1Tw;FjtGS zO^U?+U58hrJenDZ}|lTlh%*WRyw zef$sgNT7XPO^L$t|5TS$RCMiJ7JO&d?zc0`I(I6qqkTH%#E+&N!(s4W_ZEgy{;jHH zV4oqvkbtD`nv1}xUK&2EcU4u_Awz}^=sL9PP^G@UV`D?T5*!cOEjt$5?lEJP&7J`R zB&iI?G!lL%Un0FvvUvX&_5ie)~1l(h^Z&<=I2hNR~_Du8`r zC(ys?4H0dNaTZhR)2p-edR8835$(bF=#-q zi)y|{j|Tgf^S|oRQMzI|-s7K<-> z$%w(aF_9p8H9dS(eSGKCsgJOMpAz3}FtRq}r|2vY)z6LY-Fq~^W?=VV?foIPr>0|l z+0+?Z9&Z=xR4Gkp*R@0E34L$Gwu0=D*VE6|uLWkRLO26`Tl-WB?aK?xq_T3qZ0wTh zHoE)tuIvJ$SE0gLfo32TT}n#2R7lWg1ye6ViAb*tB$b$?~)181*UolrF?O3G^30U%7POWq&k6FJfc`t&MaZgyH~U51P~2h2 zdDPdVj50=K^s$<-(kAD@g&y*i3*%w|`j+;1dX%=sPEH_#4DRbR6uaKn(D0N!uu9eE(e zR0+4G)rhbW5cgzA;lfr(5quM&X=+F`Al}rKn<)GYX#}t?_Ur3!G2z?$J_ZZZ$f@1ad1$V7b z?<;0w2dQ@zv-7Umbe(k5`f=(Hmrsx z?|1~~<9gcy4~&%Z>Z(jGp3@v^vFtR*RoA`kF$B!0IjU}#KGHpbd3X?wqn-*~aH|Xv zCdF#{fbp?=uTZ~!(Lq%XbDgzA2Un*eolxq{5Zkpgln>}NsIF6KMtONgX{WkDy#^Sw zdUW-nDG%fdcN&U||EFapAZ?y;C9$W!pf9lk2vsN%XLXh0Nq^Pyv(tSgo#u7X^v}fR znKLi?-~$)`*nA`ZB$o0|$CmIOyIg?weC&7>&7x4C)>0<*slSMst)bvkNWHNj~(anwnD67W7jL zhcCQ~f|J#HrCQj!rexntS6*Qy+Xe-8#B*L}lM8 zool8^Z;!4RS=o15r<$o<_&bJfls`>=kqGkN*@1sl{~CiA_LUbxy>B2aYL+lIR;hTs@Zm(3k(p^kY!Q6U%!npVsC~1!{TVa7 z3%tL2CGP?^NZL@q&m*P#2{zd2)67@Tt2ue7Luxc@Tsds zt+s#P(+^(10`>juEOn!0i?ylAx&_QQTCM-__KBz1ZCbs*?ih=`^pd)dkAJ25vb6vgcn1h?LulO4!Z7NBrgLcT*WT4ELh`T!{C_J|0tKupT; z06W!V234u_b!zCTX^@WRR0PSv^V*AOdi+`y#>3A&c;l745`*n*nF0L7oxR?YE^NL? zEn;=z+Palas?4~e)f}MYC@1m^?1j4CDM}c zjieI3@|LD2o@mMTZjnp=A=%4i?S-NB6Lc$KTrf_*#h^nL$t5@}4s;~|o<;9+Bi4tW zRrrHQGZMcbQsBt3w8_eS7rH!88YlA7)kK4s%J@b=L(VI5O4!Qy?Agr9p)2)sS(%2) z(RrSFYEj}SAy?$pi;hpxz_7f2R%e7P?U3czScNYGX*RI_JN#xOePvWS1hWLIPs*^{ zQNjswB_K9E87Rp>HL_mPQ3Qf4yd<@|=3H^WI}I0qz{FQYI16kDDxsqRBax2NErE#- z-{8diYvrQZ52_QI#9==%dGxHwm#I&xOW2}o23l z!=>}in+}30t)E?39)z7teLE`KwbzNVlrLoq`EHxdF4)Vf`cwqlCbC*oMvVQr7vpQaJ;rIxk_&=<;%8Pj7p(fozc6;LG<3Zg5{3`q|!swW>Ck+C$I)1O7>CrHNx&??@ zdYU|@vPU!>3*p|9J^7C-JF;z z-mHxuXNuQN+FA+f1jW#jOzW!XK-9?q9Vk=j=f& z3M1Km%##~UR1$yEf^*ad`I_H|@n;}L9YQR)on6RM6d%U#!&%4pvjH;60+BGLgJ_V$ z2>Jmp4l~H%jl4LKKj|Hvm%#&s;zYpoDG8^_d@g^jb-Jz zA>&ZCy8s~=2`z>SHGZVRY^k^?4`rcIAZ5UWM13nAAnOd9!iW=u-MPevR7jeNut-{g zw&sv(-J@tBurp{~=qplLn(Cr&@ii6+b}1p6PsfEs1rq-wAJW&5h<~OXrxlC zNOWcm;1Ho2S}q469LPqWPOHfQ(uDw2wA0~0b$~7cF(8&{Qjlww^a?7g0%&VzRSF%2 zdB`Uz(QE+ebr#Axr{&N}ZFAM#RuHX(p~=CH0!lDbC8cp{B`g<)bUD?z{!=ZiXYf5y ztY*cndBev~%hS$8CH!ux3$7Ms z#=3Z-dBN;#i@8jPURBQGVx3@?JJduyZVwK%$CGOa26J;!|DNltgi5S5#M*U3iaE!hYS&1G_Wad;pO)c$H_JOA?U%Ycc?pM>$yn8C3w{yX=y|GSdl4XeVl z=Ug;WZO-dfKcufb@0!1>559VvfAZ+bmxj4c1Fw#J7j%Ok2$v!^)!afWqu>_zyx$(k z@Vi9?%0P6BF2Mx^CfIE>I;tc?14X1ryJ>eqi}}qrx~DZERjU);hLmdU)OeY8oPkAX zp`~hjdZ~x@Bwicr7e=C<*^Po;z);aI+n(W9L{vqIZv5p#9oFDdFjNBlrO6~p z_(+M9iK{3|y=!`k>{Sr6C2N6OjkI+=Fda9o_#7axmSS`5cqI$YTll>Kuma1_wiNJl z4_JfH0v$jCuk6jp^r3+t6s!mE&H%a!nF9DHorF?zu2D2snMO#viWEsojiyv8#>}li zs1OXAsDrvQz=(JSJ#^?>ohzG~VkMV_uQ-_Ik6U_~xXziliUjNcXz1m_2U%H?G;XrG zg71S=?X{1OTTx7C&1kTJ@z))o!OQ-Dm&T1!PmJ3~<3^a$rgM0b9kow0Zj3DXhhGJ! zj9W?8{Yy%O&5s7-@uG~2KS@^ke|Rzb%BV(2(k9mbXLd!huK(7*pvTB>Ar7@H@F10V zL6L%C!g0xB8SaeipwZvh;B^@dSwdC{sw#Ri+&03gLS?8_}@L z1tK_4hC*W>2FR))+8iSJF)4#jE0x2j`6kAIX%Ew8=GSe~?IN2Yy_u7jsw-k%5N8$Y zp;M_87U(*m+9k356#4{t7sg!-yucC2S|QJ?4PTwrBS=2Kom16;j7zY2+$fMlmJd;J zZt;+;HLQJGEl8FDhDw7xi3-sbE*2M@M!0Hy8{LrNI+VZCba@f>!w}r-iqJQfPh$j| zp4Cs}0Rl3rqH9=#Ha-RYm65_Gr9he>HNMtIEG5U&NHAQ)J~~x;>IZGKL5g}p+5y=S zXW3TB&LQ-QMQm0x3dKcK*gC8zH`N0Rs@H>N1Ef?Ax6LS{_6ZA}G_0UjX=kR4TJ&rkv<#t6Gg69CQy)idJ-hOr8(LLz^c7P!$2R2N zk3t6I+k|XJfh!XP-5quVIvIiU%4EW|Vgl|{&@^DmNNzKK0-U6tP1`#)cX7TNYnl7T z<6tL;+~J$)%$PSl6ZFs)t3fo%09V@`HV4D38I#bwK?0u#uMELfpq-k$#?l5Xri*9N z)5vuLHnS)DhJB{K-9jU9L-R0g&U~%M824bFVN?Q^WLgd3uotKaC{8p1jO3X>)@0x` zq?j})z5oISnjYX+Qu_bo-q2b@-D|VgTp7+s)z;TRzalOXdCr~}$(cFX*Ew_hV);w> zt_u@i!k1AWKiSpEuTE5A4$D#he;?#V-MC8?18BvwakXZL!r^QKxW%cJI#90RygYzD z@<6C*$qp;jK#(9gBt4R1YWYra()g_fUyRaIf9LE#%F^rHqdC>tQS(sIo%jw;&8;VU zJJO@ggE4IUCkowxKmQmxo)V!B)g{%6$x&XOU6U{57gouc*(PJhPF01OvMh!|?Ll)I za@!DqvB`Q=GUz>GF_?mp1fjX`zucB1I}V$o9W>^`iNi*WA79k9TQ_z!YkZ##*j~3JPkr_e^;7jDPa})%{F!Ah z|6{`g>VXNKa%uAm_iR(&?|%+c?!5Cb$sqz9j~jWV0&qDr#yS)V;YgsuURs*vHJP$1 z!g7adIS_&EFXo^DMoy02Y6Yo^)v3oNr1y!S@)tE8tE3=!kZVpb&KS(^f9cs(VY9e*fD1f@)WMZXR^SwX5^6H1u;7=}p^z%<2RsUKm6|y;3EWdu%#m{!= zfXoQE;(;SW{|5MYSHyZp^Ye`+djQZ|Z@?rM7l8}OCPky+F!tN4Vv%rBekK)#WXRHTAQWjlqv9)6(3$xFTh{BXUpf z4~XLcnMUMJBZ^1)5OaLPiq>r$peA;3FNXY`*VK3KpczeOcVj#VJ4}BOFE{O<1uZ1L<88lg^peB%~$2M zc4$}#m>(C*<=M-C0iV2?61x$#*4aW=s}Ey2F5>61j~4qYqbeO44*SRWm;*WyM?es$Jh=1*N5|@GY@lvU~Nu zizbRa_`PVr{`fD(ZWTf?yV+`mX=%6V!6O5!6l>e|x1z#>5ODA;Tc8QOotZxjRyVRJSLTxP5OtC}o;<{ZZ@Sv`@DB~9GAPMItI9Y|7 z{jJSH(;*%zeOu18}Qx4z3N2qsCZX`Pbo2eJ7L>aDTPBU~o{Ou`;RZ_2 z`+D^=mdoXeeW&Y*^>Ppv4!vMZvQa0?pa1|!9{#CSv^rgS94bKP#cH8oJtvD&MUi?_RH}RfWoS2Y! zMza8!;KyG59qY}5u zxidlfw;p{P@i~2z=oaSGMs%3yIKf7$ly6bJ;PTx8PG40)?DqH`B8k<`FWCo7D8O2c zle>@)z@IJhhuZ{Mdp3@+P0=G`CpeOh!s(vPRouxBFOUWTNvLt}sGH?3E1D0d=y%`^ z(6cw=*_|+>NB?n8`)9~7>o$TCe&nm-sKCgcMq zP5q`Q0HE38t@(pj#BVu?K{)LgFrl~lw(JMyGeEtnu}!=ZQCbU9W`-L!F=z=CwD>`D zc19k->tqRW`}m|$Pz)1R5UzNH|GmJ0YeSL!YuDbldfL-x#&hZ$I!?ShJ`1&yrR>2D z)8m&j-^)wRJ0riAebzjU(iB7;ARLpQ zVh(|tEdftH6>;FZ0s#LODRIrn?ah>!fB<*ZlB6(X^uZ9veEiYgx%01@bKl;* z@k_2^&po!An3m^Ek=S9i(ws+p%dENBtp3i|4fRQ!H;IqaAh#N)%*Q#5j}wMBEU45t zb#4VD4*%;o4atGF87IOb`)U`iim#c`^NM(Uc8?kCN|88~)M@L@aOK}MN4S5$tNJ{-dsb7FJ~$l);0L_Nss3izU3rKYjEhzJTm>06y}?iz z3fWvzyAlDdB*!-tIKcfX1v~+dIT(yIqS=5R=`QSjYE7hBS(;TrVMW*#saqoLlWF_E zrS(n-#j6AYB9w%;3xM1(t^mXYs!%Y53n*>Igx7C>r_;{i>dS|K_TA|1b;WhAt-iU1 z8DO#vzrs3f)g{J%te&t+AGp_e&;S-s%C&@_N+(LXI{&% z7uTqNe*Yb_B;b2!5;A;jm^)1yRA~Lx4CRK{S^sGc!z?Q$0En zpnTqxCgfH!rkXlDEt+hVw*BGd)Wn`a&)xiJD~py>0E~H)h;~^ePwrGQ%{k8kxxPGNTNg_r0L$~gj(Puvp?unUgdH^=C zhPLz?x~RyhWp;nFkoUqcCaYyb@>-U#tr)`epz^Ya0`1Vd_jT`+~Z#QRY*pc}zCJ-^Xj&!~Wl zFqirHz+AR{J#^AGh7QXKLl&4P|2e$)*jbLthm`H_&{k4%%_A5xG(1`B|u;HVrb9l`>^v--)Jp6}eNuS7AC77GBI_B5?NI7*4P`{>xX@|u>m zUgC+TfU#P~TYj9a?!2K@Coc_PMS2K0RA8!zPs>l^^7}+Z_EMMym+)5Jt%gNf6Cf`TFq0r#(We52+Hm7Fr&6eHe@=ZFb(E(NsS;|6j|uyi zbD8@QLyZXFp(mzxyIv2j*FXlxr`a5^oy=Y@RG^odz_!`BE!V8J&Y)M~5QU6Zp;Uwf z5YA&W%C@dJ{B$y=3!L!h2h<-sO&U9x3Gl#HrW%PIpMsXv58eh82XVNJ;T$f0VGwv; zF{ae`T;_}b*q_W^+&A7Vn-MS$n;B>+Gmr2lW1SEib^B~(3A7m^3i`+mBMb}-O{yI& zWc@u;CSNlr8S*{y;$yGnEf{^~qmQ%uk_>4Fn{`Ljzn0vJv2lDK_LzfWunxNjHX@zg zrgu3_utvb@Mr4Y5w+NOCa)kI=rmf7>N#}4-TF&%1swH{G^&65hdh%&?BD<|6{7YMj zuNlYYalfZmEKdZBhF-@2WYXEeglnbnFT&3byJCdbfll?>Rzf)7G|%UE6qkSoz@w2+ z95}uQn7fr}K3NT8myQL)&gXWmPl(0B7Ng>GGO#lFj4<@=XhsI3)`yM?uxim%2UZzO zG7blPt&OQqi^02Qonq}n#Yq?5(5hL;y7*=`_M~_?;`~&|=U`s5MF&&R zXhB~ezl4aNB$>c&Z-P`TME;=R-cK`*R=l6IC)#j-UVYD$lh{AeyDN*GnPdRvdqKaS z-vQ`Hn}NJ-Sf>*iD$V>-BtH)wDov6vAlZDtIK^}j3xQ0M07-7EaL>NIYz5t!G#>h-Xh3eX+>MHXYI=w8R@9J!CRBwb+J>%aG zad78@SFg&=yMA(WOxhQC&*z@8r+FmcpXFVaFrF@F zic%SF6Z(2OM*uEicUVMJ-Jq>5r&J~%1=uN$^-SwdE$Tpsu#>v9YFq3yomvUtITslc zbZc!*uB`1k>ez~V8TnfV?q1mAL-@Up3v@9DO&Mh`Z>h)#-AfxBK(H;pyp5#J9Z>)!P+NJ9E#U=&>q!Ns|bB&*v9P^ zFcIMQ^hDjwbDXys876#>kLc4%^M&5#^WDhD&l{>T$CD@2IEYl+_G#6)5oyl-l5<8B zi_3^;4#jMJcBF+F5ziQh+`?AfEy&#sHHC#BDg?5;hs{!@7wKi9dQDYx^cVi}#e{&pMEt z8#2S%d&udTblgTE>x$=3)4?LB z4Grr-v3|EuZKPDv^iZZOmBGoHk|7KUkCpCvB#*cpRYM^Y>=tv$rH!op5#MX)?9lI ztp$G$_Sj!7@1wQA`%d6}+QVAj$9t{O^N>f<_ViJ_kDu|pJlfMCLnz0{+x2R1!WelC zIp=d6o?Q1t>OE9fEQvuwFjL>^JqYq8d%6KXtzci*$3hukfXPI?y-~={$$(*s9)_UZ zIcsQ`r6Z={gx2=CqblCOM-X`g+dmr1mjrL}G z?Wiw2*GB{h#o7Vg4d+gl@^pEjzgs)`w&dC!OTCAR>ST$yUt6yop2Pb!@jZ#(&!2M> z-oy7KJx64DPoDTPo)>bqYG+O3t0S&st{>Fovy%E!WRVE%$uRBZaIWBMr6RQV>2moQ zJ52Ax8Jm+lV{`UOv+3q4bp&+gw7y{C`CGiCLq1{DqW-rs31)QL=;3s zz<}65EVu%;buGK&ynF7sr`=Ok9H1gTMEeN_ zg4zm6npf8Vf2nJLzi16E>(pBM3`>T#O7cC}KlbrMavh1?k$6vbHm&0u&LEc0N-Q?@3Rmb<(V9enUz>U*LfI zJo(xDqMglO6YmKIX`O&M;B)agzUn&M<{!iJ*iAxdEKP^xdNgg7EM^mO9xztOXv7Uk zo4m$@Z;8Mu?re#Om`g5;H(PUft8VG`tIv|#UbmFnUSoK!I)AQYMCQKUD48uL1S(Aq zdM+CNC9U|n+hFQ*DJ>mgHky6FqilTk17xXwBFD?eDqcj`Bt*BeMEI8_^i7D@WQKHs z>*)`&RM&*#=9-Ed23|?Lk9Y*{Q}8}x>ia0qAG(6_{Db80b_RnCeb>ROj#JK`iyQK;94J;8O^T{*}8h!<}D( zE0}NMW664eYURt8H#fO)zPMq7S{9%f(dqg_=Pvg~>;s0m(vRaiC=7`;0*rw`kh$GK z1jMDFE*MI;AjS!yCW=8hh|a`8pA%VC&X8JlzZm@)Mm|F^CqDrjnyuJL?M1ErwxBH-19u6>2ftjCa3s};?eO-7?Axg?L< zsng>wYh#kxsMkvtOsL=;4f!S7-5eDuDQ-K!oA}Ff|02}c9Hy7r$pfUywb^<;*jF>n7hcKeOVG>4%KoKpT z1d1}||GzLw5oGNk%-LNzhw#g3E!6oct3=qZH^n;HrTZ|P48cyN&3#Hb!6*v-A$zw0 zk5sbQ6wzq`oxM?L=o`m{o_q+ z$I1AU-ze8!WS!W2Wr6Y{+qdNOL^1ETMgKgfd?yx@euaG^eaG*FH*r`7q9-ZET`ll$ zBbzfTVhsk-VlN`NBksb2T%^n9da}{H41&k%^Em>N1G31W2_HBGc+-zG9fzNW|aNr#syj}7m4{<#x>6?eZW9&K2ir}9=+#qHaV!x zXn;KYhC!<}Tgojckc=L=VNr(BU^EP^&dWo6xZmye`wD&JDF#3|CO}=i#*-uXV{rA(ix9**P2Qwc1)%*Ed3un|DzU|+> zXvaoIZE$aVo)U7I&P)$EVF5&*UWe?#9KBw^tT{9%LBQE5 zAfb>qs^?ncfA1wMm7;4S$!5eXF{3kOXc`Kl%bU26yY1D>w<_yP?Hit}H#qH{c_#DH z5%Hs!y{z--KJ{m(Ic^^|e+FxP`m0kH_&$arUT?seL@B4!=`sfb(X2=~oTYQgxq0cd z6BOs^y^&}XjlZI9UaG?utH^GoCV1KzSpi`MtS|9#hj%}XCJ-d{p^&~Te(N67r3}qxnukG9Ypmf z&jV(fJ#Y%TD9mB@`lV1-mV{=Qk(`VS1P63I(5%#qz@gbMAhRM#?Ee$y-$gMehaPk(jdOTfAh_fH{Wt z(C7oyG{P&+TTfe!5?W_t4t+w(X#R1PB(&Ugjh0Emq`2u*P^3}^zzA9ze@V?6Igoj;ukLsqL>Cif*u`>d)hAY`w0yb zem;zV>;|7tE{C}kKc4}7`h@1OOtXZB*G5T?>C-M3a>2Nn1oaNTRRWQ!Wu(SlRB%-p z%`*=pJgY}jnbNBu%nbQ1C1XYcc~bxF+pqogI@+UL-!}j(`S`PB4nH4Uyd`5=i%!M3PC%)$6^Jbkms^k8Weq%txxxC-J+NmOLJ4|I7Zj_Z)7$eA@i+^Jj0IwS3Wf z=Ir|XE_F{g!+x3t`I(1%P? zsFUaX{eRn4LPTL-6>7S4qD?zxP$yb9(exGe$;tON-7$XcJCCr=rMspf~u@ebAkrqD7L z5u6!rcfe^t`KJ>x7pLS)hg{X^P>-U+JxNEV1?jI>w5y!PSl}rMrnVMbZNt(T3VD(@ zrV5wX`gdY<>-v_l;v44wQS|p$<=)NqUCxax-QHs+6w{DPI?rhEWEb6E3}&_+lEqC@H}! z`pMyv67{F*|4;JYY}APFd!nwJut9$fFj)=@dcO2I><~>L0){A>JQP%IcmRJ6&7#i_ z=&$un{-HgI!Vv69(DsxQ^I%fQ_1kgyQnrH!r@)*F|4=~Ns4N;T6&r=ajmjc9rTU2~ z_@w}sUc+UVa5xm}y{4@fA{+*#wEw;6E6!KI5Gm{WQoc4o5ur6tPPkDcLQyzUMo4^Z za^31GG^M3%l5!vDq#NjDQ|&?I*f^g?wy;wcWei(Ef1AdQkQxeEkYCYZ#;ENFdJY;p zZr6H(nbnQ~>uk~Q1N4@|665%IgK%6>wFQ6l!oGd*a-o;jd+4WDq+cdPgmBE}$`CB@ zM_LdO3uxyuSS^brX(W+MMG=Quy1|WVO69Lo3z4Z^-}a2W=IIlAwkzvVQCJn`-}GqH zq*v48#`@;@c`<*d{5kDX>Z@b=?MynYi;lzEAk17O@*@zZ7(W@i3Et72rKuP*rYVH{5gN$URRf zi)w0W1hKXO$Kj9ke8Cqpp};JL=ISo$@kNb-(2y$tX+`#`F6F2;?cXtSR;`dYqn>Mb zj~hG4svQr(4UX={I%#mDRE|bvs&KzSaMO@B64eK{jhe3OcJHomw1lMi3N8!Opn0>xR%Q4$jb2|^UD&KeK3*d}7j!Mz6>Sf`m^nyOK0Mt` z%tVj_?CV(jcQChJSlxf|!U4@2_wC(3G`m@0gJvxX@>>u9cMKe`fB-0MUD&KyA^w41 z27&(e>9@gWlm?Lq2aE%UYfMleWH1&}3+~A!>7)C8Yqqanzns9xk>*IFeEqg2{YRNc zu`a8vB~=KC5f5G`-_bWi|EZ1%NM-1*8>*9a9+mb6tMVPxXTTT*>IVlqUVVJ|i5okP zYTt20L3yM6QKJS7unZbx9x$+RLhv=$~ZA6805&~ z500AAbN#My;|9_`LBHU^PW(L%A*j-?`c61o%96xO*dzXmX#YR{;OJ@W>HRxKPormz z9{}DOuj<9&fMGkPzZnE;O^h*yo_Xg61OK{;s)9V9VY~9h+Wjnhy>g_yyZ^KDE!ti}`k;*#Z)mXoaa*B30R@D!}ffYe~N*^zQIT8NMv7Q@c9Uwk={M z+Vdf)&~30F<}D9}nhOyYi|mSskrG%k1rAz`JCSKgE+kXs_n(854?r>u;?(m>liabhg7q-IsXZb`05IxibGri+)E z2ZT%(%iNVIW@_4RDw&vE!K1Nm1ijh*Z0%i>*iGYh*I!(@GHv3-X-9syJ_C*H0gc=M z8ZjQ`WN|&!CCNbfQ9FOoRcpNG<=PIMuIx2rtnw4);H4Z|Du^#9$mm}QJ+1s(v)^E2 z5v{3q9G<-h&sJ$RN@oyC5>v`Tzo*^Q#Zsx|<I!vQ@aebV6Bl15O5@qz%B@Q>|vdh;|s_{xWEAhopjaY8hfa#=StCaR4 zP8GUKc1|-A39CQY2t7tTp>kyj-t#)}RK@XWJPfmezXunjs!7V8Pd|k`T`tcRPwBb< z->#!jCQxhm9o{Mf@)by5F1CulqU(ZWZn6VTL5~#ivk{DtnCjU`iL=oc(cv)mZ)K>s z*6;y-&ru`kWKz96WtMzPc=o@yDnm`Pe_qA!eb?dncX$_tXYs&Aa&tV(fNsBLL6*^Y z4V^Aw{k5;DX6Qln9BVIB3o>iL{rXN+ydmnxy;;}+wkqM@v#-ATsxR7Apl1a5o?rIHtZ30h5izg>Ry9r4HepOz#0 zuPEQgKia=vEI7V}uh|yNUxGE$;U{z(y#|b7*XuKaHoMX5O*hz2V3~AMRXEFZLJj#> zsA7S5NIk^z6)%GDF`^^jiAr{iv4~c+k*Z2XbW3LPu(2JQH$8Z4YVWR1{E-);X{DVe ztZCA;P4gA<+3Xwd{yeT{zxZ0sT2OQB&btP+&i~uS7W;8#4S=!vlswCwrBP|eGw}|xeJgfLL?a5=sXQ6aBmG+uv!f2zWyV}| zw3fwS?h|}(7CcY~)&`R$N1x%zWL1#*!K2F=8pY;Ef!46!#YP-IE?J(r|7oR^y$AeC znb*D$-#Na8(2FIphlBVuim&N=N|bfdvv@S8EuLS1b3&Zt;PK<~*{c=&_saq3T=^5^ zx>d%g4hA#LooMu9r6hI0Wwbxbnh8dNk2n6|%+s9j9b%b4WWEB-7lIobPQ$zkaqh&H zrQYK`hAI2y;K{t~%FfN>1H>KU?hH>qk$_L$1^8@23CgvsRtNfyqVSu}+W=uh=WR%y zw}xZB4B-H)a4L&RUv4ZkHqBC*DVgFW#7o|Qip(vb&eb6NFP zOmjpHZ^-Ltr%D7b_C!Z12qwOwfHEUaVRa%x6%v>~4VqTH;A&xF1uR&2!JLA)q4xXZ z8}`0(sQcP&+w_fN~i}-H5{MiFPvb48PwL8>$R{RW-L@1XGXCrph&c=TX zu`eBJM~~B9-%urM%HhP3_`rk$Ws$TSxUvY*r*Z3t>@etGNTP_b);vzH$~1C8)X|Bw z`xzi;73V=^k=*mxHkCrq;~jkWGM?!|27uXyMn6VD@Yrpd*t7#2czgI75*@ng*3`r% zoi?KW6_~)?ns?9zO}eC)wV(<8Y{eSyg!F%uSj~}3YbgX?> z`bbZ;pj9!e(`LpnI@xA(3(j^X)(XT`iS7ahzNR^G^ccsISd@fyRb?(ZmVf||dVonK zg6`Bw=3f7`;yBK_Aness*|__rzcb;^kHn$MEy@GR$BJhuP!1jhKVYuySwRnq!;aw| z#hgBpoZ5~>tcXiDU!AjI{T)+FTjD%KZ$N_b>sK#=B)s2Vdr;iJ0c-@KV?D2}5(|z& zBjHI^SnngyUK*w!BAV$(e;tR@hiGijAj|0pZtA@499Yu{>|%NgUsnJ*>d;G?h+mh) zIs_hA4m(aDf@?0`cDIQeH9M-D&Cz4tu7@Uoq0~S!|M+SAQMPO0<`oBTS#Ww)S=n!Y zU(oG{{3*&!PsUg6XUG2Y&;Kar;y;VQ=Vq}bXJ?J&Fdu|&Q)Nc*D5p!|S)v0@U%w;G zU-|u!veY3rH2sS~p3EsB|IBRC&`Jl%;2xkY{T{7!q(nzF*_t=E2SX&o#ds?McKB1Dw%Dp-BW_{d@0HJ$E6TUZRi#dD{goG$qtG&K*{Rve=M^7cIL}!BXVsZz4QfA?Zvd<|p&WgE zMG>{mxK2Qris&k3dyeQGm<4pm{Bc38Ck|H?rd45TI#KC%l{+E48!+zF#-_ zURByFV|SNJKVR!Vf<4S0QX0p9=A#LK=H+1V{`@Rj$E-HlVz)a<*I4YwS*!-58yvb5 zI7^41nnpx@oUfrxlNJ&11Lh_Tu1XQOn<6f;F9`iOl+ERTD?R12Un}2UJtX%$zD0R- zzjzSq!tE3*@Z9t}_|~NIOo`!G{$)R;|;y&}K>_2oqLy32sght|X1CWnZaK zkc{D?o&ccJ@o5sLLOS;75#`EHN>BC)81Gk$8jj6DBPlY^x`IKaI^}WwAo$~Yf*@5mW7W|@o_2J)9(8~Ml$ueMY zA-3&9KUb$-C%If6oQ>#42{tq9ApW0n!HK3)t;#6ex=`sgY3jfLAUVGK3!L03mITHUZvSj(EAKiWL&^)C(*c^N`A9T_Q@JJNs zj5$3PBNIi;Ej9YkRncmJG;Rr&ZH>!{ho6&b?T{QI6s3?Vp$EEHqE6)y#udDsR5Zdy z9WNL4|9x9Itek)6^O@`8KT4Tbhs!(I!Ncd2OG<+|?+#(NzJ1py!c776kgD7AkHIE} zc4Mjypo+ny4Zv3=;*9)>dL!-6l^cVc_*u6I`^!X6WS_$+FhkIomKN|E14ibQv$KMR z`joLf{0>-w(mx4id_rAV`uEDUo|Jx>ZAr&L1YGhlW^Ip;x46M-y;$%ttB?V)r^7LvH+auS1=tzAkpueqVjvM8A){eIV<_PSF1n z{2wrYG3E}pR|bb;WzzQH)D1;e&W5g9^)LMXe9$Z2ho%hlJ_D>pw~X`!wC#~_uFPcA zF}?Z(jA0=qMO;f~m3)#X@w5+Ct@@Il7+;R35+C@muP+eWHNfsb=M@7INlj)6d(>g( zJ+(zJ>dP=V*l0$2w}Vj%5;dwoP6t{`@m_Lt9pgoSM3xwat&O1mV*e~<=8PxV#(f>P zZ)O$BS+wX`#@d<@mbOwEKMn*flYk3mtQF`}Bb!AX^Go-7uCggdR8eN9OwxEH-+>yfo~;Xbv?+X`n_@jtDPQ8sv9v7o z`-PUc7**uww{%IZTje&Rz7ho$-Kz_c(P6O{HJei#F)R_AWLR4J??T)ZRVwz@{gjl;xHy zCU+m?x_|weHDcQ|gb>Sy_v|;UdP~1o9)Ixh2TATqboO!soV^#4DWCG#qZZ^Tro()c22%XaFUsS&DEJsb2(VvL~N?zw_QTk&@GAlqNG#XJ?-~YSL8Y$M5Ej z8Z}oO&OY36&xnqzvj&&nw}pME9O~J#x)=Mqa=1^=o_!$$P$x+J-H>O{k@M6F?2+;% z5HCiw+G`YSXm{Xt>5x;YbI71*>dl)t(WF#$3f#O#s1?lx02vY^E@EQfeQc6g_{Wmwf)k{zLv3{Zd;$d=*|vZ#OtxdLOOKs}Tat*=r~j$qRh+~!m|yu6X4Tu&C-SqiZ+-m1`)^CXB!&sUu>8Rt zYuDVrdkvjcJ7K*1u=G#piH0#>z$szGf!8UA(rrSoYMWQD_fVt`n@+JzVSw}HsFx+R znjtWt4EcypaW?Nqx&2uhdRW(`Qp!<#S^7RZ@Wpp29WIGp5OJt|Up_1kL1jofa-BQG z3c?O6bz9dN{k|-h-D=8>y6jngol$QV%%NV@2D9Mlg|5K}wi8aEkWxC+0#=YsUKq)d zRRt9R3_Zc*sDQ6PUw{s@lqBiNF)+%S7pJ3s>g(Y%-d}w|Y&3A}>&w4iGARCL|F=I` z+DB~M%@N zWJglhs1`6$@`FB0C?pg|oX+B)R8pFr-@V!wu%H1}z!YeJsVIIARIA5tL)RqRpV=kc zHsMN_N;60xt84*np_-fOWmPjn0+o>_XvKh8x&?YrM1@X`a3c{=kFstMMwK*BYmf!I z!#(c~=KJ=l@`Li_%!SG|*c+!-@7%pXd4JD6*M3#oWr^*1yDTO={WOu1@|E)C;lr>p z3O;)Kz4zG@Z!0g>_02*}JZx`rn&$~O#v<^>V2oMVmM#b(gUyg%AUiW-X=&zkWPV3` zQ06z+g>>CXe*>Gj-hk%99g__Wl<)@&f~Vl{l+be86AIq76xSw|QrER}@7((Ki>Kax zXwKX<$43o%F>-#wv>B5oPDOp=@if_gYVFH!|MleBm%Vc4(@PeuWQKTqW?i*x>D?=F zc1-9iWsa*L-nZm&Vd%6TJ)4!NDy#o`k6B(mZLgSo+ypo8UOF=IU! zhI$435~MwH`@N!(&wob$4=z2EAS8P|X(E)wY2!vz|CUc#UbrT}{lmpdOC|=CSI@Hk zzkc`@J5=-a(mCVrh_f?FCHtV9>F(cj^)x%fei$@($l&^Lb%^DdtY$$#xj8y%x(t#b z;J4aQ9BZ}8%3*gwXCEk)3jh&nk#YV};EH3otwK&2>dW!hbu3?2MY^&gCBvbYd*;ECroJmLJ>&C=7*9m~OQsGmf!+-$yH z2cf@wpL7EJ*D&Tb32u)^r?dM7e<(vpcbhN}^Nl8D+|Qr5r*cWva-_s=$f9B9m>kahdBi%AiS*1`G%8#Bk88m2LBJ@ z?N1>@sE?A;qHNHgfU10T9Dn`@VAkegv4p_JIkl~=meJMGevrqJb^Cy;kTDR zFvr0fBu7%27#QIEf|8en9Zscnb)7`?N#lF&s%?!}W&BI=D`oG#qpI0GV!-&}t5}0I zE0mX{$2Y^=2Au6`FH1$})m|Wsh*e-H6~;e%(CsuWH`n8o3L6xlp|sZn9<~*vc_Rhh z01RiEp%<4_E4K zY2vAxo(xs#U}}!Cww*F&K&?bcznRVYM+TJS+!P#q?=3A}e&+ZqEpEAYaPX#_k^zza z&8PS8H?KuiRr9&ts@?OvZ_uIt0Pfjj#3v*wkjsS^wHttoXW6xzAr7ynU9nr@d0ej;g!Zu_ z5m^ch9zc7@A`90Pm)ZcB!nIH!V0e6{@eVF=$>whsJ#l2u;YW@Kf`I390(tq-OY~^;gR4KD$FJ^t`M;L4!;VG zEy-J?@zoBy@Mvlh0_R2__zAyA!10ml&%^{`2S69sC|mBjkG(6kj>p+QlzcHO-e*8r z+Wo9D0sRc+%rxcT3~A=*D&-m66Doi^%)Ew8=;JYh2A=eE8Pku@$x_J5w$Kz*0PX?Q zn3Jm0xjDq?7pn=E{4CXQ6-)#G%8O{VYXCtD#ByI-BuqE4ESB(Td77%OEh+1{$CUYO zAnV(w&*bT^jv4mmo3B^>>(UR?#!K>@o22tU-8w(bwTz9r^#cbczq$);WQchDO8`Hvl5a#VVLN{scf*_x z6Yt!_&h91XMIY@1dk}>t*uy030cH{T1|2$#fe<`iz5Qaf{*ulCG}j@R8-pq~!MR|T zCNdKLH55*o@!@J^8*BAnW?Fah*n!t~9hWX{`*NOiKK}KwlX3A~@NQ$QRe-fDxCQ0v zFiOxM;lgUQ5&b!II9hVIGx=gQyCj(ENRc0Cs@qMqgJ1#HIo*7X#ec+~67%98iDR+K zd5yeOnGwb-{=~ZQQdx{LV6a(rSSqou$pj!wmjw2ISr@)07&H7|SR9Kr^RV_m*w-^y z&uOf3#_=vrKEFZlThBfB%!gIwPtI2Mb{m9LHnEy?d)se2dn`%UJU672&;&f z7|2R<4`OSuR)1Kxjt-7saleYdAshb`=5W=iWlVE9n+rWx^0_v};8ifD5n$3q+Rx;Gn4 zga_mY2o~95wTT9^fzeomI^oS%`-A(DiO-XzkTv@39aQd`1)RLRM;XoD0$% z?+=j1aagT31kfOXY-l*|yCj+2lFfd}AlQIyOpb@5tLtHb$B|&fq;n{nsFYt2fMHP1 z)L|Yj={8A@)Q-5C<&qX16>3cxrmU-n0PE&?xxUfyo5Vd`JM4&$1$XyM8y6WS9!$`D zf}X7K<@%7=B?}nbh5p}m1Cz}u=mQm9Pf8}HuFnS+hz}?>-J)IW7yT0KuX1;M0(Sgr z??K|4`s>();+Pk?QmCLdpVTW>|GQd-aisc8CA0t=%@UV>#)WFt8# z_-H2~wJb{ds`m3wYnAh#3bpq<^}x;}M;?6WN$In1;kj`C_#Lwll->ug=(7$OBvnVxx`E$ z7{|=bFs@4&EU$8|1M7T8Fp=Rp3UKFw>LO(;dktUG>!!ZL9)0qOM~3$6)N}EW>f5J} zoI3UPY4R(Jr%c!#3OzRWg_@(!&)=6BcyQXRdADzzvtjwld**E9u!E+h5NOg2uLwBU z0S~tYm~(p^Byb(@07APMF6l`GXu>^Juq3oKOw}fS18KTQ6Dd@)P5EXm+w$n4wST|L z*t_!1wR@4?asHvT&nruSFIsaN)+GC(N2~#}VqzCjAF_K)vdJH?Vl*ihcSFm#5v|s_ z$bir+F%1T8JEUIW2ytZSaJ!5wXCBCeAfRkFTw29<{;Pbpmc91qq4^V-9gA14{g8e9`jg_E_=e?c?p-O4h~HoH4qt<@ujOB`h8KQ1uh(rc z$v(gRlEn?MJ*G>{NJ0haCkdefh)Im-k@$=;IW!Uo9k!5<@xz|Ievdt4?;hpXjc3v_ zAH8dDp6f3gAWLR-UpjnBd^K46@YI>mqnp?DmAc|ATVihp`6u{^%3#a3D8WF!u(7ZZ zJtrKJ)TnV-pQ&$n0UcEY44=)mm)X&O2qAlydZ_*$OUTvJ~fi98i;j) zY?VQP9nGo2Sp?v7Uus+K^&!78e8zqsM_HAR9IXXWJ1gMbi*R#glY zaKbc^kY``0q+XYOfdt{vL%I)jjnCUM`R_MXt-F2NwC#^S+@)vFTg^+t%8!q?@3BpJ zPF%Wt@#qe1JC17}zh)UWxbN*cX{mMToi~(+Z|igP_MXX;uWWC9bNFy}zsn~|J-fBO zvE77LtLILdI|;ZHg#kGCbMXG83AyktX1R3P+4dkZ-ShI@I=|@8xKQoC;`mz3xlnBt z6X&kdaYC;pjvX-^aHbcD?osYlFo0sQ=TzM*o>PA8Jvjhp8!HQ^WfSIJ2$Vx$#Z!mJ^@&XM%!ZIfIidi_j_D=S2Wl0HKxY> zUx-xI!S$%LqJSjvpHnG7X7m1tNY}@1#LTr;*H)xZ>DnZ$uY*R>@`$=>LDzac2sGI| zwu}%Yrj#yRs7@ytlAdnHa8aT>vvi@_tf`EII@bPADiAI%V1sh~QRfd#88Gww*bziH z+1sb?*}s1m>#z`{Q9W8Gb-i;PC}UvtU;gsK8D+d{7^eUgrv#@P7EZfZ#Df+>!JyS` zO%J&}7pmQQx87lL_%BpDzA^zkQ&OPeZlAatB`zYw`;_bWms)J{EA!mLP`WaK5j9;K>v!Szm}t&2J&6p>hT3g9VIgl^V( z$$g{xbQ-#L$`@IaTua)v|J!F>W2%yi6-5sR&kRPSloUNi?6Plh^9C284j? zLUrJZps%+dj{Ag@1)E7QBiEq#l-N(g<&6q&RrDs$MsxeKj?-yQ>+P95$ zHcZO;V(Q()JN6m1fqmS6VD^mmv9&|zHEmVV$bM|{fSYc^Si>>5jGM=0M@!)ZbjA-t zHax~7Vok$-qcI~r6O)ni9hr`j;^1YU$t}xP!Pr+)Nd72L&4M(+rE9eYLrn0Ksfr$rt4<|o0zm3gUxY{#6ef4qirbq9dw|aHA z>0!|zPGi#MtnvMa^zAvcdcuh@ecY4&Ie+8JC(oU{<;L7Wl5bV~nTH+{AG>?!-8*;Q z^&rWdA;Me8vAhVaW8fAZ&#f^T4VOjS4Mj4Y`Aid{rUY+l2{s?hojaFxnmbo|Fs>7? z5)BN2t@Rw_mxZJk?za3|Yq9YW%XN3#%et>MdzkKt5KR2hBxU&mnjNf8|KgY1qF)Eh zo5$MUqr8V=y|3AzuF4OxKR%sJLj!=WwC-Tok0qeJJOO@vOp`F>37Hugf(b@qTI51? z+7%zoT!hkdU$I;kGEHPpmTOl6sP}|=Q$YZebF)NG^-Klj0iP=71Ld6O@tS%DF45eq zTem^;r0F^_e`J^5!@Oeq?Ndh1ZFXz7fdKNt`2O>zD;>rD4-buYYu~Z+y^GcSUUvUWG~f(08bZ$Jb&vv1uh#WQ zwWI0*>=O^1Y|t=N(*1^h{YK9xFpDSlnm>uHf5$w-GDfVJ&@TyK_jZfcCG^iZz+}SR z2KP`W_t9ZgDj+gk5g{%~Lu!Umoxx9fky}IcG-1uQ$Js3h9#}kZ+=QV+Moqd64(=H* zzs}aYdV0tD`ySY|WdnGO37sG}9>SUdBS30=w5^`C!R@4lb zgiO{%a$S^&%x9jfB}V!UU+G`y+cbIW?uWO{+-je1>3_pB()b5gt}#!Vb?)S;v&$wK zyWC7Lw3Nn(wC~^NA0#!xpXMLx4tK!W19VOUKs)DP%aTolX*StzkbDrQ z2Af7s*fiG5rf%+*8#8kcagO;Ozxjm$7mTP1LBp-ckPeSSwkYC$U*@kx4ld z`mS2Oxc7kZ1Id~tqvlTiP0HianiW1VnA^~ObK8?=ci*-7fz4~z1Gi!JDZ5j*9(OzR zqO+jatR8n|85wAHy#YesVv0f3K&K_(On;N-wE|3TaY7~lN(e*ByeqFN_ff=|jDsq6 zXRFwTJ_B-lu3VW@lwQKVcO78y!Ta|QRB%sjCnKOfZyga z`Yu~tE(in{5j2lfLZhUhhP+_OD{LT27c#Q>UGEVy&68)yV#7(j=TDL^DjAdd%$v@h zjqiJSX_pR**0F0?w@3^~VR;nR#r#)f!P@Q666pQx5BP+O)jpI1!3T^oG|GJ77bk6H zE&^%8DUodC`6coJirCwE?IBUp#Cxv?2;0W`gV`sLhnJ9Nd`m3PW-yq2 zEH67FBhQS^cXnSe-Akj0KnA)B?Ig4RsOSdcO$#^>7{u0RmHKl zX?roE;zMSiNI~#-0r|)MCb!8dNtu~agTinSH{Gu?axulrfe@f7e$b2)ziV3we;$;EKj@Q4}MJoyAc{uS9(tupduKauHnX^lkZ%(ih z@wfPDaaLTX{0&d#vKDwU!&>Wzyr_^(%5Z1s(_oHer%B<66bkt=Gc1`fK{LPhLH_!Z zx{O?5us6vN;r0l^J~(QwSqixSrM!uhQxJe~Os2sVz+EPGx#`v!D<%wi?aA`W4+gc% zJ-q3GC%5nXko_oCcH7;n;l8PJ?v%#rFOC>D=f>U79e;Sm>E_?{raTZ4UHMK(UFDyl z^TOcJf;7Qqa=S4UUDi2F7)>qZM0~in`>xomSF&(J!Aa`B(s9ga{CS-rfMcX`HNXph zD+D>uM5uyq1HM)lT8*kVRG$l_ptNNLWgcT5Uq zN9$idvFm;R&_d6aRE6Y6lmYmYj8DBoMpcf=pD#u?cS-pAo8B=txT~M}K%W`isS^ct%-ihT9d$ zDQn)T`Oun0zcKF;%r-3%JN`JL(lE$=!`!#o9f~)88aXv1Yv&06l-4;7Qi1SL9A*n% z8(lkxy(}(q8pN4`puIM}=6e$6-d!Uur6_>f}ol6I{n#bTS~My#F(MT~^s^-nIu;tlctd zTKtFHSjmjDao3YE-#u5;3nmX9$+_#MuHY`n{@S0=ALJo% z8ay1iF$Y@Y+XbW1DL7=zbU?e8a)>l8Jqa+P8I-S~wvhy=-Vm*Gt~os7Ig+QM&u*>Mca1VAiBr|yKUs8+kR$iPQR*5uV{SdkY;^+j*;K8Y%<9< z!`cMjDZ`|$;$nCLa!{?BpDu)D*<;f=In(7v(TN}sz{MKM8!U(1bXdHwRtQ|KrFq&n zghh&=;Lz9SJT5Z=swCxxRFe00J-+2Gd$0Z@MvWa0?rTv6?qd>h->;Y(_u7jO{mnnr zz3Hw^>%e`h?mv4-<-Tp4#$~0ecuHIfdR~&GUF09?e?Z=BRA?wnh&A)NETM)735Vc} z$&E%sE~%)PTrmxW?$r$&hD~8~pAS1NS@sOK!;YrUc7JmIFD0qqAL;#swy5*Wa6?Qx zsDz|Zl^v?DAR)PB1OtGlLecPT%Jojn1D3u?&IA5_Vfo4RAD=y1GIHoW(~sU!^ZSv& zTf5)<)EV)ec+ZJ5nl9KP9_%|VWiYT?~PM0%vrHz&C0E4&7TuLQ+<2(B6HsQ439 zkgpMHDAgq{Rq&l^6AAtR75W2`A^wlVjDmw>2C&3gpd*J zQJvwSG86T7OWZ&v6FH&kD1(e9l2jTtG^xd0brb;QTZa(3uke(>fRA281AQMGn>_fM zBnGx@S$bFJrX|rE+q{c(uZikmE5q4m>aN+X%jRudYF%aQIA|C;ch$azD)tND`7-dp z4Z?<4*YcvG{G5g%0Y}$bXn~2vR!lY(8cH47hrHQEzHA(JwzITRBi5=_3mOUCqCs(S zv`JozCPrhE7I|`-kQSrjF9#(K5(ZSp2+-cqSxVh)f&g5aW+!7fc|TK8LV`h+;}KVG z0Wbj;p*+r}1?A|@lklMsfA}*|_Cc{>LRVgp$pO`K=gaf!)UPB#d3*g+H!htx;1BX| z+b$h`+mXi*&Mvu+GI8VU_p-FUQ)Vn<2U^$^8~Z-3XaD8ikJWtXL|*UDxp&Oo8BmU} zPPV4XY|+)+dY;mJ^|q?HNCYOGR{Ip(FDHQ40-;s)Mboql z)XlMQF~a|aPFt}v8g&-iWJ5*!5+l{{wqKk7gOD=t`ifR3cnKWBd+TNh~2# z!VeUYGSo}W+bvx*zeP=P$>CO1beU&xpawzm-qUzck?F*7wf5O12F{cP~L)2|8#< z7DAS*VM7Zt>T=Vh;v$#5p=2%yo5T8CeE1sreCZgm>a?S7K(%*MHcu9L%Dx*nDo>RR z*q>kBUP3mcpu)8-&8u#yOuyeK7fl(Jo0r>a%B*0vsxva8Y?+>KgEgJD`gq=&QRYX? zr)nme_!gCuDSOC6#3PKEIuRi~-2;O;KQq_w$aO?K_>hA>+0?5Vsoj47e3G|u+iglb z`$ur53cGcKPAqN-Fz=XD2j-#VKE#8IpCu^Q?0Yf+WnvE;=f9$k;%B)t`sN^`qA(cH zhbo&!Y-m^6ta;Gy^;R}*WNTC?Wl33();U?IjYE1Z4KL(DTDs8++(hA=mEbb^hDU3m zniDQT%Kk57#7GF?RcV)Zs-ENOI!nw>kfgyc9sBa_Gap5E2JTtBYEkdei~HPF)Oo3V zSN6MSo_aT9w`19)c{7T-PH5M8by+B&Upg$pinczwd~J5W9=&_F>5`e3UO4thgNj== zKCp0&w^P?!Iya~)%y#<|@C3jtB!|jf@_QX3MtQB}2A1O-}O}4N{=Q1welgsQR4aXJ<9jubJymx z@@5$YSxd#qeJU0*Ile(^ro4YkL%uWiWPx&Y%_=tAM!O2L)@xQN_j?*aW?(a-umZ9% z4EoH%X?2d}=S$9P%-%`QaAuU2*ic94=kqN*s3HWPh1A*s@D&wB)(w$pp6D7uqM!ak z65^6W)CX&sq6@rk5VRWgax>HE(JX2*CovAT=i$F*9d*wiq5LF`{qeQuUVmfyW2LhB zas5-Bmi1b_aMt=hcXpCHuUVYlxrg%V!FQBjP^SL5^4-{Bqk2{Dms+yFTx)UX$&r8g z?4K{;8pnj;>?83pc^{9}M0gG&;x?3mXb^FMqpKU5@mVCv@-GZWzYJb0!(WKCGM=EF zxVUk7j;u^1^x-pdGaD=WX3u8*=dh1hd*uc34dp{t7^mJBU4g?g&_ftFM7-G1uwl>w zAEY7IidM-%skB7KR4g|dU}xGgaZkxaj-d^y^tNPGmUbVhE5TIJNH#j|jwGeH%Fxh&kEsQKae<5HgTpOV&t#}973_{(+cznIUy+4k@w zTOQc|ShJP?m>X`D-=J0Tsx>P<$<55DY~N?bwvp>Tnlt+&cJc5dduxt8vWGW7My^%u zYr1E3mti&B73-7{^7=eZXI@@D`Y#pdBPYIGFNTqc9JK|rTf`K%7^=Hr{{R15abX^H;B*}&HVRKQJL z=PE}>1(yMyGmsbe@~dV9`THes2v0XEFhrlS5~siNicni4vcV52A2q-6(B7jBhxS>t zwdQiDeQt;9!8bQ5eP}^ROYyJ7@bTx^O!1TPV{`gY(av#gL zFHZ|~P~`w}0@xEccZ<*@mS$lfCfR62)-P|-fN=t7&Ol8W5ZzL};bbOZPqZ&6AHG3& zCGn9yv1Zz|nu$oeen%-=u4J$a{9dd*AGIV1jUbAd1v(px8lNmg1B>aF*brk#ot_3! zT_}{rGCW|}P$=rj5@HP+I0HTt2#hz|@Y#GA`sMT6P$O(}>Jmy_qdFd2rzV$W>CQKr zG~o%w2`~v%{3B>pYd0A&^hN1{tcF*j$cRjtp_?@!X|Fu@sT&S#r+1T#%EDdn2H=_c zi}Z;V=}_luw926ndW%@y*;(moZxN%cZPy_>AblNvJt05YbrRdGS0TU;AhA8r5-~YK zFsdgMVQTCO1B{?LvPK_e-yR(~^8C5iKOZf+m&{h&v(>ZW=QaQ7)2|+%uWVSorhosn zD*>+=@Gb?sQJm8FSTk!uLBQ{}G;HW`7-cyW)v-t@Qd-g=7!@E^{T2=EEPYmP_v$Ra zhfi&D5~rv!l!Vvf4|V`W0hpWMk9tULSQTYEqLmkB8IvOylZ^w|Q^4mHkl>!WDf3|e zz_H3N>~y!;A+c*{HpX!%{~bNZe_I!({CMGW<@;O2mh1P$H|!P1_ErI7LP7v$V}pnrrs&|)r-!h9IYyt(~z99+L{7qq)$}8 z1za?L=%E;*j$d8Swo}4WQS*Dhg!1irMNwz6&h~093i%tQy1rL}_K0~j@(tJ{$@+e= zGP_Vv;B;nWxFdRfzztEKiTcJ&pBFuWydmCkSu#Q_VkkAvmh3tbdzx9Jn%4M6bIsE> zvC;aD2>vp~GAkg}aDxBS`B0_$8Cy>hbQR>*evQ4HzFJVCP9>>@mEzWOJ~x2SGYHMT*aM4lwCQ}*$PPK4gd zmz6h`wOw%Ab*_VJ7YJgffaRJyNHlq@LkS;`Jsx%LO?M5x##(Q&bdlY{TTDlEPW& z3DiW4<8jU|mD@Fx>igLW}33BGM`C#tt4o5f-<7_By&1Zaiq&G#J0z#ehBLE; z(7YX8hNwW3!&S#Zm^h;{Ns?(CMHP!i)bvxnEQ!@xH_q0bQ>ouVYphiI>6ha^Tp`>Z zt1JnJi*nK`Dg>9&XhqFRevVv)7rR%N_;O(f=Y~{oh%3!!^1+bzInh_g1PD@-ZHVZv zCwvZvN+sw9$}M>y=nou6zIx1|{VoRI|4FU(a80s}Cr{Cz7JZ*dvU;NWTirMMB+|^# zZm|ZrvYeGAhW$31%O18D7UX~kazg%edpb~@?nHXHPevz^L{kIJ^}&T$9Sv~FMMghJ zfZ9sZrL*2X>ozFmEEx6sD*pjVviReA`@~KXdIok-p3np`xHP{c8x5cWlFeWf(vfx7 zv|My&OHpSO#};*_I|3mm{K3xuho+(UfOcA%t|5NrLKkKLl#OQW{0|4t9m-{z_1!vF zS;Y;Wq#1O*+eUeL%|~3@wC^#+#!VibK4AvcbJ{RIADnzP*@#Bv<7OrYMbVk5!@#OY zIE14Lh0J!@j#f2xr@u0m0kkX2dXFS14=Q2cojrlfZ!xJ1?bo60pGN345#PGQ$c-IETE(pC-ga$DartPNyF?Oy1|-yoOrz&k~m z-ZXbOtk+w;h}h>WK{M+C&CYf z?kvbffQHaKQVDgTs(sj@M1$28k;<0otX03_#y52B%ubMIApPHTsV3f_EnhYYTl z4RD^J<{6?^6|ophNOBFQQm6fhkT8@s!6UGgk*}f_zKL7EO~AEJnf>Cowr0_SpHHrV zlKapIXy%}ORjlyUnX|-~RG0#Q={LX>fCr)>63lgS0DWH!7{RQU<+NY`-DUzlJLtw9 zGGMGYv;oth6(ZfNw5uc6NLnl}fzXs;@~L1(nNoj5d)`zH+!i!hTr5UU8VnYX|63WH z1exQmG2kS>kB4e+^`eKN1PT{E4{GQ6xLJ>&_rTWh_;l!90=)?fUiJ0x&p=`-_JF=B z)t#eS1==w;7VjtbO?;gAoCZ`peU+5IX4SQSqyQrd{gl4wE6@#nMJi$Uw97Y?mil}c zj+}0HIGRf7QnRYuK#%HNUnB#)D?<84J*xAm*r%4jO%u z<%5gHRC0%KdncjLh%13w?GygvO(MX3b+tZxJhaP8cW?fQS4hA{udDb$nu(w7czDkz zKmRmk&fH0s=S$h0=iVHg9+_2GQrbZIW|v8SD?K zB52VpG(hvoMrE1Kpv~{{;}Y!0NGJGNMYQ>Hxr%Ajr3KCki)K$?#~Q(N5t>>g!x5nF zy>5rmf~2*l*3H1}2=!NfU97Z3+hGq;uH3Qc%@+|Y+*Jwl?g>MOUsi8kHD~T!TUXCf zxAyuPe! z%JPCmMR_r69u5IY;h>im@Cu%|or#Bg=%C%JqT6a}l=%UmX_1WUu3~kdS#A2Ryf;;J z|CPtc@r+A^RZ4yRPCrM8gI?qKlNF4cc+ALk7shMHj@CaP{M7S)R_%EpbmI?yxzTf* zhgHjWna7=^*K^emFos3gMb3{mSbRPg3fmS+<>leRaAgIZ`Io~mm6r$e^D$=m-XIcQ z1#>;vMWXbO=#p^3jMqK%!*Ev(?ZqI%RpB@O+QaESwZXn}hdq73&w}EB-{{Pl+rRqp z$1k#v_VrsuFMc@zSX6(b6d*^T-%{Lo!W1=!l<&xdHN{TMXFSjCXHSiX6Jb+H^O%vklgeE3SEsgOjixVXSxPzGNC zw}P;lz_X%sMmVFdX)WeqAVY_R0p@lHO3Zf&)+!iYe*hs6V|wP%Grsim9p&UyR=&@_ zb39`7H}~5&_1IHciTL#Ik+^$;F)K%YbO_1#u1V+MyBfaFMSM?2`PHLtyIqz8I7LwNFU~4(=Orc1Z#)|pUh}h!o0qQ6d9vu8`#=2Z@W-1@ zH{{5sEkE-QZ-4abWhdp_{4kwLAXn~RCOv9s%nRf*UI)2Ng508zT4|vqg={Fxgax?F z2TpyyJZBy@Sb35YzJt>g8r{*L8O|?@8Z#QE8R0JCSG=ViO=o)KGm;A|jxj@Q)I}TQ z#*t)wH*FX-qPabM^9^TlXYu!s>~tP`3?zBY4G%`Drk$Z&^~fIo{qPgHZ*v9omhu~y z!w0J>wAq}_T%Vi8+_B1vLaDSAd3;NABRI5(gn}N5Z83$ z-xaX0W2kP18{=*Xxbw>cfB@u-svy+N?kd>vFM`|aM%1Wm4j7&K7r}c$~sWgv$8@PGX z75}xP_e6Gn-)v=#-ox%)_x3ffpFd%YSS0sfcHZVQHgD`h%>EManCyhEy7#BTuWgnC&!NrvL3{BorB-bVcIGQ9G=O#l0=6=kx#UOmss&hMm0zNeowTU|zv zeD#&_<7P+Y?#uIs9tuU>%w*qF$IfwHlymb1x7@dV+v*#Fm*if(c#g7M ze{0HA+u2vV^5QeUTf5wG%~d3)7WSaL1+vL~=UuKF@>4!nkIxE(G6Ts++$EBc^Ejh5J4B=6)Z;!(Xc8Yi>JVSl+DT$8W!l7!GV&G&j8QXHSe>H-C6X&8(HDHnR7Jx3`Wv@AMtN z{yi~&+4cp;ZMtXlDI-oPZYnzAvKHz~GxArwX32#it&&cPH`)pd^Fn@yTXh8jd9E@g z54ZdsA-7+31_H=@Y$?GjZ}>;v0}?LEn=dH28xI}!!LO(7W=e(A8M_!>e{c&@S%v(| zmC%GRUS{O_i*V_>->bZJcRcvOj#J{t&)I+D*weSPj(ub6%Jn;|yU#vn!Q#a$mazBt zJp9m}+FLriJCAJ}8ENXCK5i)zJ*j)2Ws9!3c;m(^FTNNuu7V!V!26yGX>ELhH&zge zMqN^VL4nmGS*j{yJ|qal)0H@$c>8Fw`g}^UC!ggi`SekKesM7_{}&%paLGN6qO(^1 zo*gN`xCP=-TTEJUl*`93qrYU6U`WaJgQwRop`ggvfnhTf^1;Z;Gp@VzqOHz#uIo?e z=$JNc^w^H^$DJ3t(0$E{wO8GA;ygLA$%kN>2;o zn=C2L2~)T3aN&Vpy7R6-l2+1{;0E2JW#N?@e)*c{b;3AzrdPj5iIr(ujD8-ID7G1n?Xn z9C1NB(sb7X*9sR7j;w*tz19O1WQ87gr*0W=M*=dT7<>Pe9Ed))%WX9)W?is){iGT5 zPG7&?qFmK6;o`0I4(HtI7oW+V|M39xuEbSh899;;um%W}2e_#0q}S(i zA8=q3kLv<_%|M>$gOmzmEF4d<9H6=prVOZe1J4q)lz;3)Hm?EjC@%7o@VsBZ z@5FnCLh2Ot$>sjZ4w=2-Mh-dVQ+*Nr$m|8iPr-;jD_1~vURb9RX;!?x&>9MrxKQ__ zG@7RB)1V#yRdeSbR`gH&W%eEpZDh6}>bT z?;$yUp*Pek(14dfF>~r7J$Y41%jQhDaMZKaF*eM0<}5Y$$lRCTu!8q_&;}&A5jn0@ z#*h;ekH+xMjZBA$ma~m=C^*ZOHGSq~uk=v93+W9G73KN@2W>ujPmGbmnGcy*Ze}yjD|sgjzkve& zd^yBl20kuMJ?K?_Nw4VE@juix7J6Ykb5TIS3*#lv);3qv&rp81oIfRIA1|M`es%wc z#(g{g;9BEe3eg*Vs)S5pQb)WBYVPo}vO>K2b6`K<54ZxkFBYi zFO3Y#hQ5f$fHad+fmmT{d2j%uj-GHElX6z!#A3v7QC_}yL*ynTH+l9PdI$L9JM8|cqmMh`*lCw+W_|Q2e+I~s&*7ceqZo@4>6m!2mYa(il;bXO zmqsH+pLRw5rpY;<+hLvUhsYjtp=X@A(fr_=gM$e30XIKu5u#6td9ivfe-?e=iuK3N znR~{w)oo>so$9e$f(v8xCK@lcKVCU&{p!Ahe{ijKkwBTW0B^S@g0EuY3t_V`ih1F2 zd1*=JyKSD+F6-TPIyOkFtFb*f%FFFXJL`&SYo9%K_B=c`#qigkeIj0N-@4RwuHz5# z7-Gp7aq)7S^93z_Q2hjR4EfNU$VP=U1=bv^*QYphG|xd>4p&kh_b`1)V|L+wF{KAV z0&t$0?JYLim#dt+T7Su3s;yB!(TfTi=CL6hjrsI z>0nNd7D7N=lOlORER;cVMuOy@AQ?HTJ@Pl?j`Txcb0r^mpA6x0@u0l_l^OXpdJfGW z06l*CDOP=rb-|KT7h2i1*7?d(ea#E6;^~j>TEFq)4f4tBFWyKgxxr^UZ1Bt6_T);1 z@OJCMYS8ZSbt zwr<<9X8O$1^18BL%Zbq=&MzNQQd&4{`tUQ)o_A`_(w1S>&Hmizuvuf4pI5u`G}~(1 z8;mZBvg9tT4cB3O!nhw+=tW$@Zja=-k;&AJgq7|Cl7_XAvJ{#tk5Csw^zGv8F>8D8 z3p$3@HkFpw1Wr4BN^z`iSo5>)Rlee5rYi00dR*rRPUt$aA4<#+8-A8up>4%no)WJJ zJETxvp5LqHNI9h?fj>vMmmTsQbRtKP^9!De!B~_@u*Q;1E`jOcfGL{V?w{(2e@NKzLa^dEA`f$d{*31Ft2z0H=BFmlquX?o-E zg@u)kN@H_N6>fQAOI9nDyAMj>+uV|op(2J&Zi!hw=zQ%Vr-xVc;)O$ztQJX`#G2E} z^_bW6rYoTIWS3oY%TwjGLPo2BFrlKcW zr(FEfJ2%|J{-oEOaV8tt{ruF)FY~$A|D8Pol}@`uQ;;5omeiKiLs1Yx0cr&={qTg> zavgIT@9fo2`!x1+MA(1bJ zk)@b?=J=AMXJoC9hDNdH^)6f{9)o+t2NudB7BB33$=EYu{S9y#;pKF|<8o+{l!Ggd zpw)4Qbi=E`Q{>N6PAe_MDO;FL)tI!HusmLfBU+OQ`G%s(XyMTE9eN;xn_EXT z*;d*AFrS!O)JJlZr3!YoOgVINxtCv;>qj**OJ0sU$5UkSSW2TIe}EowBMBBV>;(?y zd2=bfiOb=kH5n(Kn3vqs=YcdscZ_hRFw-dZNuj8qr4G+@t5CUC9Ew(yS;elLl~3Go z?1?9xa^}Ou`KMI8j?#W7p$N(tw*J@|=e0If%2U^^m^XRWoMXnR!*f;s;J!y?*6z=G4p7PcP8}k6yd& z`KveDAKP@^IU7!6C@n;5_$c;~{J2Hqc|IdlOD%-to+vVl*^!qY0&|`a(jSCEc-ja# z+YTsrG5by6#v6;4y|jn zj~cbKw4$^K*0sE-s#%{F_Xcwus2t$%`aL1P*X|28MaM*n0&bhd=E4C;z-7l_2)5d= z7y3^1W7e&`g}Va&Cz#*w=)ootokB(&GHiJCfDE^llrxqpu8w4^yKMNvg=fUizH-B{ zBU(@DUOGOeom4TT;)=6JOsa0G7C3*azRBvfH!%i*y_(!3E1T1qc7RmF)!%kjkBUn> z*)fN-GjHta5$)bYyJL8}+_;;mo4Y(piPESj3S6+?b=RS3w4yy6Wyk0{^}ojtv18OX zZ(MG)P18hMRYqI21dkn%71n4Qr~+B-&>{V|Y%cStZ}#*cfi2Vf(bO2)X(d{trf7I8 z|DvF2t*RiJzdaOZ9yV8-)w7&Z-=ghe@Vgzdvr5nj_}5wwk|2Xu%s*3r)+#GlZfDy^ zfB3Eb=IBEQwQ*-(c;Ss_qsy7?p{F=+Woi$yaKM;0(miP?79WH_6&MJ&htEHFXf&(; z_QTP{``I^Mcp(B*EV)9TD=kmHirlc?JsPwerUEs}^7liV6uQ61pM9E7$2KdoR5{!} zwA^1b8i$@NGA8%r==^;RYQ|Mc6Aai9_=WE=MQ-m+zL{trsuT!l;C%q0s{@w51Ga+0Zew?{3Mul0 zQdXu_Dy6;2*Ak^=NSpxd5`7admy37qXmgy==6c|FB;=|pUnM~iUi}sI0OyzEwfmJI z@%u|cnMk7)_yCJL=O#Y8U;Y}fuDx=FqoT?oXcOHB^rtg%%HPO60%x>0xi=9t*)(uI zorx2$?HpFF>`i`_s7#?Hyl3juv~`xRyj3<`#$l7U61OTLW9dR(8#rgLq-k1ZX)EhC z`ZiZzth}my;;jONUkU<#B8NYpodFs-du1qRWl0(yGS*JxGOjY@wV%sa4u`c$In`*O|U~_wumnCvdRt>yQXW|8H4Pa^PEEOrFeC$T_ z!}+;PwdjE}(wn?C5h*gdZ{Qr!59jA{k$|o0O@5cCs_)P1+_;Fz9w{Oy!0X}4MgK%rKFfO72jRFyZDXb4~xITixD|LUkqtu z_o_Co=UKF0;_xsB#q7G3Flb`A@`PH_4}S1u`GO(l1i>@Inu z3_f+YLI-3gnTrXTU>aQUg9OPp)L)6T*2~Caj`6}h1_My$b!VGxuua$Cs z>J0wBG5GO#fZwgTJsMYK!?qy2qkPHXPcYywG2jae!GHd+{K5cVb24mEDc2p2AGYW$ zK6cFAoBS$~Le6z(Gi=e9+_rJtutkQR<@YB4kw_utu!8T$&`*Jvv1@@hV;=f?lYdM2 z4CeV7#pjUV|5LQ%G^Ay$j4hM$;~trnmdcn##Z|>d4@4vA#&uW+Qho4Rqzm&w3ZF06 zr=|I&&;Kbte?VWIhR2wFCBH5D?CDK@lkkKMff{{YoyNgoaTOW!g%wJLsA|AR3bc}Y zaL?pYF2{N<_pe!jzzI1f6Ye~w2vCB(2(VMQ9H$Cc=_8I)MlQc1EsgU#=wb)AWgMry zm6ZXmIPOzqhO@*<{J9ouqEOEH z6**gS)Vp$a<-DGw)SV3BS9`2q_;mpm zx`1jccBYxSfV$GJurZw1=>m>jC~#_|3mOVAq;!G%TaDa)0H?PKI04G-NEPe|{04_Z zGyI0+8^HTd%XY{ZcnOxR7qH1Y*ffH**}%Q&$330NEBdum@L=mr_9bj7c1a)qq(9A2 zCqdu^?QghELcdU~EyXPK>H(}B8U4rFu|(j+c>N>cwwfX!oVP*8D>HB&mOd4*&fesa zgj4G$1LU5Oxvn))f8fns1z6HGZbJ*F7nXSPxyVG{@LNkv`vm-U61;T2yw`w7W|>M> zcX3^Vt26^1wg~viSLXQTZStvxO)M^kE*GkWxaW`oFYuE*IDUdR@DCY6_?5aL{qgIs zSY)mj96!eJ?|>&8p`|cGCV2DoiY3O^UXB-VUkbQ7mcmGI;McJE$aaA7*v9?_e)(Ji zyD(D81j=f=O^&%9gJ;7|%`n=`GI%a72G50_!eV!3AEC>J4dgVy%PPP_?}7f?G^|q4+kn^Go759tlWha<)0ubydyIfBqAnJh3>$do z_QQKv{vQDguj+6jgz~#w90uOG{qP<(@s{-_e@K)S8iL@ki?Dvb&24Nm$<6+<5(C}{ zY8lBfvam60tkK3@eArjos9}6~{a?!u(2olb3KPDz03~e{r>TLS2D5aoZ8274*T&GpipK4vZ!2xF>lZU6Xc|dup zBzWkKPf?YU*2dPctqWRJ@t5iN>x$N0tsk`7Jgw&MRYLP*M>=>tkHfszT)zw5JQdyl%Lpzh( zN$|++&hvssem-bm!?!yT$=3$h9r-G5=VyyH(%pgvqfuj_oDd!dnmwm}!0w#MZ6&7x zyK_3=#eVXoG?P$)0{NY5v&fs^iqL38BIpI;(-FyK_QIg|bY?%~ZxoB@ zM;`H(XNrl#&eEUG?1%gfk2k<3Vqg9N_T`!V=*B+uJ3a>3L-8>%_bWq(!uU6a8;7Q& zI^cf<;tfx5`Oh)nHADUl4dA?9smCoVlXJj#!-x4!y9qMo_#uD9nrN+Rlv9{VE+b&` z@um=P953XuMZh7okcpAtIKS3V3g4?@&rAM#dpqrVo0c^V6C88pr#UNl{+|@?SJ{8- zF9;vMwH1oVhO`f9$uO1FM$n;sz2N&bc?bC6`-zqoqSMx!{65h(GLsbV2ba&6DO#_w zA5B_(!^N7F=Hc12NJi!uK$o_$1gqe>G(IKULC1n*W{w4K$7oCp*^xgp_?W=^RTG>Z zVqJX9*Bkzc(+N5_C~+FQ0$2~oq69gfYAx@ePjdx4PKw;*A4H(zq*VV(r=iGic56fwvVM3yYe{*LR{r&P^(En)MiOp+K5#Ph2 zhkB}q7zr8?X!5QD~gv=RMS#T;lY4YK3hE69~{ zQyt-U8RKldl8>|KhhL?-iJ0pdm@y}leb9&fe2h6>*y4)>Tn0v(yI2>rI?nG%u4miW zvj)E+_Gfc@l+4&)7dz4Lr-t`ul)+!G(H|c3btn#(YD_dP zPYw3?2=C9y27Dgqko+m~<OHe7lHPV>$yrv|rs}(ZSbfg1064`yGP!dNIDfe4kU! zjP+v0P;S%y1o&=_|0?M|Auq2N=+TKE5P_!x5<#yX1miMU2G;dC1W zxNK))F5kw-nd8S?z6$sS?2mvoP;=OESWh$XVm)0ASm+MQK&6nPk2CeBGjU=qrS)0Z zhrWcXAI{By&4||mC*)4B!s42NU{2o&pJKS>D#&6k$wFF|{4;zCKBk3yp+JncFOhF7 z*QqfTbHuU+?Zh0pojna2yd6yvnP}1al{pnj7x>unc4BOAm;Q=&#+;{iMb4tI7~PcK z(OJnF?Hka7&k8@O2UoGl?#g4(U<}0p#*7q4q{8kZCMo+!-$)P0uaY{UZ#<5Im?Q!n z-y_grns(ZkNlbbG>(zMeCY-8L^*L`$DJv)_^p};naLW_-FJm#!!Y+R-R;reU7Ic-W zg?`n(pi9M@Ou7k#`z!qI(!eWb{KXpMc?0|1TA_3;xB!8>&R!{68o(=NZTtoAZ@%-= zg0nW?cH;woJdU+BKRoBuD?2NmWeO`~OPNn5XBoq*EVAKP;6d9fv9(XSNwV znnEyRCX8t@&(MuF*>w6i9q6w~2Yb!i5}`;YDU#RFELs|;#oM7SIA~!%vM$kfOeQ%# zCjDh+*VFx~t|oV=W^_${Cb3k%$Pb!8@^)Bc#O#DDrc5DP$A^#aM+j}Q`<3MP4!Hbi zBdN%^j$AP)ox>?QR!)ca371jPW_)k*)x>y17g9Dr=oz=W<>W8I?k*%gp>M{1N#;ul z(gXQ5i)l@%U!-%~{-R&-@2-;mEZXt&7}BR#rsAl^NO6W zTOb;$jA7;z6t^^Sm5- ziZzJVMYu>`<1DHF+4d`$=jE`A{|i{0mlrq-ip+y;=;Qbc$ge5y0uF0UK>=J3 z7~Jm?{#*kw@xpB`Y)C{Sy9gf~>(gt>iLi^L_cb->N2#N<+@OFKDH^m-spkEb8TrZF zuLcdIbT&`r>oW1vK1JZaQ~FB4%eado$84#Sc>_MH53sp!0iLhP6j^{zeR^Jw=tI9X zc@DR^oJOY2t*oSZRkfiF`8l&+F$DRsb*(86WfDUhT1yHnWOGHoEHeCKVPoi)kRrECZ|O&9 z5IRZu4YObM^+deZtf|jtm6vHkfO7#@bsW+4jLgZ^5*I{mRs|}lbfib5@ zmoXQ_IW3+Lxp9v|4wGhwBZ6|~T%b7>;Wikwxeflpm{X+Zw#Q|D*F@+OPJd+CT8BFr z{5)EIji0NW5}(Ge)-Z?LfvemVo(O{I5pSe4(iDkDv=KN=iF8I5Mpj1dj_i(n6|vDT z=_C5*KKh_E1+f+XwBn56ly16L%M}K4b-*$EmBs0;wDU6>`8D>9{vqDf4g*~{>JEw1 zEs5L}`5=;vsMGO7E{QM+BzriG4dJ*^O#W}m5k*@)Jfb-fP zvd(MiYqC;qk)X3Rr~PzeP;k2;g$qtn(tgl|o#H+l<;;s94k)tuRmvn<36RoX}&A8#ZPV zWC!1iW~YpUw!TDBG#w!xjE4P)QV1G+eXXjZ^|g9Qby4a_MfhGs=XgFhIUU#m#5zP9 zy7`8am{ZH~IEq&PlpVFMcyH8)egT1K|?JFud$;k3vOh<;`GUUb;3$W7y$`Vkt0PBKkrW=%@<|V%Z*@4$lyMR8_lc@nTj~at(0}Z?6pBe9zKAwB3p-7;;W`RZZzakq zEU8;vrjGWb)5oa4CY|cu1i`}|Sso#KWZ+NPBe1hMQB`B$7UKb(Qpg?&-0*o` z0dCkX48@g+vT_42hrJW98S!=4xpO!yozeXRXLSAJ)~{s7*Mau|0SlY>4s7C2#H}I2 z&jAa6idJkUE}Rt`$VvZbE{M1_**X!wH*FoGBUOgW#>qsq!XRR{L44ZM!}S$s4P0Ll z-6A^WI6r1^Mej|N#+XoH6~l?(!X3p!qricASPWzp$lh?k`3 zfH!+0TK;e7jF+y;pi|wee+wNSj1JdV-XF;Ah-lN$fc{kUCc6_AHG|RN`pWy0(pT1o zqGSv@emZn~P&%ZqoDTGr#u71bY+@Ov-_k^Nt4W6R4`TzFk-iEV!WRWR-PnPCuYi7= zBnTdQW$B?c*}(5tmH|K3WDLm!x`!@?M2rV?u#nJiRBQF|UiQeR%1hTxOqL93eh(pp@J$x&lMH;`9j~qYX#u9yhB2v&V4ml(> zCpZnviDSV#VtMdX%m^pxE62;@gTU*h{qnvvk2HpYM~nygs}#rN2YepQ6G(Gt;-T^5 z{Lwxlgf*Vv$$+vVwJ6%3KZ?UzX&)i@li8WTpE~bw`HM~TVqS^<(>{XwZ^e1giv~QL zLsl9ntvAsNcv>@0vo!fD6>iV1*W>(X3E8H4*e0mc+ll&W3qN)fak^~y{nGCN4;xF@ zt>mF}X>Vw~*}$0V*=@FS8ssx`?;KI9*AmSJ71L+VZu6-0GU#x>0S{KO#v&B8H_@JM zMZOB;o89gn79sxVj*uYcY_1XPaY}p)v)kM}K)2ujqHRQHK`XgQ_ zaQ==vj{6!Ee`y+>qLs;?wJgWfI&@bPd5^}>wzx)mg_<_ zW)+uOvJfm-AHxZTp-;8}F#89~NhL zh-cRTm}UAr+@~j72BXh);UhyA;FUAfmnv`@2dqLDvf=kje=~G}UTv^)e|d@7fa^jw z{B~}WNE@(|!5_WJ&l42}4bzTgw@KMCnHzkW!T+A)Wi2>q^~E>Rf-A!F6t}z;jfcgB`Q>hk2CSpKQJC4>P#V^@katb9j9STHKD+ z8T{;UiaF3|&VfN-c4xy#Fz4)AuDfUPnRNXfvcA)>9H-24`UQ>OZ2$ZJin7j>Wx7%#T!?;Inhg=VmJb~+7eYXB$=@n}*Y-Scr zN`F%@uw%-b0#BCy+D(0t2Ev#&^QT};{pF-go0&z+)ZY{g>8A1~*Wdaq{jD2W*gJ~8nIXGc;8Kw8B|Q&BHu2vP0IcvvcqFt*mt~}s7kjYO)>n)%yt=mBoFkK zHI}7AWTg0mw8`)z4O)#==|&)gHXiIXM?xPE6IznF63Yc#t2ka;7L2J?azz>@l00O6 zjq6o2-7-k6#kiAhj-xaXM(9;G%ozrrtsIZgtDlAudX))7`+J?+03PdxKD^EKf#Loc z-81+yQ4lt-KpT6`e(?L@7juh7p)A@+ev~LhJsZ=aak*sEF!e!3YBv}fDAAu1QG<%9 z57})}`XDnDi9;(y-TsuQOt&)hA-ml@@S+W?CWwuUMg)5)`^jS*nKIbL+i=Tf&>C5p zZY1=9)8Xr}klPN(jqeX+ChprJ&U!FWQlFxg$zMjh)V>0@Z4nue8QOg|NITdp%WOX0 zqxinSYBjYBCj*9EBj0PIfH7=@X^-;Kx&@d|4Vb67-k7!~1(Q5Xc(|<@W#HLutjlF7 zJTnn{ILP&8J+Q!^XU_}0!HY}QL~mG{>I-txb|f2qKYLW@4IMAiMdQCD+M3J;h8@Xn zvz^l*KaP&D*)Y1DGR&Z2>P>c=M|m5*;1$yLcO}HwLZBk?BT4Z+IKN9tyebNb^Vc4xl|0O>P~RqLs;CM!Vfye!O4DA8)k# zbdYvf4=q-Vccn7FpWR{&Tx*2g%7RI0Rt5~|*3G0h%KUzI3k;`pVjzsEfBj)v3_O{3 zD;rOL7*j`6c#>a$pBB?KpAip~>nX5;1A?AJ z&ERp8R2yXk(=b1bidLVoyQvOZj}Cz?c<78~}^``F{+Hcw;}lm~Li&>#9_gSze@wt;JW8U^B zVpsj!QtT>=?$Kjdmghi4xcR#z?>T=>S;n{R|2l(dTByt7z_g$+2pq_}u9i zw;BKoUx={&N3ht-^@GibuMWTq-)L~ymo62Ex)D%GgJw=aBjp@RI+Yw&w$3jX87_*o-{`@EUVSMFesXy!~N~ zIW!P9`33mR*k{lj`hm~46l}(RV*o7n8#LYnVa2{f`wTuJ{0f}^-p22d!vCVYadg?_ z*NNJutUUxj|D<=-Mw?CahLH9hJeG=G#EXdMb?yKM|?TbY75QB{H*|~pZ(x`&}ja?`8yC0Ro_eG*lI0rZSbSL6T z+<%1)kr79DG0`;2>=KVh5-qy(!23h*C;^|M1;6(w?GQ;#-mE) z$n_%NDF;O^Op$|1_TvGg9FPO%QxAknAujBg zinGUtvR(PVGpp;M?jGg!G?MPNuv8dBDhi2-w8~`-NIpv4lv|pM@!j5nWbTJ* zXHA<_(mu>S-`O-{Z2hch#}zN}o$Onp{t{_O$Ba0A>1h1C;KsF*BKg!8Gd0MT&MR`bJ4&11XM>3)tXD zX3y^E`|Q4dRZg8auCS%aHq+fbt$OOjc)@(nOjqq>^~K&^hW{O9)n&t5H?GEy#!TBwp zgTTq}u6zPLC7&6QdPdn@%&>uZehVH{&$@B0ltXMEg;{1p3PUU#(`r*xlAw2zH2El{e zO-BA4#O}EbB01nrQn(hca?FHSvQ(>HA)kZ!s-gZub38Z4VsUyDoK*Sz9*;v+y^bDP zc0jX`ba;O=k`AZqdm3{%h7`n9Fxi;N@;R%2{h9uTn9l0MsL+{{p3;(_hx1UAo+@*S zvd8c9cn}$}ID1spIS4&aK(LZvr(lkLid01U9KW+I8 z=V38ak*bw^55Whgmat`#L?(8&4ctKf7~M@vvsw$=*|MG#LI8>5f}REIM^W zNkvK9@N3SgPqPlc_a>YLHt}_sVpIWQ?^hSQ^Ew+lV4#Ckt5-lDu7WO7NcF*rTABB*+T zV}rYb&j!_aurs(YxH8xi)PmSm_~^nBL$L$5$}^(j91E)sPaU+No6oW2%b@emOgh_0 zBS$1Z`u~{@B4#){rS(_z|IW?aU+{Gg;Oi~>OnB=}*O{3Lj9%f4 z>AgfjrD$bzOw46uE0*LX)=sRceD=$?CI5!Jg|K(3ihG6D-sF!7tC6WM^`6OvHbZ$n zH_>KO@*QNxpnW+du4TwsW@cX#>vk#NAK z$D86Im7#QHTE^^7(c-gvo*<>|7qdG>jQZAZe%Hi9gU#j=%ATD$ zo3R)kW$LC0A9d>9VsJBH>SpkTHH^n``P`*AF8R>^0Kd==x{pA)NZwE6m82uHe0>mY z$cGkfHc20dHs;OKj}wu+!P@Y-g*I4?mLZ16XEE+>8nZZxSsZPQPLHmLs-EcB=&tCq zQ8gazj4q6>jP^vesLYwdEY6qm^DG`(v-;0svp)mrU~N|af2BitvPSZ=yEVKY*8@Ml z15gg18`-`366K|45-aj;v*Gt6QwW{;%JedX&PWa=YRb(98TZC@ZYPACdA<{h4e+oU zW$i7gGsT=fS0+2b;jK6E{0Aa0CtWpwcpn3Jy;7zn4-=4QYUaRFsepyNk$($Bc%}d=@28|Oish;FEVw7>#w6BMI6ficX z-*F*e4jM4egA>>YglJQdCa!x}`LxIQ*=viTLFP#tZIlR;l02ZkAwQ>m3}2M$64b{1 z8pC?&K>gYr+2ykLI4zVoz6FWXkv^VE%+Yemi`k$CjFsCFEZ!~hbB{c7;Jkg0{rZtd z)Hj}b#LcWO{cleVh9CHV-!AZLJx<$C;IH7e!)oByfAQG9^G0z3T`v85xBDsV171T7 zgbyrM_#Oe+X^T~Xq#E;o$6aOE1UNE*5WDe}*N+2SX@&k?6!I1EB=T=Xtb?DIkguwg za=G!}tJB5r$AH*BbJDnfp879Px-gQOD%Jo%aQmY}Y^uE9J?fyk*Hon>=f z<>xi>oeV1v=_(Ru(Ru8TiPm_kg-OSRCx4dUQ*@5r?quBkP@p5&gLucz_g1atr%sSz+%1J7cvk2wF$!h^ik+75cZ zqRbck^E`i$YhztwZGlL(1GfVE&4;k&KhMV$dn8N$F%3{X3+p$Rz7DV;Q;KlJiZ+L^ zN8QW!sAb5DWY-ubq|+tK5>NY|+N4qi656Gv7lp^ol_xZRU%?eRD9+M$2 zl;}Xk0pxh7ouyg7|J)J*OetVK)UK3rq_Vg#M|Ns9&F;2a-6*zTRiM_W`$v<7D#fC0 z%!l$URVca;wuY@$wX-&~@7!tpzd=8&b@jAAaR2ZJ9~^%F1MNNN67avRA7+mOzXSe+ z9ZrbVYH=V%9pR-~4(JCJd;w3^i zEl>4Zta#@Sxf}n6oQB5BBpIbRrKh?)tb~na)0x_cA1l}{_5ssays~aGgi_bNN>p1J z+T?4ChSb&Ug{!aTd=f63Bu^E%WC>+D|KD)gfM9T3*h&9KxX8c>zEZf*=f~lcY=a%O zu*+!vCjX*OQ?E(xgm-ysPWaNRU<{bFLFr|awT-a8G1y+a9WI{JAz5#quSq-SYYLSoB3s7oE=CB2 zhTB^#p_b5win?_Rx|H7Q7u_)a>Wg3CW6=+n1&`TLhTLHTE}OCw-&wd&9m*OThCodV zn_OAF{*-wys9!DJ62I!A-*CQA%4V|k0{DV%r*Hv})pq-QD~2(XEh?+tA63h;&Eu}R zgxEqKFF_wCnSE55Y-8ZduI`*~mv8T~r}*NNMNCfNm9la3PFY`FscrntMOVeII|H7) zES;eA$_qHIfOLGk5@l~48VSwrbJ!iBpw;K_-45&G@HlqPS3SzE5W6eHy1P+KiPt4U zb>38ADLyx8e$q@z2{xMg8@su@_QF%<^?hxMdciNoqfe&92k_!`IQ$kngl^CA+x^6g zAG|1v+wX?>{r@>HDQRk{zsU>AhO9ef-t+n%bEvwnp8{sCqY)Ew20!`_@&Z0)B%2D( z57&YmKiDxDvH5*|452S2n#K&#M9g5GvCxxj(xaQnPpm$ZpOytTOadQU#js<}OvX4( z)HY)6`RrB1fvle)r!DEiHOccSzm?>a+T$foJ6sg21?4E+E{F5EF014DE=|Qx?fEV$ z+S&;1PbnIC9^COzVv5$LDJ@DxNWMCEO|E`7TUhvZAzP$;{^MqKb-}$wY^{D((Y>7B zRmnBV7oZoC7R5)oY&Ol}MDlP13Im8m`V`d*+aAvK2cGNl_@AHecHyV{`S~8J?YS=1 z`uu#E;ivrkd=)Du@ghZ_`36g2u*22Ihre+pGTEw70u`Z(KrDa{0_^YlE$mG1E-zcE z-<+S%&eU(gKlGc~(tPFf6te~T6Z(_*mwpvnTXb&$ZWS?U2D?taNO>BvDy2E-a&N(^ zqBJS8;;|#6WMlX5x>2he)wQ6%qSPCeA?f0xa_dfZ-RS*2vnl*QAr+KI_ zyy&qUkluxTwE|~DJlAQns_4IJQ(SJ1*=_c1=%|hSJ(?y|Oit8wrOrhWXEeVp)z2T< zyYI-J7vAMx7+0}Yseizl?SOxQB1Mg8L+fQUR6CQsr(Q}6(sbg3=^a>)Y7AJFhPef6 zD{G4aTJK=*%~UU){zI{L1HmT!fc;Xt3>^EOz?ND;B_m@26zdgH`0~YBFS7TPKm1s% ze#9Q*G*a!Hw01hErCl_I1AzXe8;7DM0{uL;$wV?;hbxy!pW}hpaIGCB>!^gLfk#1m z&N>$2FWDv_c@ONUB`h>upGF)34XvBv%WJXzH~wao+LQmZzN}q~vGDNv^exg@=>+ys zJlRlDQn6>g#AJmjB_-9>?unz@NAH;*Z}(c=);;rc-RexG94dAB=-^IVKgDve~-5qR72OH7B>N;3%2m8K*{jGz& z(!qY!!EWtf>pR%#9AH=n%j;lvGy%lH4)%Tr+u6Zx>R{_S*jXH6B!_Tzu)YrVP6vC9 z18nPH*P1Z%O)>?X43L0KCXx{ytdi3h4jP~FUq8XQ*whg}k#%*ji5;w_gJm}VRR?>d zgRvVqr%O862_0-KhwyZ;PdF31JD7ZT2iwxYR(7xj9W36#N;;U-!EC2=i?0Rvu}XX` z;6GNUzD$rbL>2(jS){)VB&dW)MuAGHnt$@?=)?3OPlEpBE+CZw75~IMim{doUa*e} zn6(Zwia+xbmD*80XGi*Fc)&-jZ@@>| zM%UiQ5>Gw-*q(%b^A&3^xbpIK7ri~;6Lw(0N0_s-l?z!H)?tOtaiFYNnl;R2%nY&j z63c1Z|iweg(6KAZ=j^h{LWDl?t_1*skltu7$&n?lf9gBVN=Iq zGn(YtgWVBU87wngd$4g+qDt+o-?9&Xi@i`KJ4e5s*s&MBDTJ`+N4RTD6jRF=c@q(s8F`u`6!3>P)XxY1w8yuo9U;j-swO!NCBHAnrl%cd3eIH9IjrV{{^qwnbam%%gF5`6-npD#|H{vWD1yF%|&s)U@#Ez`z-+}P*Pmr z{iJJxb(VFBRcS-aU9x(ur4XxT^%oQ_T zL-G>;h3Q*WwHM=Xby!;~YG7MqC}A44^2)37MK@n_!M5NP&YAH^b7q_{f7He(`$1nF zD4IX)((9D*m*2elEYGM(v%6=^9>v~V_0Rk$wlU~a9coIfMW39Axdm-1Hit+)Tf4|uy*6}wks}rP;Zqxw89tPe{;AyIpd9fjCE_7b~nhwkN9RiO@V)t~c& zpLFH-7mz5A3JYKirBF+wlxybd6+m536jsK`{jhe^9c^zz0d($_(h*vyn6VEg9CzIK ziQ^|s2;Dh#`n1j&$8WxE>*hNa96w>kO&c!##fcM6>{545964tE#P~@4jjdBB&YXI} z6XU5aPZac^L9qi?KFF7V+!CBvKfM)DBq>bhGR zz5DCBo73{=eTMu|Lz(&rV`5aW#KP>8N>5 z?+$ao~mIBVdtsqz+FYJ;E)Sz!+SJ1Hj@L~+} z4@Dp+Mxswr8v|1^TrN0Hg1--k*!ZrLeXV}%NW$_PLmt25>Yv{#FVJVR%k_)ci+xvp zeOUkE_BYDahqwLm=@)w$+p|f3>l*#ZP3(27eN6kFY1dswW2{JLCbytQq)jSA&b$U` zZG2SBZnKr)bkbc^EETJMpD!FyBaKaEMFsJS`UVAMD_BWE0g}5G1Zsi~zu#?l#2s}D zx;$2gip8O&PRfsXf7eW_#0WmiP-M9gCO6;v`{uNk^D@h(wqy2b?UizySE^`lvF4&? zDE_ckR+Z?Tkegoc0D|5 z+UO(9$?~T?@l5wJckwTj;osk;JX55<$I6Q=KfYb#=i`1RV#@1~^R`~PAbwnJO^qur z50zU3g-Wb2)-c2iS=jAfZ%L)6mbsPM+R934L06?}$t%fQfD!i=$V~Sew7B8ruj zlk}p^YS`Mql*j@7$Wsqk9<-@-D^D-4C?B=*e3r9?3+65Tl^r-CIrcBCVbY$V@2EfM zf6{;Wlm1Q<+r9dl-il49JMMElmv}OVSucS9dB*a zzhL2w$`{-CxE%w2J27rUq+8;1hLo2|v69M4ugew*hij!;yzW+QlG4~vmgC8p(N*bi zI%agmb~%~k^g2tOiZfo~@sxNP!7Mpc!C=9fF68cWYLTE-t5s{y?HZy+Fpy#tTTD~V z`-3x`ZeimLJ4Y@DZTh+A!CgG+9IZMm7Hu1YV`B7*;)TMX(=fsq7BQpOWZ9^b6j=VZtqRB5Ph{ejv3h{=i zv8lMKv#TIp>>c9IQ*s@-T4z_TH^);KlN=#w?C)Dz#2|)8M8@X-KGSp=YdvN-{>Svj zLSq#tFVjNWph@px$<$#gV_DZ$}u-BU%$2JgFDyP z7Tx=c$L6dYQvSeWe|cH4uDE0E?z{W0Rc8Jg6*)#sUvtvpv)Hr$(jI%QuTg&Vj0Jp+oQl(TQT^~Q8ARMl#t||=%^H@cZR$g8bRYG1zP7Yjn zZ^&C)R}t_aSu^rj7rG*`5+~BsFX*a*cV8Kb)Ko{LY83EOWM9Z{!7gEJ3w*OMs)Ezy z7aC_H&_g2NIzq!QsraLpg@WKEr{w3B%|8B&wK(aH98vfZ8R?oPI~ll646m7Q#1C+qBFUv;t%GXR=0 zT8+&7#oYs1#yi=TPPU{n8YQ`ioVio2`sYu7FRXoUHnljqQkr}{Jj+;%r>i)*mC-mN>t5{rKKXt~^ z?lV`kopZ+>HSL9?<#{RQOY|n zAL@Ho+xX}G%-8p3-_*$Z3)ycj`}*t4^u^Pr{hB;%eJ*2+2?2A|R?~dGh*Q6gmP+$A8O@62E z50iHMLcXW(#Mcg-314)(^o2sPlp#&74@M&OL+TqEt88J9C+L;j zHrjUNJN=VOz0BsddHvCRC2sdjR%$zkup#i;#x{1h?5~6KKttYLM?pf-4`G7&zxW4Y z2`=@CxzbX_*I;8_g=3X%6@1SnRxbssv3zfA`5gVDU;KQ|(imfF*Ish+$?96GzM*sR zitdwFES_W7qhecl;eHMq|WAB7H>H@LRA z?s93acva(M$>nwRxD>k!zKXBJx4@^+k1f7kKGlOCvQzQ-q)@1EO;=vXYL(V>Db_r4 zSyFaLcwehq^7q4T3E#``SW+T01V__{2mG)D4P%(gWF^+#f-~~=7Ju5#z)i76u~7}< zM=_HhU$Qpm$>Mh(eW$PZq3X4Ya(DH{((m5g^={FVIqQ~fzB$C6sn$Dw!B?fZ>9;ctEWV>do~wK7DW)%P%c-`%%HzIIjLCsc@t!DgXW z8TboJ9r0?3PEd1l96q<(p;)bUoT55%71YWLgkZT`h)${Yknn|JX);_z;wtk$XsNFv zgu>b(4J>JG6>a#YvyqVe2AiZmN#DUxK z?YF}hs?nEPzqG7D4DuM{&zmhRkbV)LIlrhjJf|Zzv;|{26F$?pnf9vKoFd!8MOBkp zCN1g;cg%^Y$YnThQJ3abyki!2m3m9P`AseHAuYAFEo%NLi@NOj{=oEwT>%u$TiB(T zAx?fI-7 zmx$YjRkTAdE!JX&<5*eEjGZ8|W0kGB2%Y%bTCssNBPrG|cWquiXLx1t;|0z3Rpr|* zx%7b_TPK`)&Y1s?wl@Kcs!0CFU-x@&?qnvJBs0111QH;Sg8*_2cjOE=!<7J$Q{<2v zxj}=d$fX>LC>|)_0U;nB2wux7g6mfiFLcpeU0sh|*Ikp$%l}jT-b^Ndu71D2f0D^u z?^RbbpL_OUKr8So^yU4%D;!(Q2b`z~!ra<|gn zgN6<1Guj*d6uXH%Ip18E)qNaq=^vdq;AUmNcIo&9ZNqCUW#ZiV6DH1ItlZP@`Sags zHA|M9W)&~|hvn5T%WK)LU2DhJnvX>CSN{Vys z+?>f0nPE+sbu%8;L|3@+zdYjEbw^qGu-;vL(MvyjeC2+Yv+vNu_pe%U&!b(7+WJab zmJU#ApV!6(?iu7;vBAANqjPCd4|Yl$%iMK5MSG9+V&B}oYR5hEO8WHdFi2am`b+dr z5w?1F%%KD+4d=|{Nr!z?5*YIHrFgiGb2BrOQd8kRbvSG}*(p|QbR^1#w#v`8RQj`& z?;Ok#k-{R96TU;lZj>*_6V*1`mtCGMw~g>clt;(~SfNI_qSB(Us>_B4Cn8(6x!rjc zez$B3u1T?+ff*084L^_$;MwtF87iJ>l_O*$s29{JA?;#JS`ZQjLnin%WWHl%mkxF% zQz94Ar9CSwO^=OA&&fvr1d|qShl`Zt4Rr3psI?UpDg%dYGW&PuPGD$scdPwuY=8M zjY5J#LPCa|2(g*yj#F}TA~TXQD*R+P#U;hW$ceuAB)2J|!f&%7R3H;a^~mtzoYkkR z_@96Y3%Ln|@m(kLG`mGfW3kZ`v=|fP zib6C(gd@t4ndOR(H{*bn3V)Jdh9%uZItBHr zkSF(^R3feR^=wm8Vznj56nhJlyv(e4w;RrhtUQ}i+CDS0Rc@<<0xYh`A;oYypF0}% zu{#03xsKelN`J1yROxr^b{V>(<)~`sSr(kUX3QzF%TWL(WR7G{M)_?si!{o+OhO;==2e)6<;E zrevsFht=#eDN<5}-z)jzq-dB2E;#6j9OQ`8wG;7t!dXgVxdy+(k&y-IA^2T!!$*@F zCH!yTbf&X8e@pvVdx7;~X}7T5w;j!YaUCf~Gxp!4DzjY-nNF zN=5FVW&m$6mtc@^J*_*mOy?ikbKe6yAANX7{dKgxzJ1`>u|o!r9X+&e_ba_8*zb&d zbnWBMJpK5JhuoWN!}{MiiXFc2*Uvuw{L&YJIqK?$c1IrEQ+wc%$M|d7p`oL!OLu(v z!G*8xTxc&F@i1t^C3oXDe1*E5P9l@~`*N}p5}Xcea$FpTtj}#RxCOJtXF2hkn#Fk* z&oZUC&DbX_INMFPNg(UtxW-1gSn7z5>O7w;4CJKb4qzeQFZ#zh&orcfY8e z9{l5!g-Xnf<@P!aO6I$%N4DtM%?O^!DrJH?IdD-*+>u3~jZC@otQ zv}B2KP6Xe`=}uQFGDck4Sy|W$r=>2q7k}ev$=eOVr@Fur^B0|>KSW9}4-Cm721f!y z&x1QkMQL*QD8jr{asq8ld;iGaw>%`bfBLEY&m2C$#y&9ohAE>*RZJbDx%e{WQLSOL zy1IVo-g)wUbu0Pd=ifN<;tOZArCZmo-@N(Om4Ux(*@AxIOkVX>taWUdZ<$h&G{V;k zX$BIb#G05^Nij(!#c3`6fD_{!wljXnE{B(U+y24tb#wa}KX-*~E0S`iSsGNt#==zJ z#3M%u88G=QKDeXEU=9igd2w`BQK1rTF6LR8848b$iB^gVv!eOz+t`?Ok3Y2Woj;b3 z9yNsDaY}pY)T`Q|=T5WoQ>WRGm&WbaK73-o_R;>wS;pg!vz+}bSI%EIcfoD!cI~49 zWqtZ-@6#=3*uWP~vGUU|Yllxi&eETFT>I!TJofKrS>Vm*`BHN{#?_5kJJ^>W8y8`- z*`p+^5bSYsQgVzp-up+t(~jtXxU{(PxK(jV95b7v9P#8?q(OTVHb`O6Ij(DA63t>N zMmZUr6S+9k24m{>l)5pdv~I+I`BFYJE-ml| zyZ^12sK5!?95|cz61%_QXTBpajqjK}oAl0&(67faW|^2x*(vcRd%QgsS&njSLb{xu zot5HsSWJlt@QK)7j*;w@+?HTQZGupwMRnvW+ zRNGK#UQq3oNJ*wT; zzR$EZc{^|6x3k!i?hCZP(cHH*zHXkReg(_Mj=jbbX}Pawi$r9X#>K|A$xsjpWVTpL z2r##2NKR{M`!vvcYm4FD@Z2ZbdZ1>js5qc?G;R|_9toU882xka- zq8oG=Bi#frWW3HzqNhScSXri($?35Ei||~e3yIL-jA8yoef&}w9E_%$>yO-}3~YGJ zubmu!C+l-N>#i+X_8)av?W5ZdC$DJn;;gDZ1J5qo)fAK&f(T$mc&=co;=vEp z1Q_oh_=smIo$6oP$hzGQ_JOam9Qt;d@Kr8@xsYoP@SjD@(E|DBz**#A@Mt(xD9i}g zX5iZO>Ya#7n*X!7mLgpcpR_=c^=mVmt}So;$u#I{xIp{Oz*|z0FI|y2_MT>=jK zq|8p?5>CryE^O3QvN>!OQwzp1`tBFtjyLc%D9{AgVrubU;12&axSPY^zCaWX;%hNa zk)(WId~vy7vX^?u0svfCzU~1Lr(H_hik|!MYtmp5?!8he_9MB;hvBAGzMz} zmywhE5_)p}<*P)c zi9#%XNZJWm2iU^UDT>23GY)lS?>0e`_cpcDka;!Uc9gIH-Vth~)8ddLu7!6FcCkIN zl-7V~8+BzkJhrl_r7Q48?hyj7EDjJv!a2pOV7Vo{3ADqo&Tfyo7G@#}f+dQ0eiN*R zni@fjQW&j0QfFVHWH)iE)#PzR#YRTrKxc1!Br=?0F@2F*53=h*QC|jzJmFAiSBybe zzrsg@YfX)ZjY|l*X8rU)mU*2>F0o70zT}N}cp_t?n-5e|w++;Y7sE#^j1FOA7iuwl z3XhM)Y;Zg9(SyVa* z8wNrNe^@KQ&*AZ`Ev^CZ8o$YV)% z!Nr|rku0t9JuwL}<$g!n5Wf=#W)~#PPPmkSEya@w2*XNHv6HRF6|`c=SAmg2?wz?5 za1nHD=;jGf7fEa}Hhr5-SN5KwQhlbE!si$*ld?C#eiSZ6`9#^)_trglaOGPohObXk zlr^hMmbJ;6JLlzd??lb&J>=F=V}=g8twMfck9M2(#nv}!9%Wr0o;-ZPD{r^R+}c(9 zGEhJI=5<>Ax|?sk7nLqKtN=;*40fUl3*fl#7ADzjJQA5rE~iQ5kqUN3m1tWUwp$T+ zA8nK5l`dA`Vonz#CfOwyJLzIbhPd0s=C~O4MmD)vH}P#J{p)kVuPY(=U93WUcS+p# zfVdjBm5Vzi7pWHUW9)Ly6Jgq7J5+oELkW8ZLO_NrJ+bfI4tkZvrk7?jEReDKXWVv? zY4w>*iP4@+DdN`DeC-fc430bRbjlO@J?KcAJ)_}a$nLMTLFz&D-As|2@I#puv&E|N z3O`QUqAbCX$*&u|&pXie4ZKn*z?(-#97Kmc}&U>eSR&Ky0{PrxEVxZ!+z z?q&AkM;}qYiyHr-OhT-)MS9qGqnSx2lSPEwTC6seKjn9-%%-Z$e8j|_va!2uY>thU z+gMv0+hk+2#S52>eQ#rfZLGk?oHo|+I~zN1V^7-{UYxVhZJWex`it4YOAS^5e_&5s zxK4>x^!@RIB8uFlBP*L^C}tYPePNfSBpneA{H7V&7qu+%AMDB1k+OF|V32wn`S?aj zZ>Zzt3y47^Z}(7NyVQ7>ngL(8U9uOniTf1C@J1$mm(TLm_zagKcSHW0FFLkNvXoo6 z#qEq_kq9j>`iM5SJRRZ3B_FPV1$ni=tF-u)n_Lz8K0`KY5DD`h%M(@1cYV66;}5TY zV_mNUqc=YJO!b*n-49K?<8kdspG6&t77ZLWYnE12v}n-q>C;B_SU(hO7%s!DX-BM6YEs%6j)vux><qNwk8PU7`HP?b`LqVifLr4pZ4lq4UY6q6 zeZIy>c#F+uugC5x_e*x4okz$}^>{ts%cgkQa4+lSW$nEz$IIfp%;aVFc-i${*3rva zdRe@eS-ouP&tCSGmwo7Ek9yg{urKi8vtIVNm+kbj4PLg)%Vv7nSTF1EW!D8SvwK;+ zxc#D+z2RlgdD#JR{l>8CuMf7Dh&U}TD-E@$-*Cvw?)S1g#SOE(j8%A<&&%3+S(=x* z=sZ;RqnCZ|W&h)4uXtIFm+kekySAsloZn}=cQ@x7~)l*H^GV26mKFna72s=afhL2-TG!tiA+0U zdR$;^RE#NWaB1g`2lhk$&0o`w{8=cd&rN|f?ul1>|B--hZjdYNoC^M6(gQ~8K?mi=WFR}*}1G` zdV1m^|Lk-orMuFx=bUc$Nv=bF+N7_^?ObM|AYr;+JmY>NXxLFE`J(CmXp|~D1n0*& zJGc9A9k|O~hS@D-a{hQ|Yu79J2_M5L`3fAA2rs2T!7DxvNfyi(SmCrLg{uzqpjliR zi-UK=KSeN1@Z%P}H($Sgc(-|d=WU<5?xvetubJ0r!3gnL&-vYF?@k=B zl%LQ#yXfxW1ufDhY|qQl#=zO$vf$3)32hwd7@(&W=2W^V zvz3*K;=raolv+7sdWh{QS0UfezcY zu`1xgk*OysS;{$)zj3`UPvwePvT=#q9cG|nRtK9ZOq)zdRrK+4z6#5IM20D=6zqP+ zL(UgLy5p~@>EIM8VU(k!qUr8v_EC9ELn8F=qO)w{E^WWIf9E%wHyo1JVl8@)?ga5k_Z z*#7-!zf@TbJJ2e%^2KlqwsYYbQe-WEY}d9Z%8D3)bo{@c zon>bmYI&WuR$I%LaZ{i!uzn~vu_rXK7Q}ehD1Bj{s8XWO0eDnL34F)^IW%VN+Lnq? za~i29Yk1c!+HledJR`76{*&nm$cqHnHq632aZz?%VTqL(gc2AM(VUoj#!j-Bjb>9U zKe~1Qif;XjvQr{lIq9w}t0}O{Jo1&Fy0vXlY_sI$=4MW(FlxoG32%`^u&3{7%$-&TlcNIZct%XdV(!A)s^fqJ@KFYFaN7cL2ijlX_;NL zlHfP~#Ix0-ycUD!Jt~Xx1;-gb>}L&Rp;5dOa-}d*HuWUW*2jgh_XAt`baNT*@f_jE z{W;D|HDhHB)pN+X#HiCquVU|S-fS-W2}NdzR(A%rigr{f=Li@@!acQPxmndD+>Qn% zY7m}m+-`ut+>OfQcgigS(>L?l&Gp#+BYXo}r`N*0p_{LIa06(qVo)>yNGUcgaD@~8eFnV`EEKa^xR zDqFD_0f-<#%B>avb*`a6eq$Je>?4LiJxL=8E~>wYM#11cfn&i@GZr^ZNXm6YhhK#W zG{Kfc%6kPdX-I;20;XNM&W92ys>K3vX(wFmVwaQ$)uZwZp)1<>L{t+z0~S>Q_!_?&2=<^}XKj5xg&GhN z5c8_8JLcBK&6~9yhTLNp!Q+2IA4mtaIHD>R05sQ()fq#n9cz{~h4{0Oun)}_nsW32 zC^k)>de)r;zQ7r!?8}S#IfumrfRa1x=YWzwdI1_{D>ddC+6AO0344~iO ztQ0&a&)9rna|7I|kX682@Q79FaKwZJv*S#u8Ov20=9Ia1EVFZy)r7W=`mGI$MFZ_2 zEf7ed_rREE1YZ2AN3~>t7Bip7u98i`^)}EK6(ij1h}Gl(M9@aYCLx-GNU>PpjzKHO z{HB1tNxT@Hq9#eIh0#s)h@#wxcJfCJM?pS5AC04HbtjbB)H_4&2pV>(L59DLgdRwU zwYbfqnMj7!_)YbkT80n`7$Y7|LVb22E74%8z;wtYHWz)YYbhg&LvQ0xkZo3}(^d5I z*~UsXP|bApTx(82UEUE5ZSqG%KwawTObebR-U`vrZgE(|C`qydeZVLwsJTlc2m09p z+^h*^KI~V2nSN*qnU?e~UH;SNhNFD`W-Xb|muJ+qRQrg&L00lRX|8~$ty0IM;5)$` z&;V3AMhq9I7YGX9dR_Q}cuj-VB=3j?4T+}<+mw$%77^`bLJ!3rjQs*-YR>P~or;72rzc`NsC$imcRmjid0%j!;SRxhAy z#20L?&@M1+NWL!R73s=ozT4&ON+Y!Av5=|zP*+GPbDnd@WU6U+VS_)uE zLmcWND#VjQvcT|EFiP0k5)6uZxls1iy{fS(%W>An{dfiqRBSEaLkyIk2ft+{$Nc-h4qwNx`mxt=6Mp*oF-2 zQDWJkyc9I=c9eM5VbgUmm=;<%oNZ~sG#8nqjP?H0S_uM82u;H*5v&{J-JklC=X$VDVf4fPuTxI+;kz7av(H{DUSbqGofro`Q6($CmLTV;iv~s9D zs%Pkw2G?Sj%!*t&AxydK(uJuxBEsc#pnu2w&VX#!g6gkcQiS{=VqeMCI=M-a3${j5 zJ~4N9VIL4@;{~KE8P?XLU~tkMoAv$+TZDM7$1#M0M<{~XAY)Pc2y0hhfx))cn_xQx z4HKGJw5O+=?Lkd%7(imMS&6XyVf_TAt0yt5FvjvHWMAlGEcCry@*R~S`Dj{^pbc@k z{h@{x& z`3ccLy&zY=`JRipt~?U4f9a`1QSb` zh)s4g(H%_$Z6Lj2oz=~99n~gmZbDz1?$mqHCNcX3OlQNK7PceB5y1xKGn5VP0%BLb>Wzocw!^-ao_Sgncd`l}|zt}{IKTnM_r9q2A`yq2t< zG?$_K%4{$^1bm41KzFw^#us6CI%6Y{c5K4w1#Xfv5oDOfL_^Lv;8p@o$NW~c-la3* z5q|^&=VQo)1~Wz?Oy&qjiNTeI=mAIQGM`pPLImkgm>7l{HN?LzkAy8J>>W3v)~re_ zQc9hU7*Iz!275tuTVgOFNfAWENG=RjL}Y}(GMYy=kY*4$T7+5Hm>FCuhO0?2KvD^& zr9liThzsCa8cah;=n5DMPO&yrNOl=(UpulKJ<_aN?sTdSFVHjl9Ajf~DnX3h?y-2u zHY6iAGBRF`jf(|_KD(dC)l1QJ&fow$Y=J1Vj<7*cow7~z)ESIM8z=@5m&0e8uE5FW z3fw`s2WOnY0Ald#PUtgFNIjDIaMAEt>H+Wo%F@6gfci9b@Vx2D1I-|oMLkeALOsA| zDIer&FnI5kuSE?Rd>{Tib=owx2aml|rv^6T`2qcLZt7I6 za@sU)(zIzx1fD1Ak8@2|hybkpnle>TA*ESi4Qj7jt}v9jrBa?{I7 zYR#@|HklZobUD<$|F|9>3&1JYBdJ$elF zn@*yt`ZsEvG%}Q&MUe<6b2ASzVNwz8oXheN zpjgC8*>wo!?#cSH{uFRX{8W042l3Yb-=7mdg||WPsomUbJcwVzuMNKTT;p20|Cg>O z-VNRx{`^ay6MYC4wexGw#QStD_#RA&zw_b`FY51@Iu&eBfBL6Wr#6iFKU~H3ghM(# zb*lVG^9Iq()T^4*@d_bz&1>radDU&#z*N5#@X*LL{?lj1EEBV(*<1^oYQeb{{*ibc zJ|8Q^?9>10^HEGnV^->)hXXKXsNS$KPxbFHTWQi0a6B-%=`AkDMG_>j1R4LXX`ghx zG*B9X{6fDpUYaP~AWfHMNpqzI(h_O8v|3s(ZIJGiHcQ*3oznf%gVH0?0i>%Qh;HXfLU1ti^8r-JVT)jmd#qS z)~p@&SvsPeMt9bmUC#!xAy86SFPA``UiCnlFJdRI^+5H7sB1jf6&HJOO>Wr3e8u-F zzAFv;OvUSTJj^ATNaZXlapO5N>~ZD&|MmN@wh0e*<$I&;Af7?^W&IaZH94u!xJ-E zx0&zGWC!#c#m8Z=n!gVL;&A{u6Zg>{|C+#t8o0nP0Kmkk#MBh{j0ODo|KMeNH0%eS%*E?h=9Z8$B`Z+|{ zg@r}7Oup2Rt_*KT$9|^?`;F(6GZOjTQ+y5tU73Tt31v1bB9HtZfVDz(KS z_67U7R!gG^b)Hb77@=rTy#N6;=7x5>b*ohd9y+uE+I<92D`%6mJ$nD~AGOY@qCj*l z^~i*o&~6@E(LDw@+9AmIq4{tWDAp=3iu0c;-;8l${yM-q0dZkYW<6rRNuqob=e_Nsm`eIxuPC@!3i!eHH*70j zj3Ho)@Q*#)zNllM7hdfwF7CjSjEgiGFFvQ+-tAD_ArCKzo=iF{ZBR1hI>d!z-YPa{ zq#4JtVXNK9eZg#_$gvnHGeauDnDQAnU7V+B$7(fg_650a{s%LTX&RGjZ+sU=y$~^5;zY2W_&@M!bVA?Zig47>|C`x*K3QG>dd5pFd@+$yj9D@#CPevcciVWXjoCb? zpL-UuGU1dEF^EPnM^9WR#~u+4eDb{$d(G*!^@gc^`wf`Vr_Yp0{RUR>KCk!G z&HegL?K5RcpT0Lt2klk%3}np`$R;~7VY5)3vJ-NmyE!_=r?$^W;cLl_FqY2OwMfcn zlTp%cT07=%$NIKo@$Fc2Xb5P#7-SE}@1!x>Tkn5x?@m_u{zcX{Ft$xvTI<$nX>Hg$t*fJnCq4V@BwnUL53nhLIyOVw{_{mv$ad|#_`dcQW5sM_TASd_;#oVn zVMFGL6QG&G-UiKf3Yt;AWoxO**D=cx-6F*s?FG4{HU;Ui&O~R+*3()ue{0siHS2-` zGx<4G8Lk4^If`X)PDKuP<~VauCAr{DqSJWL>8&O@1`oXho+L7K9k6-pfabJVs#7g^M;q;<6DKn1BoEH|G~R5ARIWoLeW|p< z*R#ZBH`{n#UR->eoQ&km%y^sJu|s|y&%;ro=^5?f4T6B}HBG zeCOewT+?i*;7z)+7MZ8PN@&z);ttnYa1M+Kya3Zs&=O?_(w}$*iWJ2nfee*+MTrCx zo#KM39WY8!_%RpNACY9o_cx1#>&aBDA!#y<+iSEeSGfxuFETNQ9_=}a`9r+ z^);=JS1s?lssa!OMs#_ty84xg6K}764J-mWeSi?N9A!0TRW2&N7G=5Y(UFm{QnE); zB)i5cpjr!a6SEj6tmjeFC)X=b0Bx#NA=Ey{2PS;5)-g7IP&VGhkjMu|Rs z1SNuW02Pd)T^@>@G8@jU0~%KN>Kz*OT)W4jXy*42D+wK?L*64&yp5 z8#?N`E*;179`*rC`>%LphvQ{BYy7k?5W=CmDOP+)f?l;F9^1zturi8GC)21YnvPLABlD1;%1z;^EWH%Q3(~)Jxm5~9m=<4o@11@4no;*pmIv=! zXKDD-bj!g}W1jG@@lW2kPB~Zk_{J6M`c|$w`M{$Stl8NU4{nY;xoVuhdgvku9!95? zsS4u0i=_^}WbQ;oy%eugDQTaTZz+gLQ|zgB7HyBV!`}q_bjG20^(HLawQb2tyUHb^ z8jjE%rG*|b3ZOp7ZEjD)AUKH(u-p{cqx5O0wcNI-qJ(!m>>s$g&zA9Bt8$|356zvw zxA$;YT<-K<*WWk4!@`O#YP(r0=Vd0gU%Y)l!QzDjRt&s(4=d{4B0FcvJ$q-4oYX?L z3>q+h{v+*%PG7NeaY-!oX9`aG9iuG497*yyDCf>B$0tO%Y((CoU@isX{oBBA7A#wg z8Zvs2pgEH9NpWMJ9@D@5#v4X$%E`EE^u$f0XWy~fGqU!ZWmT(t%-k?<@uCIQlSaO{ z;mEsSUMBTu{Hy#Y(9ePRcZba+OA-z{MiCP+obfnyNso^v-+P?oF-hheo|D^N;^s1D zUD``~YgqQP+eXZ1vhwzH7hi`i>o!4r-eh_+Z1$9Xs7JxOHoOB5*g)34FkM z4VpW=eZi2~(d?z|+QJ39OIi z`Vpgy0nk+|=@L>Wc#tJR#k;6lmbs)S(fQI?b5yB{FXXrWP^r~a{sBLgYydrL z?`t*e1wZVFE_=1ztgHV4Kl>N`>#KdbmyOfjz-!I!U&a2lSF;n3pr)u&psq$N>>^*^ zh$xhmaZ6Os9s6)mF|r4>cg(1_5{*S|B}(4K!yVIO%E-()^SD3#uqntH>gCJ(JVX@eYtg%EeDBAYMbzG;`q;4(3UdRNJyg<>qh13e3F*QLkqr#eC>MJ;I5 zgXVu=pReDxo;7L=PulVO1b-)?0|zlCZmH5&iaJPc199p^Yz$#uE-)`|hPi<_5Vqpz zX6y^07=6)e&5;z!T^|QvC9LiyB;nK*H7jqw2|qRTk2`=B!iqn^Q{YF!t?{3rs}FQd zl=}LzBjV#}R=X4AAdQu3EH_atA1R^Kst^FWouQ+U3!Q^9 zVnxUcje?3o(yWl}pxOtSxruA|%;7mFlTd`zv}R0YdJ=z(KdZDYn0AZIdjuA$1#Rce zQ?5~DxB*TQM?xN+Pfb2ws65Dp$2>MG%EU(`Uu>eM4~M>I!a1dbvO z`mln1Y2Ur6&bMG!LeY&8EL37+6uo9twyPyJ}${pGk?fQb^&V_xtuHMM+{g%nc9%Og_=dt^>>)6?QAK}*r`VAT2+7q>{+7$Wj zi}$~m6nIenX4&e%f9^!od$~4Rxfy)f66=@t(BNBr<)xWz+IVB5i;EZzqKt`2_M)n# zj5T08*(-PHT$GoaSMD!v+o|mkzd19vQzutTAIptPOB~`)i|dH$)aCwIx2qz86+~=` z;0R7ZQa2UfhKa84-BOn^(=g>w9Z(+{(Q#u{L)-*z7DbT6t9ES@Q8tCnigV{smPu}O zdLil<%P3;pM3-C?|H@~wyDRU)%xa9s9f^fVuDP`Q#bY1;WLedA;F6o{&$nUgs)p3w z_4d5H>k`s?^{pCfGW9;T)?V}d#2q8HX}9?vTXrLlh#WXp8_k~R@wfdoCYBmEbW9Y> zif8v~ttK4T+LRA5&v8wkbIVliMxMwjb`J5&UrsGxi<~zx-}Nn?lrGFal-|!xGt}%FKphlPo$Z*_#;eU>*e}29e$s8)pIa>}H?Esd7~HMgavPQo`9c zgX}>CpGm`#@+0bmgc3rD7|A5;1{JxH1sbAAuFTw-x3Y}o>q-{Ca`>)5Ls7S^UjFgZ z+Mi{8Z@*m|%{TG$s3Oa@)X!-Y3c#+vlH?l%YuVQC}_^% zbmCwPSCmCYtw#%k37|$h@p*-x>EgikntobFFbhRTmHv~@3!VTG)+Ij8RO)sHOeatB zx|189LD>Gy>T0U(Sf3jB1GRsbHcgof`X%9Xm#n1t*w{FS*=)1MS(8&dD9#}zCCN62 z!#u?Ah?S#Iaw1r*26;=bQAR8kQ*A0C2690=>81}_r%(pr6ca}uP*Q`Lb?LNk`O}Xr z*mqlA<%pF#YicmNR?nFH)LVhWfiKt&{^Y#b#ZPY!tWn?8R&-ed6U(4ccR{0}z7~?% z84+Q%+9I7!*`z28p}QcI6k(1qMcPquOEH;Ph2PYSP9(OQqbW#(c@HNg#f2d{h*T;C z>mmF+b>lN#180weW{-U)CkNIB{s6@G-x2s6r0dF03!HA(rpW=&%_$9mq_o)_R-9vs z@Iwc54PtMjTvmoPwmAYdWstHRYj(m@OG*WySvu*$a3W;zCp0~cU+2|ADJ zPNV(@A1DiJpU|Gwwy=4u-^+XNNez7V zw}m(V=zKcD0^M<*ZcL=+8l~~g{Dyb6MK66D z(ZSW}uDqqw9;uws^17_NLXXlZ;A`<}Ni93SVfpT=&bgg>iS}WySe^lGvZPX9N{l-t z(&JXLc}}7nnc|UACnp-mN=hnd10B?@kjMgDJRK?U1y5qo3vng|34E4`3IMq#v#jIQ zJ{o19QHZoXl_)hovuD!W9@iIkj?rp9|IyXj8eQ_!=i0>A1Fdts-F`auuMTO}j_wng zcd7SmojJK<@A|d!%92+LXTQ*}8uiUq?>~GKYP(Er*m_}m=8rx3&ecl^_eNxeoBjgY zWnitCig_bvCT8K3j!bXrFn^NQ73Gj+bEM1SG{*{?Oh+8546_!$K%WF2m4`AgMtvwZ zJSk94R6$j*%w|^ZdHYbYx!~}5?d`+ItQnc-PrcA4KIT?W&vC>1EiIXm(k;JL`zU_m z>&!O;{XpZ~^e66?V*;tK+}?vVj-J-0&xoql?YsJbFNYURl7Ciug9|f}v7Mh_O)@)E zQp{H5TA|XSs>Vrjc9tBS6b+3X2iI-9(~*w44K7X$pmSt_u8P5z#+qCVf6?Z$Usn^DxKWzL!#$IYF5(+D>zD|h-Sa3N!QMsjii z75U+rS*v1P^U9{ISv=Xls`zrd+e%7m`RPwukGO7NMt&~3LUI*+I}!52Eam#hMn+X3 zt3^mpn!qF^$XIXEF%S-hW>F^UDkZ2-ba;1xHZ36!1Uwv_h-qNOiD*Oo3Ssq^N}QC0pgR>RTTV-Lqsl&N zAzEBXPVOv65F7M3M429c8>Fn5V$79}8IRMCP|ONC8wZWRcYsQ1;P-f_D&>Q~aqT1R zua{npy=B#=-MjWYcJhT$<##`L4>pI6u_PA%SC*Gno%q4=x8Ah!?x+4o`{-Y%-qHRU zc;5Uz;n-c;$Y;p6!Ep43%n&g_!r+l z^nKt*{6pMtKe<+_fnFhah=TKhbgQrfm2kEr9u&-sB{*26cGrky%FSE1zQE%GsISEv zkI4UKw;A`v95b7l3CCZ`xVOFsc%4A3fy!)fHM_9^M zmZFZ=o?s(9Tn^DCcrWiXUP&( zhE4DA!ABRAKk@qdi#rUzKsYS$1x_18lBWgLgD^1IhKA#aycPsa=zf9*$pxn{yW@in z(~B?2kMerfil6~6_6WPf7@IU-Lx5`@FFy~=df0dTC6>nQe9S3z6T zXcdx2!3frfN@pFuTmIB?zC#_1Li}tpjqzUm>wuS5a3shZVk|MeS~5g5i8vG1AutWM ze#a)`PLeN4+9jgM+{CwhX51&2H@^?r+`o9Ab_3fj?vtft z$kK&qFBY0688OGLOsMMUj$n+(B_zZr^W>D&B&Roi80x*+Apc+}!yOEfs5%qK))2W+ zB0L+~p45)Px9BMSlDSE?9$CI{vNRP}NXBM`pe3$i(`$C@SaWOt;?|kHyS{U4O-+q_ ztl@h3*xIGr);Km;`iz^nmQ6o+uw zx@%3eJxrN^-9SL3+Jiv4i?fK9dhJVCznXjQsd;G3pn+pemp31L>aKy~EBgu9o#iU! z4VMl(7fU!zwDBEMyE5^8%f?Ge543GkQI;3)dtk7>5$|=}u-->rJbe$&!uOc;s6;^T z!7F%Q4!XW9-orJA_k9}Qkv}x8#`mZ)iuVYuP|k?&BgA{Ccljal-YedB!S|QNdw6)z z#W9>Kxym$Xm{yP`GU8Aq9O8`g$B^gH9l7ENut(UzE2K~L0ntxKFtWkYmE49RcH47o zBj+dBLCT#64n>VWsVhx=Kwo$cqs+<=Dl8?*ag3!nQ?i_{?0g*2dewCphaaf7#{GMP z_q%d&KY9%~KgusQK7)PmL|&1{^7!sNwl0q?%VWdwE(~@QZQYhgQR7Rl#09Vf48jQ! zc@li;ZdQ?kO_1FwY+VXlmcoYPT@wI(OnNlNi!sW~w)#*C+4jQo4;KU5(GVn*82)VVmhU?cL zT5tft8l1%nvKY!V>#%}+lS6zXU{MW#=CFdilta8E@LDad;Z5x|p1}C)a|4}42-J^| zCIkhZcEX7I-S`G3_!8w~^bZb8x+bw%OM*5J!(MwsxrA#hMq9N8K04AEo5mBoPt!ak zd?cH6P)hVgu{RX9NntA$R)8oay=i?=1?*gHrQqot708LcqGtk2)YCQS7Mv$JC>5k% z^LFEN;u`pYjBA2(Cx_;afOC)ziSgMOPhbg5!9VJ<=r-{sLP?YlMV}&#KK%#RdwdbB zZ3dGvSO%-YKm8UW72Q)NXhpo>((l3eHKUf`(Qgo&M%Q2=E5?P^)V3N&`?txWqLfolN>z_Oy53(v$nP5oPj zHp;|39s`fzsG$*gjPFfmZIf9tt`vM5lqoqXWJ)voH}S2I0RomT1Adf`WA6U8`4*H> zIVxn7pa&^J;zEpwK@Z3|Hbuw?@F~d-h90BeVosUzK}*ajeEx6JvG`o@gZLc$K%X=0 zuLbERKG*5@Zw`qJxrpnPZ%lLsU_YOS+$S~^rc+py%jH%Wi^dMC64l+xI)MSKtRrqv z@Sn5xSSJ;AqXT?NDH$5pSku9(Ah%KxYwyO%kJ$ESncwa?F_OJ}PmlfI12jiDMoO$q`DOx7s)9{sYQc_HXu)A!Bt*b;SOM;i7U;{2Iy+~2M){aeB zznHCH5nlD!!Zj}*#ci~vR!+)Z<31L=PcBMRP*XDto?9JGX1}mb8yfEV`i7ds=$h z6mD7$?g#yqSFj2`4?m6Lh`U~9pdBr=+krF1D9e)9sXxAa)J8PY=OXWtrgDmL6wQm~mE z8yh|M25(;F%n{?q_Zir)yvOKnon*JWQ|F@5<$Y%6-C)bNNzu`ZGZNag$#>@F=11n_ zWG3e4FKUq%gHLXnHmz^x;lsQ1Wr>cgaU<-RecDc%G`@6xT&uo`StBxw=eKG#zc_P5 zR$^aEz7yNO@h*#!l$+|#$08Zl)N53~T9{Q$n|Q*6Av=p+=)d9#HYa}ZF|o>3-|8Nj zu*-w@h5;gT9UEgfl&b_s`GC3T83m?cfWnN22_N>kie*o1QFcbKc&j*E4USlxsfM%H ziqa?;BZW3N=v|@5c*dGJ7|;|Rv1O5}F0r10Q#b{nC+}8ORlkN_DJ!%WE>->9^HP=m zn)R&PS5+ma(06og)g@ILUlBXgk=M4c)r3D*X`6O$dipMwGH}r3DfjzFrlmbqaKjD7 zT~28}?c(+=TkhGmWv+i*<)|^^#`~2Om#V6&8de|;<-@AFZdI3*1R#JH>_gVGVFiu{ z>3QjL#iHo`y?R!#KaUB#zUG!6Zd@YgztU?;lzk@4?9oxvKJVR6`+)V-#+<$Q?wK?1 z9@x3-i6>7*IuIOz@%H0B-R_g8mgQW zZ<0_Q&tkV(%v=2Ba;41o%1>kO6_|H>GrW$kFkor7o)GN~L??`XS@n!sP*64IF~D4- zJjeQ*(g3s6mtjJEZL7s*x0<&2E9BWSe@Z?HnB`XHw05(u#9_}M#N4yZ!Mkaz$P|m6 z&ou`0Yy&#sJVUMH(WdV)k8^w;rkKnR@Td4mY#Fag#Z2yoh*rqy0xSgSC5F@VCxrX8NTCS&F>uUX(GY$GT#{7b5$Zl?HJU*Mfb z)VkyN0Z)DfIJIUI_DxZFEG9NK9$^UHn1~28Ic-M5P=gVA|#&_ zy$OdZp)#pp*RxcWP8{kR(r{=hqYcD4O$9hxQ}8G)!dBondBX?wO)YJ29cynpq+6@u zgL<_?ovGOHnJmTD9`X{ z^>dd=>M87s|MtF2d4_#2u7|?K{lEGiYg3PUrtuHvd~9gIw+0`$h{1#pkX)B=5+e|e zBIz7O!&G1f6*?!#!d*Na^1%zOMDD{kX`f6CB%6D^E zTH>z*V>lAyjAs2!umY2Q6&BV5Ux9|T>I+EqMz+E=-hYhm7pZ^2_qznmB)q>zeFyKW zsb7sw?M&m#jSDb8EYEj7Eg9rZ`jsN=?2FCyg?O_FkdbY-x1mFKx{Qq13 z{>%3bjoO*Ok;Vlsvo$*KJ+X9SW5Y&#*3J0r7X7pOueCD`59psYd}LgA4%e+Uu6xP2 z?onJf#<&hj@LDtv%+k(i>H0O=1pOLq9q8Oa{|wX?_i2B`XDjfT$r}9};q^1xJEUJj zPHBQ2SPEEe3|KRb&!*tB1mm+&`ezM$aPLs#UaW7#y_xvzf^i*b0&1fZ@c-7hE?@ub z|277Y-QXnFS;+wpQGO3hSd}~Bc+lphU?!TwmdG;y7kts4RAtk*GaRHy=3;qCOZE@fM z4i2UR4@iTNz@A$%cV59f9NCL4NBEq92NYu_6617iy8c#NNn?Gf7TN5?!QryqH4`gtvw5Awp!fLG(s}7qP$Gnm#6Fdh4 z*pc1jMaIBvzLJwtRJbobr3``&Ts0Tbk1$N<3L^q=O<7f78L0+K!y;%oan-5CfZ&J)|F2`rzKZV8~;_-puty zn;F)+682W$e@D4gnW9i|B6vp>XK30ELEMR0c%W>>v1;vM?JT>2x>~Dv8xAvKU$=~H!)mm@%-2VfZw`yOr*(|M^Js^*%4vgWChQYK*nLa3YNv6OC zgq6VZx~k{2{T$ZqmD)c9IOXT+hF0=rGyGzqEw z#ntj-Z1zO$I0L|NTxkzCnkvpfv7$a6YMfyAUi4ld+Z-;y3aEjU3cj|Q_v3F)2<+zb z!r@s79`TVr<{XR^-uY(mP*Ap%xph3z{bB6LN7di(N7(wA+BB^|o=$N4glGz@SRW(M zQ!$%^V?-h^xMm}ghQWMHn=^(z`B}C6AX_>`n-RDVIbsG(K1bE}!$sf4e)wPd#n(iWQLYF6DFo_F(1ttv{HoJ*k_h^+1*|z^wvq(0X z|FKV?&(HD^F=k~3t|lqX7m4L0x0ta3jGf@nOf^_bOw=Hs#xKH%sa<;2?^LD&jEG@i@sa#;nHVL4m-T-c`{P=dTlBGyZ}?d87dCt> zX98w+Xk6O*;({_cH1gqu)W^Qrq(bpzh!iL`Q1i4_yn>CU$^SrLz0#tj;Y&W{uug}o z+MxOR``XY)?1fpHASgiCH&hjN1+GE zl?x3m)gf&gTH?3}0n-zrVV=(&4}VmC){akPv#Diicg^HC41sKJV?-gw zs)%^u^aOTawQ^efcsg4GFu-qb2)~dFP@r942ZmT7s9gj-&E&%;c5l??u;J{wYUP#B zsu}k6!eCh`e=De;!3nAvzo4XmXsl;wk!8fP3%Fsn>Ta0jr2qMJwiGDy;mu)($(3tE z{w273w0Dp)>#AlY{K@LTKV+!F@cu*Q>+)GvO!!5dTr+%isDvw_YFL4Xv{%@kAFAcc z)q!p#0mWWj(|FU|Qe=cXn@~YMoTjwqH8{!O(x6%WtTwF@F#r>*mBoQ~*@Kg`5BW%f z+cXE-`x1m)1+qa6Xc6$65jdo2o6QBZ5|mXpRHBX+X4})+v+TY~?N#V?7upHhuU2Hz zhwNe<|8V$Mj9!?PHAQ=75<(!x!^&a{$7!{&vcmC4rE%SU*X#Ht$4x>a+WB7TE*iW9shy^nd1`H9i8S$`KF^j!d)hgU1f4PVF; z`vi8%2)|%b$B?`=_<%UfX4fsD-@^w%T|0CkV(4gr5ST0X?HhQIZ#qitP^^b;7Z+m> zitNs?cCKQ#|H5sjwLy#6c(S=})*hBecL|JO9XKrHX6@wpVszQ7_lIW8l^hqw%V8Y1 zPn)#}i(6YY8!5+k2^7lv!r)rS{?_?}0x*M?S9AFoW_#6W;})@9mmnnMPF)&4c_V0h zfga6mFXCTpb~I<-Yj7`d$EZcDu$r}&dvpmrA(MBo$(FIe{~sNc6DAmQ$jvm&FW@M$ z1VVaPSjs_DV>v4h+F#bT4ReF`S8#1dGFaH+`nYkN2}c2~&0z;+P)HkxEb-&opyjM{ z&<3-%YZ!Z#4Q9Y6&L(~TTj2-gPq-asZ{3i8l^u2kd>wr_JIFK@XPZ%2!xg%|^xC$V zt#$n?hAkGfA%*P|w8d!0O}EAb;E-evv&Y(MFz$ALTCD=s5W^k=t($bPvBm=^L#@;B z8T=r$28l9ahJh=wzyX+srSf^O0(c=P%YL^#_L+8IHd{pYSm5RUd}%l=t7O!G+f_yA&GSQE#jI!o@05?s@ zD%-97pttaqyg-|P283A_Msu2f(EH^3`X#H3l7eUi!@%y*zMahukySQSn?X=@vkY*j zh4TdFwPJxs?Z4V(-_2&Lsu8cyc_58qc>jg1|C{VP*kpm3gZSlY==&ecsHr zMT#1Jn+ez}XHzd z+{=@(Y-d?)QMLA}yfYlOF^9>w@>^_o!vx;1onaf833h6=rYK`T2N>G_JKJ5=UE(ik zQ!7v)b8fYg6^LRRCus6SK?Ce18n)E`Bb#l`1cXgatCm*!mE^skwsy~_$Y(W$9F|I0PP4(;wqsNS0oi;dklPJ0O!8{r?M zzv18D`kEGH5osY7)XYI?EF4=T^KvWr(rW(Fguou1F|l`TJiJS(V1$2Z=f z{Yl;kygG#VBFt8UHHAfT$R43xr;yzMpGlLg_D8MTY~-bnua;kK=)y1d*W#64I-aGW z@e1luVPos_4z^kdIA9ynAe0}fkY`qC7kU$6!F2#T#DR=i0 z0xAjQ29PSf!zCTkNTCV@8(8_MvOK7D}9E2q1_YY{_<$}SE1K)`Xqu*=WR=kqd}pW(|RovC;JPH#`$+3;8$C8mXw=9YnIAOl-7;)7UR13f@D}&0fo@kW>Wx` zJGrMJ7l;|6q^zbXIPfT!hQ3z$%(aGb6@eAVQ!y6LGZkZOXZcnQ<WmLo}2%*dY#DIhJJ4y&i#vin?~Qeg@ADRlBI0asV5=eXSVb0MtV2k=)cIn&Z83i z+sHG7&j$XX-!NOt_1XSo8_tl?TOI(98NCF^*oC3f8$~|B^AK&F!N*`VI9~4D_CNFI z#YmeNZ3v}?3MOMvD{QGet0?riaNCe4;MIW7e4(}Y-t~8V1CGz&)JyQ$S2vE^@GTbK z<2gPqk& zG>lCB8oG9&{2T|JYk&KFem8AIXuS;Ql)nQF{0Z|VjgPSYIrm1G-^k7RJoLmO!CkpX z{`U6|!2a}hv@|m2+1L4+{}S)UNW3QW;6hCH?7#FY7In%>)`p(i#m?b+H_Rt!7*pe4 z{?D9IQ2*usEEIkv^uhx91D+w6Q;?!v-0-~`HMXJbdE00j2ntd^oxtsR)whd4H}_lT zVtKXT#S2a3YjS-XOJy(SyZu?&*ZXDLLL(MoPeb7DJ3CIrq+vu0jz72ck8?#(Mwlyt z8bA!G^c$5=gHLucls8v?Q62%G>{J_{n(q2!$VBP~_%a&#O1zti;q^0EyVF$9s$GrB z|6u64`SKg+2`Dqs6IkF=`(rNEXz1M_gXZeEb?fgWG+5BM7JL%AXQBLwY%hIJnSN@M zU(GnZ7@cL2HyHXMA4iO%x!=6`Cfaby8hzcMskZg=3>vBbO6b)~?GfPpTo>4mLz?We`Zs5xKP_dlZ|oDnIaZmx#gWB(;|ca^+U9t#ibbY6dD zmzeL?@O|h%u47w5%V)srAGB2V%H2-CFi<%ddXC)>z3XI%Z(DV99^jFmQTm8BgxszB zwsmINyKI$qr}qv~4xZkD5Vs;fkk9{4*}Fd1E0yx=OF7H_$^rgK9eUP{jhoW{)E?CP z;a`M?kC*=;k6Vgv@7+`DlUfnX?``xr3ai7fez1FI6f|-!)m;pj-q!F`bs` zYTejfeY=Io+I9T?rdDkTy*f>vDHp@j3at#vl{9u&Xa5=fa`$s4W)us?LeqLg{6p*i z&P0&|a3i(_L0FWB#S|UrPkw)V=j83l;%~gRcugMu@o7yw-jlXd ztY6@3O*&=7DVllEJc)%AM!6;r!}5sZ8bdHEu|@*R46s0xuhbALBe>x;d{qSE{a|5% zyDZ{qzK)?W&vPDmf`BFRl_)nU=bYXp zR-$xjye^^zJf6N8-I}$Ei&C*wDFV(YK?Dg8-?X9zy~G+3 zi)<2KzJv8FSXRvZSid8Cmn=!aYTK9eORoxj5+k>56Z#7E;YIB;d7xO=(nMx?9a%3IPTAgR#OdH*L-m}LGg3M~iGmJM4T^|g6|DbXKugFGM zzO*N_WD$SQcr0Z3X~doilYrl0vSq;=FvGFSw-koRmUx4#SOu|Td4Rn76}VWOlcnw%i7IQGlAJO$g{@AJqf%;8?n`+q<#>vgqI8bNZ%SvWb0@tS53A$%OI`h_ zAT#|u_RU}MeV`HFUdg?N_F}7h$x*#(dfnIStzO4_X}$hUzR&zG@m=v>=DS{rul=+> ze^WBNDoYOZ4h#*f4yZMO;{he0bm}B>wu{uo*O0T(JZq7~e^O?K$kS*;5xg-L?^RfG zTJN->X{*!JnzZ9-N}AfEhv3;u>ect3<0(jtxk}m5^56eOL!~cc!HK7dpyyJ2#Fdlg*Q>Py6P%2Rn*to z?cm$-4}2e}ed<5t`v&;Rf8hH}?M?q7U#_qH2fiz6U;hvJ^85{}=iAA+zxEz^GA}df z%i2$Q-IA5$_}=kD<5$P4HSx#em3Y>wRdk}vlmsau@vN+M>#HcU*hV}b_%HEntmiZT zC7zA-T=Adtl&+}#hJ}Rfv#EXELhbAN`jYCB++}DNwz`WP)upD(eO=z_a=eSyMQ+|) z)Yv#FuBG1O>>4ZDfwOp{9r#?7)in}i@V7x$+9oC^4ozgM6XmGHn#B7O-%325s3o$_ zXuR<@oGYE%<)7sJj0AmA5B@iJuaJ)aukpTL+TzwMOo>^B)7lt=C;9KoVpiaey||+`#uKB+o+%!L zMU3)zJt>|HkH!-jo?)J;o^sD7&pyv_kA*g#Z~chPA?h1>I3+VHD&U>K_o|pOi64*< zIQqA?>7FJ#{_j~9DMg0YwXuI;8DdxPHr(@vGD8dAsLbxqxJ)f;zo`AP_N&{gy>VFH zo<+9r-F_&pEyp!|Q+xS1Lcp=MvB|Mzv5R7t$F7du7<(+%>>axdCoB@zdyDH@EV6tG zK7L0;MR+4pA~5#P6B!Z1BBn-^M{J7N7jZnoir{5~;Mr}U{~1NZUB||S)KEn5b^g{C zw~YQjumt|F+lKJ>s164{lpcxdBu$q-s(n3L_k?52>)DK()UF+Sb)3<0en&N|W2=t* zWJ1SlI^NaM=!I){b!6K*$|n)T5WP&iM{&tgnmf^HE z&frP@`!e)MSI6ze9kp?uIE5kTq?8bq;7v$L$Vkw5A|qi~!qkNFgiQ(i;4N7R?rQ`< zZ(~q{=$6pgZ_)o~m*Ht5okR=q|D9!x@U_0o+_FOJw_a)LzDazxhIDJje6=iTQPQ%c z)k$h^9F`}s$fVv$Lvd|6uIZbS% zzgNp!@AXQU(7%cF#s6vj`P}L#-j4AZ^U=B)^7cY(^Oj1JnXOeb{)SgFdx4dypo5nP2hB#^+JG5>xWn62uWwTf&VJ`}) z83qBxmOR)MfrpsqLu;bj%;QQSTAhfv8_h!-YuiF&F1o0)bjPnsq8qu z2ToUo-VW_T_LHt&jHR{Y`&YDxjg=yzqr;m@O%bWOReWqj)3|1c;e~Azuo;u9Dj%wF zRpoPBNL#VvXD2`O<-6$NOFNXcv5QW$t$7#B8=mQO?v@&R>VT#_u}AqW{HZH%y)NSR zu-2I)Ca5n2A4M#s#lK&^b}XC9^}!w&JnzmyjINI19a>>C7VM+fqIrkrof2EMZQi_X z+o&ny+NyCFlbJFOqsZ858KFh^b`B!+J)gVij!p~BxPI7_u6;)y&kDi0nnq*OHlCYp z9UVa=VOx?Q*2>H9vX*0ndc&&SnQ#3$*eb1qVUCskH$c0 zz6n@Yly$=T7BiUw!!! z#C{qpEs!3?_=zrc&A?uQN=>r5Tvb-{*s;h>gsoACa32JJ@z~QYHAU>0W=FSOw`tJ= zW!#)==bnzoxaw|cf!qstfTm+r@@deC!Zam;XX(XwPG1bj4dJA%ENwp%>8U zDyQ+#*A9<3Mur%%&E=FkZ@lcmh?|;T-+%aPy(8=~gRi|J{90IZ`AyHPlLr>eE14W$ zp3!UijO5h3X^|63%O@{sht0%MN8Uj`XQIqJD4tC0-=#*XhsK#&m>$WEiHuZ|_2s&< z8u5#V{Y!?C*LOkgKaDl;qdKERiS+Oy2UfU>G(pExKncL z<;zFPCqi9j$}9YP@q){tjmXDTHUT5bxf!0gR?V8VYK5F^#Rq47Pypk%ET+YwaoiVP z5yK+k4s%yoZNg$icIs0xpsoK!b|O9=axjkPB(Ds-vhmFHc1jcR+PofG8Ss-J7! zVF}N}PoyH0?{vIn?WKM%#>5{S7cEAe+rxku=xIe99EUv>-B-_hP`r9W8Nykf@$&Jl zXk2yE&wLNvtzcq>hK-|t%IDsF*{%0Hdh^<}C9Nk9nb2Zt|5-QOkbQCA{sXcDL*%mA z%T~Rx?!!A;4w}E9eQM48fqgH^3Je*7cLwj)CD z4Ef={8!eF^-K0Q9=jK+s#CC}X$F<{is$DxoQfhs090FdSi#+J+=EonJqWYpkp47hu zd@D4665eD4+Y_6yU?*ROcR}p^gTXRMb6vf6rbSX^S)X zd#;3bqHMdMZ22zN*%@70H$OP8nbkVOitLY_xGXE`;J7AD+FHp7$q5vYJ!o6o!!%K{ z74%kPth=scn~I{X(;geV)>TEmbA4i(8`fT!5!!k8Wj8Os{lUDYt(Qa&=~`F3g9f_A ztK`mJ_WH{8AB-5>^4v~ycmXdMo-rspaM4B3`DN`TB^u-W8g|2Kl@VTpW-@~@E2749 z!~gpD-X)Vzn&L}K3ksu&E!28^x_@dFLTT$$cB~ z#0QiNMfoF725ty$TF-OK)cv0-3yG&b*Yj+6XgM(z`3qg;)!-Y25q^A`RKodJjf~?F zazsQ-%bI4*MZ97tJSlzyzH&fk(ek1A5o?iuX7M55qKE!G=fSb5-23*2`P3Xw6!C zTS5gyxx_~v{;`K|&hh8pts)TwI1S-aJN(tBt2b`DYx9~-cj-lEe!VL18-Cp~1kP4M z>!4G*$m3>GRz~OL(h4AE6MYy%L|HT!b=~|Lbn|t$r_T^4-GjDrWo%r3>|B|=0fEgb zkXsc^(|G}lP;UMfsAdfjG&}6yed)Nx4>z3=J$>ZJ!sr>zd8oM?Jzrc=m6g_`@0`Vd zqKCFZQ#9zIOjYnN%4=mp-RjkU?rH(C3v;}*+6v^Ubjf*v28rH7~#&Cbwc;-$Oy zd(Y5Azs+-Fc<7pXMMV9m%abNRh_M-oQLSU+rBvy7A<3V2?TUdAU(@srU9o#iQ+$X-rDA7j#v7En z5S(3QzfV3x%N(j?7Y*nzP$M@&s~cJ_|B8rFZLV6oCrmL0M(FKBgf^aKQz%jlS$(JP)um<#JNNPB7eq!A-> zb9Oar-t7F$bT1BV+hp`=i|poY2jY1~>xrW6h~O>cjUD646&1RT?IO^U9aM)U>+w8?Uvgj(y>K9+O#p?ul#exa0aq zT@Q~5!bB|qj#~s%`E)Ss4 z(a2B17j0Y^-e;6ytGI|}@$pt;-Wz=NuVfwjMlYmWo{dcyKB-Zt-Fj0#OGCunYY=g_ zb16%xldB(t+zyzr65BQ^ju&AZTK@2Gs|I*omuqfIkHQmW*EsY3^n-q5O{~8tzUqYM)?B#@aUu?xB(2jqAkCktJe`7%JzUiE8?NH`tJ|kfU(DhtI3Q_~8uNqekqr5tXQZ|GI?W)hFdABt&xk!|yu(U5x#lvCjvd zI4&<{e2<;8uOE?bXI*i981}Ego()YC%@gSC8R}8xcERWD>)$C?&~@zJ(&+wg6-=fC zpSpcP>h{#>gXypT<9;rZ9!d-?l8b7;LpirVNheFa5YfZVi0fu|N>A_I%0joKUAx{F zcItFtWaNd6U5NJ;aZmaGQG8=IKIj2ZNKZ-C8aZicjnYP=^f`^vv5nFOy85BSByanq zr1svVLv@LYZr%>vx^?h&Q+#!olDyuOrCmlulV(QCmTeOZBY`RS3MqEeBD;h|m}T*|8aG8Koc7Q! zIhpH8x~?yMprj`V3g~_*CqLcdMLqME+#d z4V9x?#a~=;DW&~!-20_9TDA%&;g;GT3y z;gm~Bm$C%yFYXxxiRs|X@8_J;CC)Ql*xM7_mSN3Z6z=H>Zam#aq!R?~h&y|N8>jun zmEnSp5_GJf6Y-{;FLK~~kz?nBTNbYHd^;c9mVqYJ&IjeXoiB7dAKdso-0Jh88&8iA zzm62?vEr$5paY}!P=Eb=hS9{7Ktk}#Fb)^E){g9__bQlIf7myO~d^Qk?sTOn?Wam zW+88vu`FRlmarm=aVxTL52xIUEMY|!<5pw|E3z23B8$HZEE~De7BreuSo9ERR&5q& zKS75GI-~Y6T)9LV%lZk4{e;ARLSjEiyo&V`68i~R1HoYz8wd`Z4iS{+*g*XHCL4@r z-v%8nC`uFQ$+d?;a|F#7v{2CLf|d(9Q_yNb=LkAq(1n85fDRG54H3Ex5xNZ#xiLiO zHbm$)TJ*~>zhb(-A01LF3`x(Z=lhfit-&P%6FtF*-@+lr8$bt0v#>x zA1(55w8+EJsMT$8J%Ll?=PFQMLq-b@qoLa_q&$3YD$-@h!_jP}__bQl*|o!PWsXSC6~E3CcU~g&Ss=JA6u&M) zdW^7QjIeqP-q37Z;kJ+AZzyO2r^1#o!j>_@mNBs9I$Y=ex29(F+%?_jMw=wu!2)w=f^N!=f^;EPA`$>g7P{)7INPO2s|B4S=zKvJ3R(j?0em)t@;7LbuziwvmnRA9CJE~%3F{^a z>m~{7CJE~%3F{^a>m~{7CJE~%3A-iTl+pMgmtq)r=nIAfUdAeB8GI7sLaeswK zSBvy4k)9*c^95ZfXbtFep+m8F2a6%=Fe`=>PWd}n3@M!Qcd!_?a9SlOe-n#g5vTl3 zEQUp#@;9*r+`a(K5j0=WazSSbT7{f1L7wn?X4h^6odf+#z=vO1DCi<_y$0!WaC;v# zN6>sh3k59~bf%zHf>sMUN6>|!Glg|CMGnstIXn}s-}|frJP&~82wEuUOhKyztrm2S zpz{T-0j(6=Dh0Pn!EKh{GfVK9g?u}}W+C4=<=kc=uQ=u0X2C{IIk#EJr2}k^;5J8a znkGy88gacwT(1$=YsB@HXxVhOQi>L|1hr+Qw187%FZx%n0YRp+*Fk5q zx7oW=7U=rgqo5n4ZlD`+&)aO1xUxm0x8nY{*|yq8K)2Vf0^KRD>=D0yA+^Aj6C!<5 zJoTfv65`w-; z6Z9KFPvV|6;JFI)U6EcV=z4Kw1MXP^Dg4(hf^LPSYaoSR;rg$Elt)0hlr?M@G+e{> ziR*{Nv)>AOLR{e%tzkb2`U}6JN=UC2IlNZn@LGxI@LG|>YoQg=(2CRb&}uE)0RC&) zN3d}%wBqS4kh@k$T+6-?R}KhzNIdnWphv}(Zv_2T(C-92CVu^1q)&?UDUm)cXf3DE z5Yk0izsssn7DotW%QoV^F8(h>{=(vXq~77>qHr?V_R|O zI`HWRx=T=A;_KL#Xff8YW8x{EOY31Nr@VaEi)Yuft&qDOInS@~@?8%d_!VB79|~C? ziu*ql_kSp~{SbG)#x{uiH?aMXwSk=k{YYH-2poMmsMTu_)|D(9V%YCyb_suAGeuckNn}v;=ktYu!&82S^7Ht+fY!=ctqdu&{ z^+R~SHY0~00X-&Yh~JNrUB$Kti?#@hwun630$Kbzm$gO6+5##33a|59!2deXog%#p zdAmitQ(HtHZefQ}rdt@#v8|}pZ9vh(0xf~ut*F(Ykhlu;T|w6&S5b!ie#qSlx)By_ zg)RIZo`+ju1;27YJo_c2Z)HbC`fEYI5x;&b(#K%wR(4#Zd9B`xddTTXL4Ok0e-reS zpr-|`y<|{f)TiTao@wQ2yp_ zgQZ;h_k#W)uKXm@KMVS+puY)P%PDNcQ?T(U+b+s|yC{Y2Li6n?L8}8gL>hN}G z{umsS9&kI%_6Tl!gk5{^)N8oHOJNV*g;k)3QS0^yyY?{NKJQU^JG@ua%)Q|A2-5uR z-U|*7f$~;puXyA3id@<&G}$ZOg1z9$@8R`uui*9tJA(Va5GeKq@`R^<0k{3A$(up> z{rgewM?nt@dPMxnIqU}qekX4~_lx}8&wdo?pP>1E@Zt1#K|}mHG{?IE&PPG_3(9fl zfZ%^X@IN5*IRFhe<2twZfGDp6LdpT5=K&$>fRJ?nQXaxRT*?8FGY17i9TW(45VE$i zgTjh~kiyg4-h<%J)5iq;fm8A9A@JGC4l&+-eknBk63=c$n%n**p5)l-9C&fM{(tM)TkqZ+Y!O-h_LsF(BufX9mTJ_P97099uYi`21nPYQZd=*6LX&TW z^lycp$AnhLgpJ3<^qprSLaP&^)jc6v-4mi#oB-!l?1adl6L=~MXZ$m=EkjVd# z(5+V7Un@$sR@_-Da-de+Su5_W<@%_Yv0ZC}B0tqk{sw`WVl!e}HY#ypLKXy0}jN7)OtQ$be=3NIRIpfD&rTZ~IfQi5E{XNQS@W29#( zElZJdXG$ZKirk;l47*xiNof`L@MtvrpC(1f$0)5!5eU`eUN@w9tR1CIDTcjAX-kSg zY&-WJk6fgGPU$eIwaKSp@PFY_lKDHOn@E1^R!T=m@gD4wAj#PGR+65( z0)2W=nn@0p@w)e@xaWRKYnW}enbJDaAxaz4Nkl?%@5gF&b~mLhsWrS$_qs>fqSna@ zlX{rhbUj>}Xx>BVCen1P3#B6@$J!ueNkvit{;KddPs)>WB`^MhI1eI~Bb7uN#MNvUvmA7-wxn5sNYKpgCSy@4G zp0{sVMR{38u&Su6G|B7fd-i=k?{M(wAFMjhJD@ZNj0Zu_X^>kXWV$+gr4cwQl`8Rb z(X>35n|DO86c@OT1yVI^4&oVUcwRwuaS(}2+?|8pN|B44n-{aFy#EvT%*vd+(%ifX zZ#VBB1pTXujTT}nDcgB5C83Z~KvN}YbY4Xza>VOPN=Zq=`JXZW6PsO=|LkTjAp^Y_2v0&0@TxNm9Qp61pj;Zy zUavIrPb3b7#C-7Mh0t&(KdZpALHxowmmys!u6Z$~yc)l84RXbEbyc@2I2^N#0?>s=U%FZ?H7iJF@=4q51hm zIeFq@PF_V-5I2`q6{7T~S638O<`(7fN?Qrpf2>P?{%%S2^`}v-mymNj$7YFq7%cLS zpI3_4Q5sQ|H!IIOI9OGcS6NmnNqulbC4T1F%HLNnr1MNE!5M!8XCu3KrWT4czn8ZY zoE>jJO6kdIXe;Cx%ix7k3JJ4>dPPE6E{#`;d_gOP+)~K!;yL$il%BlPE0*0m>y{0gFtDS|XEhvyW}Pu_k6k>YvgRzqF^ zbLw-6mxx871;aM(W3|iqqf6CF#t;VknuL8wFUv5DiZ(fGn0)EYX zZF$aetBP^XB~^&u_*t1W13J2;l8-aq&hgyK72LW(`#;NHekXr{U8!YwhUZBxp62z4 zU(LlGIk1hFn)|A|IKp4tGjopT7|(V0xpL^p%hJ`Q1XuC;^a)ntRYYr-Sy5C{cAj^3 zVNp(@cXqJSo10fzR8Wc^r_J@A`677nYp@jWZ)w>qfXA%7^YB{dSL9U|7L^uwE7A0O zEAuLf@`;7FsxVmPMZ-{%S5;Az6D%&C3rs30$D`8#LbHpi3c2)P@e@g|7HBo{(FS{q zO3EwBW(oDWRpwOW<&{ECFgG}@sJN&KJPLyq!5p+DXi$oBxJtPYmFF!FmUatNSCp0K zLCG=w1~uFWwcK{5vaEPkUZuFZG%qi=lDAX2c-M>Z5Cj*OmCfL$51>l$*2kZ zza*5%nOMkc1@{b+sD32l!K$kAp2^9xXU|Rw5(wr1xs#w2|MlNw%U>>DVH-CMZuLzP zJWEhr{#JC=-10oCBo&oOg;gcEpTnPv>~-yAHR>oA!E1lrTQmZ`3V#oW3I+I^nN79c zdlm=Y&Yca`smL1+%fN?HUU{p9SHMet#DGEGq2;J1*(f7;3F3zHymf${%F+DH>~dS6 zBIv>2xFr1XmM2M+K>C^Q>XpAD#lMdekMNtH##No5rU$wRa88(;Ln|J8PqZgo>< zQco+KV`Tw6N)`ULj+rDTX4`o%gC`uj#7AI$OBCj|HO1_W7%3KW>|0tsz@ zip3Orr0=DBr6;5xu`ci@=>h3!>3wM(y#8sZAOAp&_z-nsz4U>!QQ9DVBz-I$MZ2^W zdpmE&q~HRC?zutQA#IbkqrASBzLl%G2O_GzP-=t5lA9kwjLkuNH_RHN7$G(T0jtvuf$>(F8)`il3=>X!aXULg|YTH-N zk^^!!-*ZzQAYX)-`Ge%a=*2yUsH{V=r}o7N{5k^Lu#G~T+A-4a(kbbnbO<|Wjzb*v z3GzgFk~~?SB2UG3YSR!^E*HBL=gS3hp7UVmAA>; z{#`yLpO!;%Emr&>6s^Lr*R_VQmIn6swiJ&Nri3d^ln5mfi-Mw+rb;sRVMDeaXGN=M}!B~j_5bjG$fU6iiMc}h1WNl8{xlvKs1*ove0 zmF`NK(nCpCdMdq?^OXyf3zgnVhLWlDQTi%b2nv&}^i%pP1F)y@KxL3JSQ(-WRfZ`S zE5nr$%1C7tHhmqVj8(=dp^9jlB47*c}l)gpcE=a%5-IhQmm9H zrAnDnuFO;_luD&asa9qwvz0l@TxFgzU%5nCpe#h>*cxTAa;b8ea=CJaa;36FS*l#6 zT&-NAT&rBCT(2xs{-NBU+^F27+^pQ9+^XEB+^*cA+=a=2Y669q{G$A-{HFY_oKj9JA*B{!UuC`|0UL1hfOH1- zQ?Qr^o6(1}CM*K$-lAAEYs#9j7#7Q#vlc9lwPXm8f;Dh$SOROyysRB-&pNP<>>P%` z6Ra~kmvv!X*?FuROJd0^g{3kdvzf#EtUF6%Jy<&Hi8)8-vkTaTSh$#hxj%haU(DYL z@R>QRKO4X`JzTEoE1+tJyW|T6P_~o-JelU^gHh@NMiSb~C$0x{KY)ZezEz zJJ_A;d*5dx$;E9$}BN$JpcS3HBs=iapJqVJq0P>^b&4dx5QF zFS3`|%j^~QDtnE+&Q`HE*qdxMdkf!(-@#W@d>6&nMtoyr@3Rl^{ci)l+I@_#Y@6_v z3tz18U1U4HNPNO}vQP0HVi)_Ie>1=c{XUGw@5ksiMzAryd>A9dN7+|=#22Hu-?3xt zIL2Im!00H(FEP6KGy8@8%6?LRQ< zS*%{FUZ!5IUZGy8E>V}NSE*O4*QnR3*QwX5%hZ3UH>fwNH>o$Px2U(Ox2d9I)9N$o3iVm_IrVw<1$CwR zqWY5hvigeps`{Gxy1GhzLw!?St-htct-hnKQP-;Ps_&`m)b;B7>IdqF>IU^A^<#A- z*2HX9x2RjyZR&P)hx&=SQ~gx^Ox>k^uI^U%sC(6Y>KE#M^?-U%J*0lA9#)U2N7b*? zuQAj5TlG8j7#7oful}H(P*18qsz0eetG}qfs=ukftEbe{YDlfsBrF3{u-|~HX$W#_ zXr^Xq9xY4@*P3V%TBH`GMQcs9W?GCEt2NhJXmMIgEnaJ-wbt5b30hmttF_bGYaO(X z+BsUH)=BHEovU@xx@zZX-LxbvSxeDUHJ@f{j^@|8YiU{!EnVxW_0rDQF3>L2dTSY4 zrq)O6t7T~cEnDlS_16Yy7ij~vLE2z#h&EIkrd_NJ*G6a~wNct=ZHzWn8>fxeCTJ6} zu6wdJMVqPxwP{+8maFAy`C5Tis1<3`wHaEmR-%<^Wm>s5Q>)M_wJNPzo2AXx=4f-Z zdD?vK5^aIDP+Nq!jEl8Pwac{2wJWqMwI$k8?JDhR?HcV`?KZ)k67tF^bZx3zb)HQHM3UF|(>owi1?iFzl!vwp7LMenMgr+3qn^kh9nPt|?8tvkA3@2;omJ@j()F!Z^_hBw zUa42<)%q-bwmwIntIyNt>zC*Y^o9B&y+&WGU#efGU#?%FU#TzAm+DvPSL@g4*Xq~l z*Xzslf9N;pH|jU(H|w|Px9Yd)x9fN4cj|ZPckB1)_hMo7efs_S1NwvdL;Az|Bl@HI zWBTLz6Z(_-Q~J~TGx`esS^YWvdHn@_rT(J+lK!&(ivFtpn*O@JN`FItQ(vvWrN6De zqp#7|>hJ3B>Ff0M`uq9^`iJ@k{UiNjeWSif->h%Zx9Z#U?fMS=6Md)tss5S1OaENo zt?$wI>ihIB^!@q)1O`2%f2kkVkLXABuk^3=Z}e~V@APB(as7M!2mJ)XhW@Dkr2nk{ zqW`M@rvI*=(ogFlz1EPhSW`hjOcgsF>xN;NhGlq+FeBV(Vni5`MwAh4G&PzTF-EM> z+-PCM87+-?qm|LxXk#QGvZmK)XS6pu7#)psj6|c8(b+iH=wfs=&NI3hNk+1fVx$^A z!!{hlZ*(`(j2=e1(bMQ7$c2Q#%N=VG1eGoj5j726OBp6WMc{z;|GmtMvjqdL z#!}-d<7(p?<67f7<9cJ6@ekt$<3{5q<7VR)<5uG~<96c?<4)r)<8I>~<6dLAai4L& z@qqE5@sRPb@rd!L@tEUtTbLUUNT-bUNK%ZUNc@d zRvB*?ZyKwOw~V)qcZ@a0TH{^gJ!74*-gw{m!1&PEV0>hJY-~jM&CSLZW2>>v*lz4F zJ~4J0pBkSTyNu6`-NqhcFXC~2VeH3Owu8nY<4fbPal|-kd}VxXd}DlTd}ka(w9fC1 zAB+>mN#jT3C*x=17vopsH{*BXlyTY!8MP)NLrE`~GUC)S=_To9X|1VBE2S5ucTG*Y zRC-5RB0Xv9reT^`WbVQ0CV)6Kv^mBcYmPI=n-k25<|K2nImMi6 z2F+<^j+txbnfYdcS!foS)6E%Xu~}l4nq_9WIn%5#E6pmi+MH$1Hs_dg&3Wd0^AdA` zxzJo>)|iXUOU=v7%grmyE6pY5Qu8YFYV#WNTJt*ddUKids`(G|2J=SqCi7s}$51C(@ zhs`7AQS&SFYx5iPTk|{fn0eg%-u%HlfjGuLnm?I8o4=U9n!lO9o2SgvX2`6yuwWjs z2AHKsR&%R`6=$`y;;mLzYpacwV70Zp zRy(V`)xqj$ons|hovhB*xmFjet972$%}T#a!SbnR!m1gy@(yg9WFYA2k z0_#Go*w3&stv*&?E6WO4*;YTRzcs+R$Qo!3vIbj2tf5%se6cm$8exsJMp>h+G1geb z*BNh3uqIlQtjX3CEP)PM)2tjT*UGc*to%dE?-E37N6CDu~wD(hbYJFvWZGB^XYkh}c634CYtskrt)=BF}>nH1H>lf=+>o@Cn z>y&lc3R$%t35(Yi5A&!V&7*q^kBP{29#5Dj+|$Gp;fX|uyJ$~SPcu)9C)U&4)4~(y zY3YggwDPp}wDBYu)ulx#DVZsB%(5^PUQvmG-#k`QovN1<<>r=E8JQ(~q`A}xx<@^8 zS_Q_j^`JNynPmlKrFk=qpnDAKn^RPQ!Rq|tyg6Yx4QWqbunOj2xU|ZXQ=inca)My3 z=emb1$O%>%0TPJsdt9&{a0TXxgC|h`v?s4VX#_}(Jol&vT#k9-5Z13jk+6b>w5MM^ z15ZJHGQ1x~y-R|wvf%}dPQv;(JR4TnkXHLo3s$IwAo_r+qT<{<9UsSWFb0q%MP$hU z*ODSP>jsd{Meb1dVwk7!yAbW zuV{3l4|jE_a1SHgrz*uEY(zuWg;h4B!$&q^5MI^jL?7w0sS*cuBrnn`UZkVkBCU3d zbQD=tO;(L^t*RCWZBzw5>S*|WTw2g%RAZZ(R5v~|Mp4mMyT`CG4H|~cZj>I^C_T3! zt&ewYohJ^S@%44rGp|0W6_=G3REk$SwTJtPr}jvta~~aTIy!Xp)3G}p)9BcPj_GvF zq+=gC_N8MM9RqaCc8}>KKb_>KliYNYn@)1mNp3pHO((hOBsZPprjy)slABI)(@Ac6 zwl=h|tODInPF)=`$>vOwok_AYNp>d5&Lr8HBs-I2XOiqplATGiGf8%4KpQ1wRyWA( zL-zI|*?mZMACldNWcMN2eMoj6lHG@7_aWJRNOm8R-G^lNA-nrfe)pyP?o0CflKj3T zzc0z}OY-}Y{Jtc=FUjvq^81qfz9hdd$?r?@`;z>=BtMJfXOa9YlAlHLvq*jx$O7Rk>d`B@}CK=K15KS1&WBtJm%10+8{@&hD4K=K15KS1&W zBtJm%10+8{@&hD4o8)Jc{A`k+P4crzem2R^Ci&SUKbz!dr+e_(8to)+2tC32q>&jA zmyBSZIED?aEDlx{y6Lipw5a*M6jvKe%c65v8()g6jW5O3#+Tx1<4bY1@uj%h_)=VL zd?{{j_)^^5@TIu9;Y)FI!A2o zM>J}n!{M3K<1%q1O;SmdRMI4sG)W~*Qb`jZY2qVIe58qwH1UxpKGMWTn)oOue3TPD z$_XFI_mO-b$@h_bAIbNTe4FIkB;O|aHp#b1zD@G&?$-1qwCKf{1mIe1lLj_vV3P(m zX<(BEHfdm!1~zHnkOmHE;E)CmY2c6s4r$;}4mgwp4#{^&zC-dIlJAgwhvYjX-y!*a zlJ6(^2tLJ~P@kXV`$@i^VzMtg#Nxq-t`$@i^d-<{-lC;8n;es_}Jo#dyH{4|oEM)K20ej3S7Bl&40 zKaJ$4k^D50pGNZ2NPZg0Pb2whBtMPhlPBOKPr%oM?D#S%|H&)xkyqd&ufRuMfsecbA9)2n@(O(975K<2@R3*G zBd@?mUV$%@?9U|meW?7&Gw_jT;3Ln#N1lO?JOdwj20ro(eB>GU$TRSfXW%2xz(<~e zk30h(c?Lf641DAn_{cNxk!Ro|&%j5XfsZ@`A9(^k@&J6~0r&gyc3M zxt-FZNg*bl1*f5--{>h<7n|_hCOo$Z&uzkUoA4YFcwPB~=QiQFO?Yk-p4)`yHsQIQ z>b4bSxlK@R6O`KofP>z^#E?&DcHN6rOO8J>5v$z~nS9#J3mX=lJ73UQN!vp1* z{)hR6{IVISqTdH#08Ly9A6inx2gcl!QH^f)3@ynkaBqq(!tH0s(u6FvPhM3}>lcJ4 zZIB#wJT5WF(MA>`rE)>qz+icK5Ti*Y({h8#;A&+^wKBE{V>&{zGOUOVFD%nW6cv;N z*~nnEL0Yh3g+;6{{)SZ+g>yc28bs0!b?2U7z2V_`jVv&sfSVm$f02r*c7LyXR9w`= z^XfF*t`%@&wA{Sns-Qu<)Oq-U@gFg16DDzH+6-Y*v1?LkwKAv3D0A&$6@_Iw<|3B_ zQ#Em5Rj`TVvU1pygFmRDmGKM?=j`g#i9AcaaN_Bf4*e)jJmO)Ibl-UbNt-~@CXloV zBy9pon?TYgkhBRTZR#f5)J?Xjn`~1z*`{u?P2FUhK-DHtwFy*h0#%zp)h1B22~=$Y zRhvN7CQ!8rRBZxPn?ThjP_+qEZ30!BK-DHtwFy*h0#%zp)h1B22~=$YRhvN7CQ!8r zRBZxPn?ThjP_+qEZ30!BK-JE6oiT?x#twCiofOv@b*N+PP{-IIICTh49fDJbI>rul zj2-G2I|QT-0jWbk>JX4R1f&iDsY5{O5Rf_qqz(b8LqO^fkU9jU4gsk{KJX4R1f&iDsY5{OPJWxHgrN>$s6!a)5QaL0p$=iFLm281hB}0y z4q>Q680rv)I)tGPVW>mhVTUl(Aq;g0Lmk3UhcMKk&agum>JWxHgrN>$s6!a)5QaL0 zp$=iFLm281hB}0y4q>Q680rv)I)tGPVW>kG>JWxHgrN>$s6!a)5QaL0p$=iFLm281 zhB}0y4q>Q680rv)I)tGPVW>kG>JWxHgrN>$s6!a)5QaL0p$=gv!Z3>V$sr7N2tysh zPy`UC-(5$}Aq;g0Lmk3UhcMJ340Q-Y9l}tDI>Qcih8^k*JJcC=2xA?>Scf{p4t0hd z>I^&78FmPR9l~ITFxVjscBnJ#P-obo&agwBVTU@y4t0hd>I^&78Fr{M>`-Ueq0X>F zonePM!wz+Z9qJ4_)ERcDGwe`j*rCp_L!DuVI>Qcih8^k*JJcC=s59(PXV{_6utS|; zhdRR!b%q`43_H{rcBnJ#P-obo&agwBVTU@y4t0hd>I^&78Fr{M>`-Ueq0X>FonePM z!wz+Z9qJ4_)ERcDGwe`j*rCp_L!DuVI>Qcih8^k*JJcC=s59(PXV{_6utS|;hdRR! zb%q`43_H{rcBnJ#P-obo&agwBVTU@y4t0hd>I^&78Fpxx+@WD|hdRd&4U;?6Np`4{ z>`*7!p-!?xon(hP$qwPaL-_9y{yT*K4≦`0o(@JB0sERu3zfUsRNu(xa|rA`ieJ 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 Y&|?BN{_*!W3tm6{caJds=Lh%wJ%~ewUjP6A literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a6127bcebca9d7c6f851c5962cfae92c27f0ac2 GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x#lH~{772?W@gST%*&)U9MVUHsK~ z6En?4>S8TKK~BR8k_`nbODsh|T$}HIh!*b*TzWy`!t9JvK0viW zsyzQ=b@DlixM-{vayf2zOy|9VG>S^4IoKw9#Dm7VziZg-jg zkp^PUxb(~NvhdYANc`{hlKHnRUhUuHAcdc;F4F%edduCI6{ProX_U(U4j+X&T>9ns zSUI~rW&Y0&QT)9!MdQrO2<88CLX`e52v^D2lCAr1W2W~1)hU|0aOqdzXJwrcuJnI( zy5`T78JcN}lhpqotTFjl;-x6OyTtg>j$-})+Y0qRnkeuxV>3fhfQ@NYme#*REvDa# z{p5HKG?{(8Hp}@QNbT-Q^CdgW4gc>bGx#5GBVmM1KS=CwtNHJni(I~e*n3;er{7xS zz8b`ym1UZGxYgqSz6O*3eer7Gv;h)F29BD-QnwcSKYX##_X|io%U@A?cduOrh@I`F zrhZ|9)Bl4lX8(8A8LtG1;Q|>UT9Pn1OD!Qr82j;D-{*&0&He-Z_XH+J3cY)RU)Z7P zfy<_)8QqUF6pANBFS^<=dj%no*#G|w4FA{}7#;xeLw*Pbu~9HEwjKae-6tS^1jN6S Mf%+Z-@m~f804xF1i2wiq literal 0 HcmV?d00001 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