From 14929e9d156351221b6da192710437bb50409f29 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 7 Feb 2018 11:37:15 +0100 Subject: [PATCH 1/9] Http client via libcurl --- cmake/modules/FindCURL.cmake | 59 ++++++++ xs/CMakeLists.txt | 7 + xs/src/slic3r/Utils/Http.cpp | 261 +++++++++++++++++++++++++++++++++++ xs/src/slic3r/Utils/Http.hpp | 53 +++++++ 4 files changed, 380 insertions(+) create mode 100644 cmake/modules/FindCURL.cmake create mode 100644 xs/src/slic3r/Utils/Http.cpp create mode 100644 xs/src/slic3r/Utils/Http.hpp diff --git a/cmake/modules/FindCURL.cmake b/cmake/modules/FindCURL.cmake new file mode 100644 index 000000000..b8724858c --- /dev/null +++ b/cmake/modules/FindCURL.cmake @@ -0,0 +1,59 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindCURL +# -------- +# +# Find curl +# +# Find the native CURL headers and libraries. +# +# :: +# +# CURL_INCLUDE_DIRS - where to find curl/curl.h, etc. +# CURL_LIBRARIES - List of libraries when using curl. +# CURL_FOUND - True if curl found. +# CURL_VERSION_STRING - the version of curl found (since CMake 2.8.8) + +# Look for the header file. +find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) +mark_as_advanced(CURL_INCLUDE_DIR) + +# Look for the library (sorted from most current/relevant entry to least). +find_library(CURL_LIBRARY NAMES + curl + # Windows MSVC Makefile: + libcurl_a + # Windows MSVC prebuilts: + curllib + libcurl_imp + curllib_static + # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): + libcurl +) +mark_as_advanced(CURL_LIBRARY) + +if(CURL_INCLUDE_DIR) + foreach(_curl_version_header curlver.h curl.h) + if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}") + file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"") + + string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}") + unset(curl_version_str) + break() + endif() + endforeach() +endif() + +find_package_handle_standard_args(CURL + REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR + VERSION_VAR CURL_VERSION_STRING) + +if(CURL_FOUND) + set(CURL_LIBRARIES ${CURL_LIBRARY}) + set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) + + message(STATUS " Curl libraries: = ${CURL_LIBRARIES}") + message(STATUS " Curl include dirs: = ${CURL_INCLUDE_DIRS}") +endif() diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index caabeab6b..d2f252030 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -199,6 +199,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/2DBed.hpp ${LIBDIR}/slic3r/GUI/wxExtensions.cpp ${LIBDIR}/slic3r/GUI/wxExtensions.hpp + ${LIBDIR}/slic3r/Utils/Http.cpp + ${LIBDIR}/slic3r/Utils/Http.hpp ) add_library(admesh STATIC @@ -522,6 +524,11 @@ if (SLIC3R_PRUSACONTROL) target_link_libraries(XS ${wxWidgets_LIBRARIES}) endif() +find_package(CURL REQUIRED) +include_directories(${CURL_INCLUDE_DIRS}) +target_link_libraries(XS ${CURL_LIBRARIES}) + + ## OPTIONAL packages # Find eigen3 or use bundled version diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp new file mode 100644 index 000000000..45a350a59 --- /dev/null +++ b/xs/src/slic3r/Utils/Http.cpp @@ -0,0 +1,261 @@ +#include "Http.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include "../../libslic3r/libslic3r.h" + + +namespace Slic3r { + + +// Private + +class CurlGlobalInit +{ + static const CurlGlobalInit instance; + + CurlGlobalInit() { ::curl_global_init(CURL_GLOBAL_DEFAULT); } + ~CurlGlobalInit() { ::curl_global_cleanup(); } +}; + +struct Http::priv +{ + enum { + DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024, + }; + + ::CURL *curl; + ::curl_httppost *form; + ::curl_httppost *form_end; + ::curl_slist *headerlist; + std::string buffer; + size_t limit; + + std::thread io_thread; + Http::CompleteFn completefn; + Http::ErrorFn errorfn; + + priv(const std::string &url); + ~priv(); + + static size_t writecb(void *data, size_t size, size_t nmemb, void *userp); + std::string body_size_error(); + void http_perform(); +}; + +Http::priv::priv(const std::string &url) : + curl(::curl_easy_init()), + form(nullptr), + form_end(nullptr), + headerlist(nullptr) +{ + if (curl == nullptr) { + throw std::runtime_error(std::string("Could not construct Curl object")); + } + + ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally + ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION); +} + +Http::priv::~priv() +{ + ::curl_easy_cleanup(curl); + ::curl_formfree(form); + ::curl_slist_free_all(headerlist); +} + +size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp) +{ + auto self = static_cast(userp); + const char *cdata = static_cast(data); + const size_t realsize = size * nmemb; + + const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT; + if (self->buffer.size() + realsize > limit) { + // This makes curl_easy_perform return CURLE_WRITE_ERROR + return 0; + } + + self->buffer.append(cdata, realsize); + + return realsize; +} + +std::string Http::priv::body_size_error() +{ + return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str(); +} + +void Http::priv::http_perform() +{ + ::curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); + ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast(this)); + +#ifndef NDEBUG + ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); +#endif + + if (headerlist != nullptr) { + ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); + } + + if (form != nullptr) { + ::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form); + } + + CURLcode res = ::curl_easy_perform(curl); + long http_status = 0; + ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); + + if (res != CURLE_OK) { + std::string error; + if (res == CURLE_WRITE_ERROR) { + error = std::move(body_size_error()); + } else { + error = ::curl_easy_strerror(res); + }; + + if (errorfn) { + errorfn(std::move(buffer), std::move(error), http_status); + } + } else { + if (completefn) { + completefn(std::move(buffer), http_status); + } + } +} + +Http::Http(const std::string &url) : p(new priv(url)) {} + + +// Public + +Http::Http(Http &&other) : p(std::move(other.p)) {} + +Http::~Http() +{ + if (p && p->io_thread.joinable()) { + p->io_thread.detach(); + } +} + + +Http& Http::size_limit(size_t sizeLimit) +{ + if (p) { p->limit = sizeLimit; } + return *this; +} + +Http& Http::header(std::string name, const std::string &value) +{ + if (!p) { return * this; } + + if (name.size() > 0) { + name.append(": ").append(value); + } else { + name.push_back(':'); + } + p->headerlist = curl_slist_append(p->headerlist, name.c_str()); + return *this; +} + +Http& Http::remove_header(std::string name) +{ + if (p) { + name.push_back(':'); + p->headerlist = curl_slist_append(p->headerlist, name.c_str()); + } + + return *this; +} + +Http& Http::ca_file(const std::string &name) +{ + if (p) { + ::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str()); + } + + return *this; +} + +Http& Http::form_add(const std::string &name, const std::string &contents) +{ + if (p) { + ::curl_formadd(&p->form, &p->form_end, + CURLFORM_COPYNAME, name.c_str(), + CURLFORM_COPYCONTENTS, contents.c_str(), + CURLFORM_END + ); + } + + return *this; +} + +Http& Http::form_add_file(const std::string &name, const std::string &filename) +{ + if (p) { + ::curl_formadd(&p->form, &p->form_end, + CURLFORM_COPYNAME, name.c_str(), + CURLFORM_FILE, filename.c_str(), + CURLFORM_CONTENTTYPE, "application/octet-stream", + CURLFORM_END + ); + } + + return *this; +} + +Http& Http::on_complete(CompleteFn fn) +{ + if (p) { p->completefn = std::move(fn); } + return *this; +} + +Http& Http::on_error(ErrorFn fn) +{ + if (p) { p->errorfn = std::move(fn); } + return *this; +} + +Http::Ptr Http::perform() +{ + auto self = std::make_shared(std::move(*this)); + + if (self->p) { + auto io_thread = std::thread([self](){ + self->p->http_perform(); + }); + self->p->io_thread = std::move(io_thread); + } + + return self; +} + +void Http::perform_sync() +{ + if (p) { p->http_perform(); } +} + +Http Http::get(std::string url) +{ + return std::move(Http{std::move(url)}); +} + +Http Http::post(std::string url) +{ + Http http{std::move(url)}; + curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L); + return http; +} + + +} diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp new file mode 100644 index 000000000..c591e17c5 --- /dev/null +++ b/xs/src/slic3r/Utils/Http.hpp @@ -0,0 +1,53 @@ +#ifndef slic3r_Http_hpp_ +#define slic3r_Http_hpp_ + +#include +#include +#include + + +namespace Slic3r { + + +/// Represetns a Http request +class Http : public std::enable_shared_from_this { +private: + struct priv; +public: + typedef std::shared_ptr Ptr; + typedef std::function CompleteFn; + typedef std::function ErrorFn; + + Http(Http &&other); + + static Http get(std::string url); + static Http post(std::string url); + ~Http(); + + Http(const Http &) = delete; + Http& operator=(const Http &) = delete; + Http& operator=(Http &&) = delete; + + Http& size_limit(size_t sizeLimit); + Http& header(std::string name, const std::string &value); + Http& remove_header(std::string name); + Http& ca_file(const std::string &filename); + Http& form_add(const std::string &name, const std::string &contents); + Http& form_add_file(const std::string &name, const std::string &filename); + + Http& on_complete(CompleteFn fn); + Http& on_error(ErrorFn fn); + + Ptr perform(); + void perform_sync(); + +private: + Http(const std::string &url); + + std::unique_ptr p; +}; + + +} + +#endif From 751e86cd4de3e768e2a1f095ca396ca1b5a3c607 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 23 Feb 2018 16:48:11 +0100 Subject: [PATCH 2/9] libcurl linking and cmake usage improvements --- CMakeLists.txt | 15 +++++++++++++++ cmake/msvc/slic3r.wperl64d.props | 9 +++++++++ cmake/msvc/xs.wperl64d.props | 11 +++++++++++ xs/CMakeLists.txt | 11 +++++++++++ xs/src/xsinit.h | 9 +++++++++ 5 files changed, 55 insertions(+) create mode 100644 cmake/msvc/slic3r.wperl64d.props create mode 100644 cmake/msvc/xs.wperl64d.props diff --git a/CMakeLists.txt b/CMakeLists.txt index 43d7dee70..b5f7cdec2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,21 @@ message("PATH: $ENV{PATH}") message("PERL5LIB: $ENV{PERL5LIB}") find_package(Perl REQUIRED) +# CMAKE_PREFIX_PATH is used to point CMake to the remaining dependencies (Boost, TBB, ...) +# We pick it from environment if it is not defined in another way +if(NOT DEFINED CMAKE_PREFIX_PATH) + if(DEFINED ENV{CMAKE_PREFIX_PATH}) + set(CMAKE_PREFIX_PATH "$ENV{CMAKE_PREFIX_PATH}") + endif() +endif() + +if (MSVC) + # By default the startup project in MSVC is the 'ALL_BUILD' cmake-created project, + # but we want 'slic3r' as the startup one because debugging run command is associated with it. + # (Unfortunatelly it cannot be associated with ALL_BUILD using CMake.) + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT slic3r) +endif () + add_subdirectory(xs) enable_testing () diff --git a/cmake/msvc/slic3r.wperl64d.props b/cmake/msvc/slic3r.wperl64d.props new file mode 100644 index 000000000..68dac2085 --- /dev/null +++ b/cmake/msvc/slic3r.wperl64d.props @@ -0,0 +1,9 @@ + + + + C:\wperl64d\bin\perl.exe + slic3r.pl + WindowsLocalDebugger + ..\.. + + diff --git a/cmake/msvc/xs.wperl64d.props b/cmake/msvc/xs.wperl64d.props new file mode 100644 index 000000000..101923581 --- /dev/null +++ b/cmake/msvc/xs.wperl64d.props @@ -0,0 +1,11 @@ + + + + + + + $(VC_ExecutablePath_x64);$(WindowsSDK_ExecutablePath);$(VS_ExecutablePath);$(MSBuild_ExecutablePath);$(FxCopDir);$(PATH);c:\wperl64d\bin\; + + + + diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index d2f252030..e4e0e1c6c 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -603,6 +603,17 @@ elseif (NOT MSVC) target_link_libraries(slic3r -lstdc++) endif () +if (MSVC) + # Here we associate some additional properties with the MSVC projects to enable compilation and debugging out of the box. + # It seems a props file needs to be copied to the same dir as the proj file, otherwise MSVC doesn't load it up. + # For copying, the configure_file() function seems to work much better than the file() function. + configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/xs.wperl64d.props" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) + set_target_properties(XS PROPERTIES VS_USER_PROPS "xs.wperl64d.props") + configure_file("${PROJECT_SOURCE_DIR}/cmake/msvc/slic3r.wperl64d.props" ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) + set_target_properties(slic3r PROPERTIES VS_USER_PROPS "slic3r.wperl64d.props") +endif () + + # Installation install(TARGETS XS DESTINATION lib/slic3r-prusa3d/auto/Slic3r/XS) install(FILES lib/Slic3r/XS.pm DESTINATION lib/slic3r-prusa3d/Slic3r) diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 49981b74b..96c4b74d7 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -59,6 +59,15 @@ extern "C" { #undef seek #undef send #undef write + #undef open + #undef close + #undef seekdir + #undef setbuf + #undef fread + #undef fseek + #undef fputc + #undef fwrite + #undef fclose #endif /* _MSC_VER */ } #endif From f67d70941eba392badbfc08cf38c69acfd8ef065 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 1 Mar 2018 16:14:36 +0100 Subject: [PATCH 3/9] Doc: Add dependency build scripts and building how-tos --- doc/How to build - UNIX.md | 2 + doc/How to build - Windows.md | 86 +++++++++++++ doc/deps-build/unix-static/Makefile | 132 +++++++++++++++++++ doc/deps-build/windows/slic3r-makedeps.ps1 | 141 +++++++++++++++++++++ 4 files changed, 361 insertions(+) create mode 100644 doc/How to build - UNIX.md create mode 100644 doc/How to build - Windows.md create mode 100644 doc/deps-build/unix-static/Makefile create mode 100644 doc/deps-build/windows/slic3r-makedeps.ps1 diff --git a/doc/How to build - UNIX.md b/doc/How to build - UNIX.md new file mode 100644 index 000000000..77ce54419 --- /dev/null +++ b/doc/How to build - UNIX.md @@ -0,0 +1,2 @@ +# Building Slic3r PE on Linux/UNIX + diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md new file mode 100644 index 000000000..8209954bd --- /dev/null +++ b/doc/How to build - Windows.md @@ -0,0 +1,86 @@ +# Building Slic3r PE on Microsoft Windows + +The currently supported way of building Slic3r PE on Windows is with MS Visual Studio 2013 +using our Perl binary distribution (compiled from official Perl sources). +You can use the free [Visual Studio 2013 Community Edition](https://www.visualstudio.com/vs/older-downloads/). + +Other setups (such as mingw + Strawberry Perl) _may_ work, but we cannot guarantee this will work +and cannot provide guidance. + + +### Geting the dependencies + +First, download and upnack our Perl + wxWidgets binary distribution: + + - 32 bit, release mode: [wperl32-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl32-5.24.0-2018-03-02.7z) + - 64 bit, release mode: [wperl64-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl64-5.24.0-2018-03-02.7z) + - 64 bit, release mode + debug symbols: [wperl64d-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl64d-5.24.0-2018-03-02.7z) + +It is recommended to unpack this package into `C:\`. + +Apart from wxWidgets and Perl, you will also need additional dependencies: + + - Boost + - Intel TBB + - libcurl + +We have prepared a binary package of the listed libraries: + + - 32 bit: [slic3r-destdir-32.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=slic3r-destdir-32.7z) + - 64 bit: [slic3r-destdir-64.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=slic3r-destdir-64.7z) + +It is recommended you unpack this package into `C:\local\` as the environment +setup script expects it there. + +Alternatively you can also compile the additional dependencies yourself. +There is a [powershell script](./deps-build/windows/slic3r-makedeps.ps1) which automates this process. + +### Building Slic3r PE + +Once the dependencies are set up in their respective locations, +go to the `wperl*` directory extracted earlier and launch the `cmdline.lnk` file +which opens a command line prompt with appropriate environment variables set up. + +In this command line, `cd` into the directory with Slic3r sources +and use these commands to build the Slic3r from the command line: + + perl Build.PL + perl Build.PL --gui + mkdir build + cd build + cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release + nmake + ctest --verbose # TODO: ??? + cd .. + perl slic3r.pl + +The above commands use `nmake` Makefiles. +You may also build Slic3r PE with other build tools: + + +### Building with Visual Studio + +To build, lanuch and/or debug Slic3r PE with Visual Studio (64 bits), replace the `cmake` command with: + + cmake .. -G "Visual Studio 12 Win64" -DCMAKE_CONFIGURATION_TYPES=Release;RelWithDebInfo || exit /b + +For the 32-bit variant, use: + + cmake .. -G "Visual Studio 12" -DCMAKE_CONFIGURATION_TYPES=Release;RelWithDebInfo || exit /b + +After `cmake` has finished, go to the `Slic3r\build` directory and open the `Slic3r.sln` solution file. +This should open Visual Studio and load all the Slic3r solution containing all the projects. +Make sure you use Visual Studio 2013 to open the solution. + +You can then use the usual Visual Studio controls to build Slic3r. +If you want to run or debug Slic3r from within Visual Studio, make sure the `slic3r` project is activated. +There are multiple projects in the Slic3r solution, but only the `slic3r` project is configured with the right +commands to run Slic3r. + + +### Building with ninja + +To use [Ninja](TODO), replace the `cmake` and `nmake` commands with: + + cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release + ninja diff --git a/doc/deps-build/unix-static/Makefile b/doc/deps-build/unix-static/Makefile new file mode 100644 index 000000000..e7588b994 --- /dev/null +++ b/doc/deps-build/unix-static/Makefile @@ -0,0 +1,132 @@ + +# +# This makefile downloads, configures and builds Slic3r PE dependencies for Unix. +# (That is, all dependencies except perl + wxWidgets.) +# The libraries are installed in DESTDIR, which you can customize like so: +# +# DESTDIR=foo/bar make +# +# The default DESTDIR is ~/slic3r-destdir +# If the DESTDIR doesn't exits, the makefile tries to create it +# +# To pass the DESTDIR path along to cmake, set the use CMAKE_PREFIX_PATH variable +# and set it to $DESTDIR/usr/local +# +# You can also customize the NPROC variable in the same way to configure the number +# of cores the build process uses. By default this is set to what the `nproc` command says. +# + + +DESTDIR ?= $(HOME)/slic3r-destdir +NPROC ?= $(shell nproc) + + +BOOST = boost_1_66_0 +TBB_SHA = a0dc9bf76d0120f917b641ed095360448cabc85b +TBB = tbb-$(TBB_SHA) +OPENSSL = openssl-OpenSSL_1_1_0g +CURL = curl-7.58.0 + +.PHONY: all destdir boost libcurl libopenssl libtbb + +all: destdir boost libtbb libcurl + @echo + @echo "All done!" + @echo + +destdir: + mkdir -p $(DESTDIR) + + + +boost: $(BOOST).tar.gz + tar -zxvf $(BOOST).tar.gz + cd $(BOOST) && ./bootstrap.sh --with-libraries=system,filesystem,thread,log,locale,regex --prefix=$(DESTDIR)/usr/local + cd $(BOOST) && ./b2 \ + -j $(NPROC) \ + link=static \ + variant=release \ + threading=multi \ + boost.locale.icu=off \ + cxxflags=-fPIC cflags=-fPIC \ + install + +$(BOOST).tar.gz: + curl -L -o $@ https://dl.bintray.com/boostorg/release/1.66.0/source/$@ + + + +libtbb: $(TBB).tar.gz + tar -zxvf $(TBB).tar.gz + mkdir -p $(TBB)/mybuild + cd $(TBB)/mybuild && cmake .. -DTBB_BUILD_SHARED=OFF -DTBB_BUILD_TESTS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON + $(MAKE) -C $(TBB)/mybuild -j$(NPROC) + $(MAKE) -C $(TBB)/mybuild install DESTDIR=$(DESTDIR) + +$(TBB).tar.gz: + curl -L -o $@ https://github.com/wjakob/tbb/archive/$(TBB_SHA).tar.gz + + + +libopenssl: $(OPENSSL).tar.gz + tar -zxvf $(OPENSSL).tar.gz + cd $(OPENSSL) && ./config --openssldir=/etc/ssl shared no-ssl3-method no-dynamic-engine '-Wa,--noexecstack' + make -C $(OPENSSL) depend + make -C $(OPENSSL) -j$(NPROC) + make -C $(OPENSSL) install DESTDIR=$(DESTDIR) + +$(OPENSSL).tar.gz: + curl -L -o $@ 'https://github.com/openssl/openssl/archive/OpenSSL_1_1_0g.tar.gz' + + + +libcurl: libopenssl $(CURL).tar.gz + tar -zxvf $(CURL).tar.gz +# XXX: disable shared? +# Setting PKG_CONFIG_PATH should make libcurl find our previously built openssl + cd $(CURL) && PKG_CONFIG_PATH=$(DESTDIR)/usr/local/lib/pkgconfig ./configure \ + --enable-static \ + --enable-shared \ + --with-pic \ + --enable-ipv6 \ + --enable-versioned-symbols \ + --enable-threaded-resolver \ + --with-random=/dev/urandom \ + --with-ca-bundle=/etc/ssl/certs/ca-certificates.crt \ + --disable-ldap \ + --disable-ldaps \ + --disable-manual \ + --disable-rtsp \ + --disable-dict \ + --disable-telnet \ + --disable-pop3 \ + --disable-imap \ + --disable-smb \ + --disable-smtp \ + --disable-gopher \ + --disable-crypto-auth \ + --without-gssapi \ + --without-libpsl \ + --without-libidn2 \ + --without-gnutls \ + --without-polarssl \ + --without-mbedtls \ + --without-cyassl \ + --without-nss \ + --without-axtls \ + --without-brotli \ + --without-libmetalink \ + --without-libssh \ + --without-libssh2 \ + --without-librtmp \ + --without-nghttp2 \ + --without-zsh-functions-dir + $(MAKE) -C $(CURL) -j$(NPROC) + $(MAKE) -C $(CURL) install DESTDIR=$(DESTDIR) + +$(CURL).tar.gz: + curl -L -o $@ https://curl.haxx.se/download/$@ + + +clean: + rm -rf $(BOOST) $(BOOST).tar.gz $(TBB) $(TBB).tar.gz $(OPENSSL) $(OPENSSL).tar.gz $(CURL) $(CURL).tar.gz diff --git a/doc/deps-build/windows/slic3r-makedeps.ps1 b/doc/deps-build/windows/slic3r-makedeps.ps1 new file mode 100644 index 000000000..8b39cae30 --- /dev/null +++ b/doc/deps-build/windows/slic3r-makedeps.ps1 @@ -0,0 +1,141 @@ +#!powershell +# +# This script downloads, configures and builds Slic3r PE dependencies for Unix. +# (That is, all dependencies except perl + wxWidgets.) +# +# To use this script, launch the Visual Studio command line, +# `cd` into the directory containing this script and use this command: +# +# powershell .\slic3r-makedeps.ps1 +# +# The dependencies will be downloaded and unpacked into the current dir. +# This script WILL NOT try to guess the build architecture (64 vs 32 bits), +# it will by default build the 64-bit variant. To build the 32-bit variant, use: +# +# powershell .\slic3r-makedeps.ps1 -b32 +# +# Built libraries are installed into $destdir, +# which by default is C:\local\slic3r-destdir-$bits +# You can customize the $destdir using: +# +# powershell .\slic3r-makedeps.ps1 -destdir C:\foo\bar +# +# To pass the $destdir path along to cmake, set the use CMAKE_PREFIX_PATH variable +# and set it to $destdir\usr\local +# +# Script requirements: PowerShell 3.0, .NET 4.5 +# + + +param( + [switch]$b32 = $false, + [string]$destdir = "" +) + +if ($destdir -eq "") { + $destdir = "C:\local\slic3r-destdir-" + ('32', '64')[!$b32] +} + +$BOOST = 'boost_1_63_0' +$CURL = 'curl-7.28.0' +$TBB_SHA = 'a0dc9bf76d0120f917b641ed095360448cabc85b' +$TBB = "tbb-$TBB_SHA" + + +try +{ + + +# Set up various settings and utilities: +[Environment]::CurrentDirectory = Get-Location +$NPROC = (Get-WmiObject -class Win32_processor).NumberOfLogicalProcessors +Add-Type -A System.IO.Compression.FileSystem +# This fxies SSL/TLS errors, credit goes to Ansible; see their `win_get_url.ps1` file +$security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault +if ([Net.SecurityProtocolType].GetMember('Tls11').Count -gt 0) { + $security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11 +} +if ([Net.SecurityProtocolType].GetMember('Tls12').Count -gt 0) { + $security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12 +} +[Net.ServicePointManager]::SecurityProtocol = $security_protcols +$webclient = New-Object System.Net.WebClient + + +# Ensure DESTDIR exists: +mkdir $destdir -ea 0 +mkdir "$destdir\usr\local" -ea 0 + + +# Download sources: +echo 'Downloading sources ...' +if (!(Test-Path "$BOOST.zip")) { $webclient.DownloadFile("https://dl.bintray.com/boostorg/release/1.63.0/source/$BOOST.zip", "$BOOST.zip") } +if (!(Test-Path "$TBB.zip")) { $webclient.DownloadFile("https://github.com/wjakob/tbb/archive/$TBB_SHA.zip", "$TBB.zip") } +if (!(Test-Path "$CURL.zip")) { $webclient.DownloadFile("https://curl.haxx.se/download/$CURL.zip", ".\$CURL.zip") } + + +# Unpack sources: +echo 'Unpacking ...' +if (!(Test-Path $BOOST)) { [IO.Compression.ZipFile]::ExtractToDirectory("$BOOST.zip", '.') } +if (!(Test-Path $TBB)) { [IO.Compression.ZipFile]::ExtractToDirectory("$TBB.zip", '.') } +if (!(Test-Path $CURL)) { [IO.Compression.ZipFile]::ExtractToDirectory("$CURL.zip", '.') } + + +# Build libraries: +echo 'Building ...' + +# Build boost +pushd "$BOOST" +.\bootstrap +$adr_mode = ('32', '64')[!$b32] +.\b2 ` + -j "$NPROC" ` + --with-system ` + --with-filesystem ` + --with-thread ` + --with-log ` + --with-locale ` + --with-regex ` + "--prefix=$destdir/usr/local" ` + "address-model=$adr_mode" ` + toolset=msvc-12.0 ` + link=static ` + variant=release ` + threading=multi ` + boost.locale.icu=off ` + install +popd + +# Build TBB +pushd "$TBB" +mkdir 'mybuild' -ea 0 +cd 'mybuild' +$generator = ('Visual Studio 12', 'Visual Studio 12 Win64')[!$b32] +cmake .. ` + -G "$generator" ` + -DCMAKE_CONFIGURATION_TYPES=Release ` + -DTBB_BUILD_SHARED=OFF ` + -DTBB_BUILD_TESTS=OFF "-DCMAKE_INSTALL_PREFIX:PATH=$destdir\usr\local" +msbuild /P:Configuration=Release INSTALL.vcxproj +popd + +# Build libcurl: +pushd "$CURL\winbuild" +$machine = ("x86", "x64")[!$b32] +nmake /f Makefile.vc mode=static VC=12 GEN_PDB=yes DEBUG=no "MACHINE=$machine" +Copy-Item -R -Force ..\builds\libcurl-*-winssl\include\* "$destdir\usr\local\include\" +Copy-Item -R -Force ..\builds\libcurl-*-winssl\lib\* "$destdir\usr\local\lib\" +popd + + +echo "" +echo "All done!" +echo "" + + +} +catch [Exception] +{ + # This prints errors in a verbose manner + echo $_.Exception|format-list -force +} From b897209e0d10d1c96767cc0f2709a1eaa7a7046c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 2 Mar 2018 21:16:00 +0100 Subject: [PATCH 4/9] Removed -DCURL_STATIC on OSX, added dynamic linking of OpenSSL on Linux, even if libcurl is linked statically. --- xs/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index e4e0e1c6c..a294dc11e 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -528,6 +528,15 @@ find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) target_link_libraries(XS ${CURL_LIBRARIES}) +if (SLIC3R_STATIC AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + # As of now, our build system produces a statically linked libcurl, + # which links the OpenSSL library dynamically. + find_package(OpenSSL REQUIRED) + message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") + message("OpenSSL libraries: ${OPENSSL_LIBRARIES}") + include_directories(${OPENSSL_INCLUDE_DIR}) + target_link_libraries(XS ${OPENSSL_LIBRARIES}) +endif() ## OPTIONAL packages From 79ee7c9a36b4901804e87ecb17dd6821a23ba258 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 5 Mar 2018 18:32:09 +0100 Subject: [PATCH 5/9] Fix #608 Credit: Dylan "smellyfis" Thies --- xs/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index a294dc11e..06456d607 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -624,5 +624,5 @@ endif () # Installation -install(TARGETS XS DESTINATION lib/slic3r-prusa3d/auto/Slic3r/XS) -install(FILES lib/Slic3r/XS.pm DESTINATION lib/slic3r-prusa3d/Slic3r) +install(TARGETS XS DESTINATION ${PERL_VENDORARCH}/auto/Slic3r/XS) +install(FILES lib/Slic3r/XS.pm DESTINATION ${PERL_VENDORLIB}/Slic3r) From 7cfc5204c85c68e07ca351ac810a7f14a8e65eaf Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 7 Feb 2018 11:37:15 +0100 Subject: [PATCH 6/9] WIP: OctoPrint --- lib/Slic3r/GUI/MainFrame.pm | 12 +++- lib/Slic3r/GUI/Plater.pm | 48 ++------------ xs/CMakeLists.txt | 3 + xs/src/libslic3r/PrintConfig.cpp | 11 +++- xs/src/libslic3r/PrintConfig.hpp | 2 + xs/src/perlglue.cpp | 1 + xs/src/slic3r/GUI/GUI.cpp | 16 ++++- xs/src/slic3r/GUI/GUI.hpp | 3 + xs/src/slic3r/GUI/Preset.cpp | 2 +- xs/src/slic3r/Utils/OctoPrint.cpp | 105 ++++++++++++++++++++++++++++++ xs/src/slic3r/Utils/OctoPrint.hpp | 35 ++++++++++ xs/xsp/Utils_OctoPrint.xsp | 14 ++++ xs/xsp/my.map | 4 ++ 13 files changed, 210 insertions(+), 46 deletions(-) create mode 100644 xs/src/slic3r/Utils/OctoPrint.cpp create mode 100644 xs/src/slic3r/Utils/OctoPrint.hpp create mode 100644 xs/xsp/Utils_OctoPrint.xsp diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 572bdac32..034cc2dd5 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -409,7 +409,15 @@ sub _init_menubar { wxTheApp->about; }); } - + + my $hokusMenu = Wx::Menu->new; # XXX: tmp + { + $self->_append_menu_item($hokusMenu, "Pokus", "Pokus", sub { + # Slic3r::Http::download(); + Slic3r::OctoPrint::send_gcode("10.0.0.46", "70E4CFD0E0D7423CB6B1CF055DBAEFA5", "/home/vojta/prog/tisk/jesterka/jesterka.gcode"); + }); + } + # menubar # assign menubar to frame after appending items, otherwise special items # will not be handled correctly @@ -424,6 +432,8 @@ sub _init_menubar { # (Select application language from the list of installed languages) Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event}); $menubar->Append($helpMenu, L("&Help")); + $menubar->Append($hokusMenu, "Hoku&s"); + # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing. $self->SetMenuBar($menubar); } } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5cedfbb08..d48e31462 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -52,7 +52,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height - serial_port serial_speed octoprint_host octoprint_apikey + serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour )]); @@ -1458,8 +1458,13 @@ sub on_export_completed { wxTheApp->notify($message); $self->do_print if $do_print; + # Send $self->{send_gcode_file} to OctoPrint. - $self->send_gcode if $send_gcode; + if ($send_gcode) { + my $op = Slic3r::OctoPrint->new($self->{config}); + $op->send_gcode($self->GetId(), $PROGRESS_BAR_EVENT, $ERROR_EVENT, $self->{send_gcode_file}); + } + $self->{print_file} = undef; $self->{send_gcode_file} = undef; $self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost)); @@ -1488,45 +1493,6 @@ sub do_print { my $filament_names = wxTheApp->{preset_bundle}->filament_presets; $filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats }; $printer_panel->load_print_job($self->{print_file}, $filament_stats); - - $self->GetFrame->select_tab(1); -} - -# Send $self->{send_gcode_file} to OctoPrint. -#FIXME Currently this call blocks the UI. Make it asynchronous. -sub send_gcode { - my ($self) = @_; - - $self->statusbar->StartBusy; - - my $ua = LWP::UserAgent->new; - $ua->timeout(180); - - my $res = $ua->post( - "http://" . $self->{config}->octoprint_host . "/api/files/local", - Content_Type => 'form-data', - 'X-Api-Key' => $self->{config}->octoprint_apikey, - Content => [ - file => [ - # On Windows, the path has to be encoded in local code page for perl to be able to open it. - Slic3r::encode_path($self->{send_gcode_file}), - # Remove the UTF-8 flag from the perl string, so the LWP::UserAgent can insert - # the UTF-8 encoded string into the request as a byte stream. - Slic3r::path_to_filename_raw($self->{send_gcode_file}) - ], - print => $self->{send_gcode_file_print} ? 1 : 0, - ], - ); - - $self->statusbar->StopBusy; - - if ($res->is_success) { - $self->statusbar->SetStatusText(L("G-code file successfully uploaded to the OctoPrint server")); - } else { - my $message = L("Error while uploading to the OctoPrint server: ") . $res->status_line; - Slic3r::GUI::show_error($self, $message); - $self->statusbar->SetStatusText($message); - } } sub export_stl { diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 06456d607..b73f8336d 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -201,6 +201,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/wxExtensions.hpp ${LIBDIR}/slic3r/Utils/Http.cpp ${LIBDIR}/slic3r/Utils/Http.hpp + ${LIBDIR}/slic3r/Utils/OctoPrint.cpp + ${LIBDIR}/slic3r/Utils/OctoPrint.hpp ) add_library(admesh STATIC @@ -340,6 +342,7 @@ set(XS_XSP_FILES ${XSP_DIR}/Surface.xsp ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp + ${XSP_DIR}/Utils_OctoPrint.xsp ${XSP_DIR}/XS.xsp ) foreach (file ${XS_XSP_FILES}) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 4077668f8..2b95ffa84 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -904,10 +904,17 @@ PrintConfigDef::PrintConfigDef() def->cli = "octoprint-apikey=s"; def->default_value = new ConfigOptionString(""); + def = this->add("octoprint_cafile", coString); + def->label = "HTTPS CA file"; + def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " + "If left blank, the default OS CA certificate repository is used."; + def->cli = "octoprint-cafile=s"; + def->default_value = new ConfigOptionString(""); + def = this->add("octoprint_host", coString); - def->label = L("Host or IP"); + def->label = L("Hostname, IP or URL"); def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain " - "the hostname or IP address of the OctoPrint instance."); + "the hostname, IP address or URL of the OctoPrint instance."); def->cli = "octoprint-host=s"; def->default_value = new ConfigOptionString(""); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 7d2fb0145..c589d917a 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -684,6 +684,7 @@ class HostConfig : public StaticPrintConfig public: ConfigOptionString octoprint_host; ConfigOptionString octoprint_apikey; + ConfigOptionString octoprint_cafile; ConfigOptionString serial_port; ConfigOptionInt serial_speed; @@ -692,6 +693,7 @@ protected: { OPT_PTR(octoprint_host); OPT_PTR(octoprint_apikey); + OPT_PTR(octoprint_cafile); OPT_PTR(serial_port); OPT_PTR(serial_speed); } diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index cb2ef7368..d7c9a590a 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -64,6 +64,7 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); REGISTER_CLASS(PresetHints, "GUI::PresetHints"); REGISTER_CLASS(TabIface, "GUI::Tab"); +REGISTER_CLASS(OctoPrint, "OctoPrint"); SV* ConfigBase__as_hash(ConfigBase* THIS) { diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index b0d3f7629..f053d7b16 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -5,9 +5,9 @@ #include #include #include - #include #include +#include #if __APPLE__ #import @@ -570,4 +570,18 @@ wxString from_u8(std::string str) return wxString::FromUTF8(str.c_str()); } +wxWindow *get_widget_by_id(int id) +{ + if (g_wxMainFrame == nullptr) { + throw std::runtime_error("Main frame not set"); + } + + wxWindow *window = g_wxMainFrame->FindWindow(id); + if (window == nullptr) { + throw std::runtime_error((boost::format("Could not find widget by ID: %1%") % id).str()); + } + + return window; +} + } } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 3e519b691..e913a9450 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -6,6 +6,7 @@ #include "Config.hpp" class wxApp; +class wxWindow; class wxFrame; class wxWindow; class wxMenuBar; @@ -118,6 +119,8 @@ wxString L_str(std::string str); // Return wxString from std::string in UTF8 wxString from_u8(std::string str); +wxWindow *get_widget_by_id(int id); + } } diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index c28c989fb..52717e1fc 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -224,7 +224,7 @@ const std::vector& Preset::printer_options() if (s_opts.empty()) { s_opts = { "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", - "octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", + "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_notes" }; diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp new file mode 100644 index 000000000..019a4e238 --- /dev/null +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -0,0 +1,105 @@ +#include "OctoPrint.hpp" + +#include +#include + +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "Http.hpp" + + +namespace Slic3r { + + +OctoPrint::OctoPrint(DynamicPrintConfig *config) : + host(config->opt_string("octoprint_host")), + apikey(config->opt_string("octoprint_apikey")), + cafile(config->opt_string("octoprint_cafile")) +{} + +std::string OctoPrint::test() const +{ + // Since the request is performed synchronously here, + // it is ok to refer to `res` from within the closure + std::string res; + + auto http = Http::get(std::move(make_url("api/version"))); + set_auth(http); + http.on_error([&](std::string, std::string error, unsigned status) { + res = format_error(error, status); + }) + .perform_sync(); + + return res; +} + +void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const +{ + auto http = Http::post(std::move(make_url("api/files/local"))); + set_auth(http); + http.form_add("print", print ? "true" : "false") + .form_add_file("file", filename) + .on_complete([=](std::string body, unsigned status) { + wxWindow *window = GUI::get_widget_by_id(windowId); + wxCommandEvent* evt = new wxCommandEvent(completeEvt); + evt->SetString("G-code file successfully uploaded to the OctoPrint server"); + evt->SetInt(100); + wxQueueEvent(window, evt); + }) + .on_error([=](std::string body, std::string error, unsigned status) { + wxWindow *window = GUI::get_widget_by_id(windowId); + + wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt); + evt_complete->SetInt(100); + wxQueueEvent(window, evt_complete); + + wxCommandEvent* evt_error = new wxCommandEvent(errorEvt); + evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status))); + wxQueueEvent(window, evt_error); + }) + .perform(); +} + +void OctoPrint::set_auth(Http &http) const +{ + http.header("X-Api-Key", apikey); + + if (! cafile.empty()) { + http.ca_file(cafile); + } +} + +std::string OctoPrint::make_url(const std::string &path) const +{ + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return std::move((boost::format("%1%%2%") % host % path).str()); + } else { + return std::move((boost::format("%1%/%2%") % host % path).str()); + } + } else { + return std::move((boost::format("http://%1%/%2%") % host % path).str()); + } +} + +std::string OctoPrint::format_error(std::string error, unsigned status) +{ + if (status != 0) { + std::string res{"HTTP "}; + res.append(std::to_string(status)); + + if (status == 401) { + res.append(": Invalid API key"); + } + + return std::move(res); + } else { + return std::move(error); + } +} + + +} diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp new file mode 100644 index 000000000..c5ed70ab5 --- /dev/null +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -0,0 +1,35 @@ +#ifndef slic3r_OctoPrint_hpp_ +#define slic3r_OctoPrint_hpp_ + +#include + +#include "Http.hpp" + +namespace Slic3r { + + +class DynamicPrintConfig; +class Http; + +class OctoPrint +{ +public: + OctoPrint(DynamicPrintConfig *config); + + std::string test() const; + // XXX: style + void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const; +private: + std::string host; + std::string apikey; + std::string cafile; + + void set_auth(Http &http) const; + std::string make_url(const std::string &path) const; + static std::string format_error(std::string error, unsigned status); +}; + + +} + +#endif diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp new file mode 100644 index 000000000..062af4e0c --- /dev/null +++ b/xs/xsp/Utils_OctoPrint.xsp @@ -0,0 +1,14 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "slic3r/Utils/OctoPrint.hpp" +%} + +%name{Slic3r::OctoPrint} class OctoPrint { + OctoPrint(DynamicPrintConfig *config); + ~OctoPrint(); + + std::string test() const; + void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const; +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index e54601632..87a8d8d86 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -236,6 +236,10 @@ Ref O_OBJECT_SLIC3R_T TabIface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T +OctoPrint* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV From fc05eb898de57bfbd845b5a32261b8702cbf0083 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 20 Feb 2018 15:37:26 +0100 Subject: [PATCH 7/9] WIP: Bonjour --- lib/Slic3r/GUI/MainFrame.pm | 3 +- xs/CMakeLists.txt | 2 + xs/src/slic3r/Utils/Bonjour.cpp | 602 ++++++++++++++++++++++++++++++ xs/src/slic3r/Utils/Bonjour.hpp | 39 ++ xs/src/slic3r/Utils/OctoPrint.cpp | 114 +++--- xs/src/slic3r/Utils/OctoPrint.hpp | 20 +- 6 files changed, 711 insertions(+), 69 deletions(-) create mode 100644 xs/src/slic3r/Utils/Bonjour.cpp create mode 100644 xs/src/slic3r/Utils/Bonjour.hpp diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 034cc2dd5..ee5ec7cb5 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -413,8 +413,7 @@ sub _init_menubar { my $hokusMenu = Wx::Menu->new; # XXX: tmp { $self->_append_menu_item($hokusMenu, "Pokus", "Pokus", sub { - # Slic3r::Http::download(); - Slic3r::OctoPrint::send_gcode("10.0.0.46", "70E4CFD0E0D7423CB6B1CF055DBAEFA5", "/home/vojta/prog/tisk/jesterka/jesterka.gcode"); + Slic3r::Http::pokus(); }); } diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index b73f8336d..d8ef51caa 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -203,6 +203,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/Utils/Http.hpp ${LIBDIR}/slic3r/Utils/OctoPrint.cpp ${LIBDIR}/slic3r/Utils/OctoPrint.hpp + ${LIBDIR}/slic3r/Utils/Bonjour.cpp + ${LIBDIR}/slic3r/Utils/Bonjour.hpp ) add_library(admesh STATIC diff --git a/xs/src/slic3r/Utils/Bonjour.cpp b/xs/src/slic3r/Utils/Bonjour.cpp new file mode 100644 index 000000000..d7fb30e64 --- /dev/null +++ b/xs/src/slic3r/Utils/Bonjour.cpp @@ -0,0 +1,602 @@ +#include "Bonjour.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using boost::optional; +using boost::system::error_code; +namespace endian = boost::endian; +namespace asio = boost::asio; +using boost::asio::ip::udp; + + +// TODO: Fuzzing test + +namespace Slic3r { + + +// Miniman implementation of a MDNS client +// This implementation is extremely simple, only the bits that are useful +// for very basic MDNS discovery are present. + +struct DnsName: public std::string +{ + enum + { + MAX_RECURSION = 10, // Keep this low + }; + + static optional decode(const std::vector &buffer, ptrdiff_t &offset, unsigned depth = 0) + { + // We trust that the offset passed is bounds-checked properly, + // including that there is at least one byte beyond that offset. + // Any further arithmetic has to be bounds-checked here though. + + // Check for recursion depth to prevent parsing names that are nested too deeply + // or end up cyclic: + if (depth >= MAX_RECURSION) { + return boost::none; + } + + DnsName res; + const ptrdiff_t bsize = buffer.size(); + + while (true) { + const char* ptr = buffer.data() + offset; + char len = *ptr; + if (len & 0xc0) { + // This is a recursive label + ptrdiff_t pointer = (len & 0x3f) << 8 | ptr[1]; + const auto nested = decode(buffer, pointer, depth + 1); + if (!nested) { + return boost::none; + } else { + if (res.size() > 0) { + res.push_back('.'); + } + res.append(*nested); + offset += 2; + return std::move(res); + } + } else if (len == 0) { + // This is a name terminator + offset++; + break; + } else { + // This is a regular label + len &= 0x3f; + if (len + offset + 1 >= bsize) { + return boost::none; + } + + res.reserve(len); + if (res.size() > 0) { + res.push_back('.'); + } + + ptr++; + for (const auto end = ptr + len; ptr < end; ptr++) { + char c = *ptr; + if (c >= 0x20 && c <= 0x7f) { + res.push_back(c); + } else { + return boost::none; + } + } + + offset += len + 1; + } + } + + if (res.size() > 0) { + return std::move(res); + } else { + return boost::none; + } + } +}; + +struct DnsHeader +{ + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; + + enum + { + SIZE = 12, + }; + + static DnsHeader decode(const std::vector &buffer) { + DnsHeader res; + const uint16_t *data_16 = reinterpret_cast(buffer.data()); + res.id = endian::big_to_native(data_16[0]); + res.flags = endian::big_to_native(data_16[1]); + res.qdcount = endian::big_to_native(data_16[2]); + res.ancount = endian::big_to_native(data_16[3]); + res.nscount = endian::big_to_native(data_16[4]); + res.arcount = endian::big_to_native(data_16[5]); + return res; + } + + uint32_t rrcount() const { + return ancount + nscount + arcount; + } +}; + +struct DnsQuestion +{ + enum + { + MIN_SIZE = 5, + }; + + DnsName name; + uint16_t type; + uint16_t qclass; + + DnsQuestion() : + type(0), + qclass(0) + {} + + static optional decode(const std::vector &buffer, ptrdiff_t &offset) + { + auto qname = DnsName::decode(buffer, offset); + if (!qname) { + return boost::none; + } + + DnsQuestion res; + res.name = std::move(*qname); + const uint16_t *data_16 = reinterpret_cast(buffer.data() + offset); + res.type = endian::big_to_native(data_16[0]); + res.qclass = endian::big_to_native(data_16[1]); + + offset += 4; + return std::move(res); + } +}; + +struct DnsResource +{ + DnsName name; + uint16_t type; + uint16_t rclass; + uint32_t ttl; + std::vector data; + + DnsResource() : + type(0), + rclass(0), + ttl(0) + {} + + static optional decode(const std::vector &buffer, ptrdiff_t &offset, ptrdiff_t &dataoffset) + { + auto rname = DnsName::decode(buffer, offset); + if (!rname) { + return boost::none; + } + + const ptrdiff_t bsize = buffer.size(); + + if (offset + 10 >= bsize) { + return boost::none; + } + + DnsResource res; + res.name = std::move(*rname); + const uint16_t *data_16 = reinterpret_cast(buffer.data() + offset); + res.type = endian::big_to_native(data_16[0]); + res.rclass = endian::big_to_native(data_16[1]); + res.ttl = endian::big_to_native(*reinterpret_cast(data_16 + 2)); + uint16_t rdlength = endian::big_to_native(data_16[4]); + + offset += 10; + if (offset + rdlength > bsize) { + return boost::none; + } + + dataoffset = offset; + res.data = std::move(std::vector(buffer.begin() + offset, buffer.begin() + offset + rdlength)); + offset += rdlength; + + return std::move(res); + } +}; + +struct DnsRR_A +{ + enum { TAG = 0x1 }; + + asio::ip::address_v4 ip; + + static void decode(optional &result, const DnsResource &rr) + { + if (rr.data.size() == 4) { + DnsRR_A res; + const uint32_t ip = endian::big_to_native(*reinterpret_cast(rr.data.data())); + res.ip = asio::ip::address_v4(ip); + result = std::move(res); + } + } +}; + +struct DnsRR_AAAA +{ + enum { TAG = 0x1c }; + + asio::ip::address_v6 ip; + + static void decode(optional &result, const DnsResource &rr) + { + if (rr.data.size() == 16) { + DnsRR_AAAA res; + std::array ip; + std::copy_n(rr.data.begin(), 16, ip.begin()); + res.ip = asio::ip::address_v6(ip); + result = std::move(res); + } + } +}; + +struct DnsRR_SRV +{ + enum + { + TAG = 0x21, + MIN_SIZE = 8, + }; + + uint16_t priority; + uint16_t weight; + uint16_t port; + std::string name; + std::string service; + DnsName hostname; + + static void decode(std::vector &results, const std::vector &buffer, const DnsResource &rr, ptrdiff_t dataoffset) + { + if (rr.data.size() < MIN_SIZE) { + return; + } + + DnsRR_SRV res; + + { + const auto dot_pos = rr.name.find_first_of('.'); + if (dot_pos > 0 && dot_pos < rr.name.size() - 1) { + res.name = rr.name.substr(0, dot_pos); + res.service = rr.name.substr(dot_pos + 1); + } else { + return; + } + } + + const uint16_t *data_16 = reinterpret_cast(rr.data.data()); + res.priority = endian::big_to_native(data_16[0]); + res.weight = endian::big_to_native(data_16[1]); + res.port = endian::big_to_native(data_16[2]); + + ptrdiff_t offset = dataoffset + 6; + auto hostname = DnsName::decode(buffer, offset); + + if (hostname) { + res.hostname = std::move(*hostname); + results.emplace_back(std::move(res)); + } + } +}; + +struct DnsMessage +{ + enum + { + MAX_SIZE = 4096, + MAX_ANS = 30, + }; + + DnsHeader header; + optional question; + std::vector answers; + + optional rr_a; + optional rr_aaaa; + std::vector rr_srv; + + static optional decode(const std::vector &buffer, optional id_wanted = boost::none) + { + const auto size = buffer.size(); + if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) { + return boost::none; + } + + DnsMessage res; + res.header = DnsHeader::decode(buffer); + + if (id_wanted && *id_wanted != res.header.id) { + return boost::none; + } + + if (res.header.qdcount > 1 || res.header.ancount > MAX_ANS) { + return boost::none; + } + + ptrdiff_t offset = DnsHeader::SIZE; + if (res.header.qdcount == 1) { + res.question = DnsQuestion::decode(buffer, offset); + } + + for (unsigned i = 0; i < res.header.rrcount(); i++) { + ptrdiff_t dataoffset = 0; + auto rr = DnsResource::decode(buffer, offset, dataoffset); + if (!rr) { + return boost::none; + } else { + res.parse_rr(buffer, *rr, dataoffset); + res.answers.push_back(std::move(*rr)); + } + } + + return std::move(res); + } +private: + void parse_rr(const std::vector &buffer, const DnsResource &rr, ptrdiff_t dataoffset) + { + switch (rr.type) { + case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break; + case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break; + case DnsRR_SRV::TAG: DnsRR_SRV::decode(this->rr_srv, buffer, rr, dataoffset); break; + } + } +}; + + +struct BonjourRequest +{ + static const asio::ip::address_v4 MCAST_IP4; + static const uint16_t MCAST_PORT; + + static const char rq_template[]; + + uint16_t id; + std::vector data; + + static optional make(const std::string &service, const std::string &protocol); + +private: + BonjourRequest(uint16_t id, std::vector &&data) : + id(id), + data(std::move(data)) + {} +}; + +const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb}; +const uint16_t BonjourRequest::MCAST_PORT = 5353; + +const char BonjourRequest::rq_template[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x5f, 0x68, 0x74, + 0x74, 0x70, 0x04, 0x5f, 0x74, 0x63, 0x70, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, + 0x00, 0x01, +}; + +optional BonjourRequest::make(const std::string &service, const std::string &protocol) +{ + if (service.size() > 15 || protocol.size() > 15) { + return boost::none; + } + + std::random_device dev; + std::uniform_int_distribution dist; + uint16_t id = dist(dev); + uint16_t id_big = endian::native_to_big(id); + const char *id_char = reinterpret_cast(&id_big); + + std::vector data; + data.reserve(service.size() + 18); + + // Add the transaction ID + data.push_back(id_char[0]); + data.push_back(id_char[1]); + + // Add metadata + static const char rq_meta[] = { + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data)); + + // Add PTR query name + data.push_back(service.size() + 1); + data.push_back('_'); + data.insert(data.end(), service.begin(), service.end()); + data.push_back(protocol.size() + 1); + data.push_back('_'); + data.insert(data.end(), protocol.begin(), protocol.end()); + + // Add the rest of PTR record + static const char ptr_tail[] = { + 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0x01, + }; + std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data)); + + return BonjourRequest(id, std::move(data)); +} + + +// API - private part + +struct Bonjour::priv +{ + const std::string service; + const std::string protocol; + const std::string service_dn; + unsigned timeout; + uint16_t rq_id; + + std::vector buffer; + std::thread io_thread; + Bonjour::ReplyFn replyfn; + Bonjour::CompleteFn completefn; + + priv(std::string service, std::string protocol); + + void udp_receive(const error_code &error, size_t bytes); + void lookup_perform(); +}; + +Bonjour::priv::priv(std::string service, std::string protocol) : + service(std::move(service)), + protocol(std::move(protocol)), + service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()), + timeout(10), + rq_id(0) +{ + buffer.resize(DnsMessage::MAX_SIZE); +} + +void Bonjour::priv::udp_receive(const error_code &error, size_t bytes) +{ + if (error || bytes == 0 || !replyfn) { + return; + } + + buffer.resize(bytes); + const auto dns_msg = DnsMessage::decode(buffer, rq_id); + if (dns_msg) { + std::string ip; + if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip.to_string(); } + else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip.to_string(); } + + for (const auto &srv : dns_msg->rr_srv) { + if (srv.service == service_dn) { + replyfn(std::move(ip), std::move(srv.hostname), std::move(srv.name)); + } + } + } +} + +void Bonjour::priv::lookup_perform() +{ + const auto brq = BonjourRequest::make(service, protocol); + if (!brq) { + return; + } + + auto self = this; + rq_id = brq->id; + + try { + boost::asio::io_service io_service; + udp::socket socket(io_service); + socket.open(udp::v4()); + socket.set_option(udp::socket::reuse_address(true)); + udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT); + socket.send_to(asio::buffer(brq->data), mcast); + + bool timeout = false; + asio::deadline_timer timer(io_service); + timer.expires_from_now(boost::posix_time::seconds(10)); + timer.async_wait([=, &timeout](const error_code &error) { + timeout = true; + if (self->completefn) { + self->completefn(); + } + }); + + const auto recv_handler = [=](const error_code &error, size_t bytes) { + self->udp_receive(error, bytes); + }; + socket.async_receive(asio::buffer(buffer, buffer.size()), recv_handler); + + while (io_service.run_one()) { + if (timeout) { + socket.cancel(); + } else { + buffer.resize(DnsMessage::MAX_SIZE); + socket.async_receive(asio::buffer(buffer, buffer.size()), recv_handler); + } + } + } catch (std::exception& e) { + } +} + + +// API - public part + +Bonjour::Bonjour(std::string service, std::string protocol) : + p(new priv(std::move(service), std::move(protocol))) +{} + +Bonjour::Bonjour(Bonjour &&other) : p(std::move(other.p)) {} + +Bonjour::~Bonjour() +{ + if (p && p->io_thread.joinable()) { + p->io_thread.detach(); + } +} + +Bonjour& Bonjour::set_timeout(unsigned timeout) +{ + if (p) { p->timeout = timeout; } + return *this; +} + +Bonjour& Bonjour::on_reply(ReplyFn fn) +{ + if (p) { p->replyfn = std::move(fn); } + return *this; +} + +Bonjour& Bonjour::on_complete(CompleteFn fn) +{ + if (p) { p->completefn = std::move(fn); } + return *this; +} + +Bonjour::Ptr Bonjour::lookup() +{ + auto self = std::make_shared(std::move(*this)); + + if (self->p) { + auto io_thread = std::thread([self](){ + self->p->lookup_perform(); + }); + self->p->io_thread = std::move(io_thread); + } + + return self; +} + + +void Bonjour::pokus() // XXX +{ + // auto bonjour = Bonjour("http") + // .set_timeout(15) + // .on_reply([](std::string ip, std::string host, std::string service_name) { + // std::cerr << "MDNS: " << ip << " = " << host << " : " << service_name << std::endl; + // }) + // .on_complete([](){ + // std::cerr << "MDNS lookup complete" << std::endl; + // }) + // .lookup(); +} + + +} diff --git a/xs/src/slic3r/Utils/Bonjour.hpp b/xs/src/slic3r/Utils/Bonjour.hpp new file mode 100644 index 000000000..039db3370 --- /dev/null +++ b/xs/src/slic3r/Utils/Bonjour.hpp @@ -0,0 +1,39 @@ +#ifndef slic3r_Bonjour_hpp_ +#define slic3r_Bonjour_hpp_ + +#include +#include +#include + + +namespace Slic3r { + + +/// Bonjour lookup +class Bonjour : public std::enable_shared_from_this { +private: + struct priv; +public: + typedef std::shared_ptr Ptr; + typedef std::function ReplyFn; + typedef std::function CompleteFn; + + Bonjour(std::string service, std::string protocol = "tcp"); + Bonjour(Bonjour &&other); + ~Bonjour(); + + Bonjour& set_timeout(unsigned timeout); + Bonjour& on_reply(ReplyFn fn); + Bonjour& on_complete(CompleteFn fn); + + Ptr lookup(); + + static void pokus(); // XXX: remove +private: + std::unique_ptr p; +}; + + +} + +#endif diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp index 019a4e238..58530833b 100644 --- a/xs/src/slic3r/Utils/OctoPrint.cpp +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -15,90 +15,90 @@ namespace Slic3r { OctoPrint::OctoPrint(DynamicPrintConfig *config) : - host(config->opt_string("octoprint_host")), - apikey(config->opt_string("octoprint_apikey")), - cafile(config->opt_string("octoprint_cafile")) + host(config->opt_string("octoprint_host")), + apikey(config->opt_string("octoprint_apikey")), + cafile(config->opt_string("octoprint_cafile")) {} std::string OctoPrint::test() const { - // Since the request is performed synchronously here, - // it is ok to refer to `res` from within the closure - std::string res; + // Since the request is performed synchronously here, + // it is ok to refer to `res` from within the closure + std::string res; - auto http = Http::get(std::move(make_url("api/version"))); - set_auth(http); - http.on_error([&](std::string, std::string error, unsigned status) { - res = format_error(error, status); - }) - .perform_sync(); + auto http = Http::get(std::move(make_url("api/version"))); + set_auth(http); + http.on_error([&](std::string, std::string error, unsigned status) { + res = format_error(error, status); + }) + .perform_sync(); - return res; + return res; } void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const { - auto http = Http::post(std::move(make_url("api/files/local"))); - set_auth(http); - http.form_add("print", print ? "true" : "false") - .form_add_file("file", filename) - .on_complete([=](std::string body, unsigned status) { - wxWindow *window = GUI::get_widget_by_id(windowId); - wxCommandEvent* evt = new wxCommandEvent(completeEvt); - evt->SetString("G-code file successfully uploaded to the OctoPrint server"); - evt->SetInt(100); - wxQueueEvent(window, evt); - }) - .on_error([=](std::string body, std::string error, unsigned status) { - wxWindow *window = GUI::get_widget_by_id(windowId); + auto http = Http::post(std::move(make_url("api/files/local"))); + set_auth(http); + http.form_add("print", print ? "true" : "false") + .form_add_file("file", filename) + .on_complete([=](std::string body, unsigned status) { + wxWindow *window = GUI::get_widget_by_id(windowId); + wxCommandEvent* evt = new wxCommandEvent(completeEvt); + evt->SetString("G-code file successfully uploaded to the OctoPrint server"); + evt->SetInt(100); + wxQueueEvent(window, evt); + }) + .on_error([=](std::string body, std::string error, unsigned status) { + wxWindow *window = GUI::get_widget_by_id(windowId); - wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt); - evt_complete->SetInt(100); - wxQueueEvent(window, evt_complete); + wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt); + evt_complete->SetInt(100); + wxQueueEvent(window, evt_complete); - wxCommandEvent* evt_error = new wxCommandEvent(errorEvt); - evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status))); - wxQueueEvent(window, evt_error); - }) - .perform(); + wxCommandEvent* evt_error = new wxCommandEvent(errorEvt); + evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status))); + wxQueueEvent(window, evt_error); + }) + .perform(); } void OctoPrint::set_auth(Http &http) const { - http.header("X-Api-Key", apikey); + http.header("X-Api-Key", apikey); - if (! cafile.empty()) { - http.ca_file(cafile); - } + if (! cafile.empty()) { + http.ca_file(cafile); + } } std::string OctoPrint::make_url(const std::string &path) const { - if (host.find("http://") == 0 || host.find("https://") == 0) { - if (host.back() == '/') { - return std::move((boost::format("%1%%2%") % host % path).str()); - } else { - return std::move((boost::format("%1%/%2%") % host % path).str()); - } - } else { - return std::move((boost::format("http://%1%/%2%") % host % path).str()); - } + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return std::move((boost::format("%1%%2%") % host % path).str()); + } else { + return std::move((boost::format("%1%/%2%") % host % path).str()); + } + } else { + return std::move((boost::format("http://%1%/%2%") % host % path).str()); + } } std::string OctoPrint::format_error(std::string error, unsigned status) { - if (status != 0) { - std::string res{"HTTP "}; - res.append(std::to_string(status)); + if (status != 0) { + std::string res{"HTTP "}; + res.append(std::to_string(status)); - if (status == 401) { - res.append(": Invalid API key"); - } + if (status == 401) { + res.append(": Invalid API key"); + } - return std::move(res); - } else { - return std::move(error); - } + return std::move(res); + } else { + return std::move(error); + } } diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp index c5ed70ab5..1d70a087d 100644 --- a/xs/src/slic3r/Utils/OctoPrint.hpp +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -14,19 +14,19 @@ class Http; class OctoPrint { public: - OctoPrint(DynamicPrintConfig *config); + OctoPrint(DynamicPrintConfig *config); - std::string test() const; - // XXX: style - void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const; + std::string test() const; + // XXX: style + void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const; private: - std::string host; - std::string apikey; - std::string cafile; + std::string host; + std::string apikey; + std::string cafile; - void set_auth(Http &http) const; - std::string make_url(const std::string &path) const; - static std::string format_error(std::string error, unsigned status); + void set_auth(Http &http) const; + std::string make_url(const std::string &path) const; + static std::string format_error(std::string error, unsigned status); }; From ca0f6131a18ea7fa089fb4eea979c9b3f87703a0 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 2 Mar 2018 15:05:17 +0100 Subject: [PATCH 8/9] WIP: Bonjour: TXT + improvements --- xs/src/slic3r/Utils/Bonjour.cpp | 240 +++++++++++++++++++++--------- xs/src/slic3r/Utils/Bonjour.hpp | 21 ++- xs/src/slic3r/Utils/OctoPrint.hpp | 2 +- 3 files changed, 191 insertions(+), 72 deletions(-) diff --git a/xs/src/slic3r/Utils/Bonjour.cpp b/xs/src/slic3r/Utils/Bonjour.cpp index d7fb30e64..6107e2c60 100644 --- a/xs/src/slic3r/Utils/Bonjour.cpp +++ b/xs/src/slic3r/Utils/Bonjour.cpp @@ -1,5 +1,6 @@ #include "Bonjour.hpp" +#include // XXX #include #include #include @@ -22,12 +23,14 @@ namespace asio = boost::asio; using boost::asio::ip::udp; -// TODO: Fuzzing test +// TODO: Fuzzing test (done without TXT) +// FIXME: check char retype to unsigned + namespace Slic3r { -// Miniman implementation of a MDNS client +// Minimal implementation of a MDNS/DNS-SD client // This implementation is extremely simple, only the bits that are useful // for very basic MDNS discovery are present. @@ -38,11 +41,12 @@ struct DnsName: public std::string MAX_RECURSION = 10, // Keep this low }; - static optional decode(const std::vector &buffer, ptrdiff_t &offset, unsigned depth = 0) + static optional decode(const std::vector &buffer, size_t &offset, unsigned depth = 0) { - // We trust that the offset passed is bounds-checked properly, - // including that there is at least one byte beyond that offset. - // Any further arithmetic has to be bounds-checked here though. + // Check offset sanity: + if (offset + 1 >= buffer.size()) { + return boost::none; + } // Check for recursion depth to prevent parsing names that are nested too deeply // or end up cyclic: @@ -51,14 +55,15 @@ struct DnsName: public std::string } DnsName res; - const ptrdiff_t bsize = buffer.size(); + const size_t bsize = buffer.size(); while (true) { const char* ptr = buffer.data() + offset; - char len = *ptr; + unsigned len = static_cast(*ptr); if (len & 0xc0) { // This is a recursive label - ptrdiff_t pointer = (len & 0x3f) << 8 | ptr[1]; + unsigned len_2 = static_cast(ptr[1]); + size_t pointer = (len & 0x3f) << 8 | len_2; const auto nested = decode(buffer, pointer, depth + 1); if (!nested) { return boost::none; @@ -155,7 +160,7 @@ struct DnsQuestion qclass(0) {} - static optional decode(const std::vector &buffer, ptrdiff_t &offset) + static optional decode(const std::vector &buffer, size_t &offset) { auto qname = DnsName::decode(buffer, offset); if (!qname) { @@ -187,15 +192,18 @@ struct DnsResource ttl(0) {} - static optional decode(const std::vector &buffer, ptrdiff_t &offset, ptrdiff_t &dataoffset) + static optional decode(const std::vector &buffer, size_t &offset, size_t &dataoffset) { + const size_t bsize = buffer.size(); + if (offset + 1 >= bsize) { + return boost::none; + } + auto rname = DnsName::decode(buffer, offset); if (!rname) { return boost::none; } - const ptrdiff_t bsize = buffer.size(); - if (offset + 10 >= bsize) { return boost::none; } @@ -267,39 +275,98 @@ struct DnsRR_SRV uint16_t priority; uint16_t weight; uint16_t port; - std::string name; - std::string service; DnsName hostname; - static void decode(std::vector &results, const std::vector &buffer, const DnsResource &rr, ptrdiff_t dataoffset) + static optional decode(const std::vector &buffer, const DnsResource &rr, size_t dataoffset) { if (rr.data.size() < MIN_SIZE) { - return; + return boost::none; } DnsRR_SRV res; - { - const auto dot_pos = rr.name.find_first_of('.'); - if (dot_pos > 0 && dot_pos < rr.name.size() - 1) { - res.name = rr.name.substr(0, dot_pos); - res.service = rr.name.substr(dot_pos + 1); - } else { - return; - } - } - const uint16_t *data_16 = reinterpret_cast(rr.data.data()); res.priority = endian::big_to_native(data_16[0]); res.weight = endian::big_to_native(data_16[1]); res.port = endian::big_to_native(data_16[2]); - ptrdiff_t offset = dataoffset + 6; + size_t offset = dataoffset + 6; auto hostname = DnsName::decode(buffer, offset); if (hostname) { res.hostname = std::move(*hostname); - results.emplace_back(std::move(res)); + return std::move(res); + } else { + return boost::none; + } + } +}; + +struct DnsRR_TXT +{ + enum + { + TAG = 0x10, + }; + + std::vector values; + + static optional decode(const DnsResource &rr) + { + const size_t size = rr.data.size(); + if (size < 2) { + return boost::none; + } + + DnsRR_TXT res; + + for (auto it = rr.data.begin(); it != rr.data.end(); ) { + unsigned val_size = static_cast(*it); + if (val_size == 0 || it + val_size >= rr.data.end()) { + return boost::none; + } + ++it; + + std::string value(val_size, ' '); + std::copy(it, it + val_size, value.begin()); + res.values.push_back(std::move(value)); + + it += val_size; + } + + return std::move(res); + } +}; + +struct DnsSDPair +{ + optional srv; + optional txt; +}; + +struct DnsSDMap : public std::map +{ + void insert_srv(std::string &&name, DnsRR_SRV &&srv) + { + auto hit = this->find(name); + if (hit != this->end()) { + hit->second.srv = std::move(srv); + } else { + DnsSDPair pair; + pair.srv = std::move(srv); + this->insert(std::make_pair(std::move(name), std::move(pair))); + } + } + + void insert_txt(std::string &&name, DnsRR_TXT &&txt) + { + auto hit = this->find(name); + if (hit != this->end()) { + hit->second.txt = std::move(txt); + } else { + DnsSDPair pair; + pair.txt = std::move(txt); + this->insert(std::make_pair(std::move(name), std::move(pair))); } } }; @@ -314,12 +381,13 @@ struct DnsMessage DnsHeader header; optional question; - std::vector answers; optional rr_a; optional rr_aaaa; std::vector rr_srv; + DnsSDMap sdmap; + static optional decode(const std::vector &buffer, optional id_wanted = boost::none) { const auto size = buffer.size(); @@ -338,31 +406,39 @@ struct DnsMessage return boost::none; } - ptrdiff_t offset = DnsHeader::SIZE; + size_t offset = DnsHeader::SIZE; if (res.header.qdcount == 1) { res.question = DnsQuestion::decode(buffer, offset); } for (unsigned i = 0; i < res.header.rrcount(); i++) { - ptrdiff_t dataoffset = 0; + size_t dataoffset = 0; auto rr = DnsResource::decode(buffer, offset, dataoffset); if (!rr) { return boost::none; } else { - res.parse_rr(buffer, *rr, dataoffset); - res.answers.push_back(std::move(*rr)); + res.parse_rr(buffer, std::move(*rr), dataoffset); } } return std::move(res); } private: - void parse_rr(const std::vector &buffer, const DnsResource &rr, ptrdiff_t dataoffset) + void parse_rr(const std::vector &buffer, DnsResource &&rr, size_t dataoffset) { switch (rr.type) { case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break; case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break; - case DnsRR_SRV::TAG: DnsRR_SRV::decode(this->rr_srv, buffer, rr, dataoffset); break; + case DnsRR_SRV::TAG: { + auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset); + if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); } + break; + } + case DnsRR_TXT::TAG: { + auto txt = DnsRR_TXT::decode(rr); + if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); } + break; + } } } }; @@ -373,8 +449,6 @@ struct BonjourRequest static const asio::ip::address_v4 MCAST_IP4; static const uint16_t MCAST_PORT; - static const char rq_template[]; - uint16_t id; std::vector data; @@ -390,12 +464,6 @@ private: const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb}; const uint16_t BonjourRequest::MCAST_PORT = 5353; -const char BonjourRequest::rq_template[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x5f, 0x68, 0x74, - 0x74, 0x70, 0x04, 0x5f, 0x74, 0x63, 0x70, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, - 0x00, 0x01, -}; - optional BonjourRequest::make(const std::string &service, const std::string &protocol) { if (service.size() > 15 || protocol.size() > 15) { @@ -416,7 +484,7 @@ optional BonjourRequest::make(const std::string &service, const data.push_back(id_char[1]); // Add metadata - static const char rq_meta[] = { + static const unsigned char rq_meta[] = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data)); @@ -430,8 +498,8 @@ optional BonjourRequest::make(const std::string &service, const data.insert(data.end(), protocol.begin(), protocol.end()); // Add the rest of PTR record - static const char ptr_tail[] = { - 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0x01, + static const unsigned char ptr_tail[] = { + 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0xff, }; std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data)); @@ -456,7 +524,7 @@ struct Bonjour::priv priv(std::string service, std::string protocol); - void udp_receive(const error_code &error, size_t bytes); + void udp_receive(udp::endpoint from, size_t bytes); void lookup_perform(); }; @@ -470,23 +538,41 @@ Bonjour::priv::priv(std::string service, std::string protocol) : buffer.resize(DnsMessage::MAX_SIZE); } -void Bonjour::priv::udp_receive(const error_code &error, size_t bytes) +void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes) { - if (error || bytes == 0 || !replyfn) { + if (bytes == 0 || !replyfn) { return; } buffer.resize(bytes); const auto dns_msg = DnsMessage::decode(buffer, rq_id); if (dns_msg) { - std::string ip; - if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip.to_string(); } - else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip.to_string(); } + asio::ip::address ip = from.address(); + if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; } + else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; } - for (const auto &srv : dns_msg->rr_srv) { - if (srv.service == service_dn) { - replyfn(std::move(ip), std::move(srv.hostname), std::move(srv.name)); + for (const auto &sdpair : dns_msg->sdmap) { + if (! sdpair.second.srv) { + continue; } + + const auto &srv = *sdpair.second.srv; + BonjourReply reply(ip, sdpair.first, srv.hostname); + + if (sdpair.second.txt) { + static const std::string tag_path = "path="; + static const std::string tag_version = "version="; + + for (const auto &value : sdpair.second.txt->values) { + if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) { + reply.path = value.substr(tag_path.size()); + } else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) { + reply.version = value.substr(tag_version.size()); + } + } + } + + replyfn(std::move(reply)); } } } @@ -519,17 +605,18 @@ void Bonjour::priv::lookup_perform() } }); - const auto recv_handler = [=](const error_code &error, size_t bytes) { - self->udp_receive(error, bytes); + udp::endpoint recv_from; + const auto recv_handler = [&](const error_code &error, size_t bytes) { + if (!error) { self->udp_receive(recv_from, bytes); } }; - socket.async_receive(asio::buffer(buffer, buffer.size()), recv_handler); + socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler); while (io_service.run_one()) { if (timeout) { socket.cancel(); } else { buffer.resize(DnsMessage::MAX_SIZE); - socket.async_receive(asio::buffer(buffer, buffer.size()), recv_handler); + socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler); } } } catch (std::exception& e) { @@ -539,6 +626,21 @@ void Bonjour::priv::lookup_perform() // API - public part +BonjourReply::BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname) : + ip(std::move(ip)), + service_name(std::move(service_name)), + hostname(std::move(hostname)), + path("/"), + version("Unknown") +{} + +std::ostream& operator<<(std::ostream &os, const BonjourReply &reply) +{ + os << "BonjourReply(" << reply.ip.to_string() << ", " << reply.service_name << ", " + << reply.hostname << ", " << reply.path << ", " << reply.version << ")"; + return os; +} + Bonjour::Bonjour(std::string service, std::string protocol) : p(new priv(std::move(service), std::move(protocol))) {} @@ -587,15 +689,15 @@ Bonjour::Ptr Bonjour::lookup() void Bonjour::pokus() // XXX { - // auto bonjour = Bonjour("http") - // .set_timeout(15) - // .on_reply([](std::string ip, std::string host, std::string service_name) { - // std::cerr << "MDNS: " << ip << " = " << host << " : " << service_name << std::endl; - // }) - // .on_complete([](){ - // std::cerr << "MDNS lookup complete" << std::endl; - // }) - // .lookup(); + auto bonjour = Bonjour("octoprint") + .set_timeout(15) + .on_reply([](BonjourReply &&reply) { + std::cerr << "BonjourReply: " << reply << std::endl; + }) + .on_complete([](){ + std::cerr << "MDNS lookup complete" << std::endl; + }) + .lookup(); } diff --git a/xs/src/slic3r/Utils/Bonjour.hpp b/xs/src/slic3r/Utils/Bonjour.hpp index 039db3370..285625c04 100644 --- a/xs/src/slic3r/Utils/Bonjour.hpp +++ b/xs/src/slic3r/Utils/Bonjour.hpp @@ -4,18 +4,35 @@ #include #include #include +// #include +#include namespace Slic3r { -/// Bonjour lookup +// TODO: reply data structure +struct BonjourReply +{ + boost::asio::ip::address ip; + std::string service_name; + std::string hostname; + std::string path; + std::string version; + + BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname); +}; + +std::ostream& operator<<(std::ostream &, const BonjourReply &); + + +/// Bonjour lookup performer class Bonjour : public std::enable_shared_from_this { private: struct priv; public: typedef std::shared_ptr Ptr; - typedef std::function ReplyFn; + typedef std::function ReplyFn; typedef std::function CompleteFn; Bonjour(std::string service, std::string protocol = "tcp"); diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp index 1d70a087d..eca3baa63 100644 --- a/xs/src/slic3r/Utils/OctoPrint.hpp +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -3,7 +3,7 @@ #include -#include "Http.hpp" +// #include "Http.hpp" // XXX: ? namespace Slic3r { From e26ccfc2479ad373ab86d4c54d11c8e2194d19d8 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 6 Mar 2018 11:39:24 +0100 Subject: [PATCH 9/9] Fixed compilation on Windows, removed debugging menu and debugging output. --- lib/Slic3r/GUI/MainFrame.pm | 10 ---------- xs/CMakeLists.txt | 23 +++++++++++++++-------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index ee5ec7cb5..442d0abc9 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -174,7 +174,6 @@ sub _init_tabpanel { EVT_COMMAND($self, -1, $BUTTON_BROWSE_EVENT, sub { my ($self, $event) = @_; my $msg = $event->GetString; - print "BUTTON_BROWSE_EVENT: ", $msg, "\n"; # look for devices my $entries; @@ -197,7 +196,6 @@ sub _init_tabpanel { EVT_COMMAND($self, -1, $BUTTON_TEST_EVENT, sub { my ($self, $event) = @_; my $msg = $event->GetString; - print "BUTTON_TEST_EVENT: ", $msg, "\n"; my $ua = LWP::UserAgent->new; $ua->timeout(10); @@ -410,13 +408,6 @@ sub _init_menubar { }); } - my $hokusMenu = Wx::Menu->new; # XXX: tmp - { - $self->_append_menu_item($hokusMenu, "Pokus", "Pokus", sub { - Slic3r::Http::pokus(); - }); - } - # menubar # assign menubar to frame after appending items, otherwise special items # will not be handled correctly @@ -431,7 +422,6 @@ sub _init_menubar { # (Select application language from the list of installed languages) Slic3r::GUI::add_debug_menu($menubar, $self->{lang_ch_event}); $menubar->Append($helpMenu, L("&Help")); - $menubar->Append($hokusMenu, "Hoku&s"); # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing. $self->SetMenuBar($menubar); } diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index d8ef51caa..5f5771147 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -533,14 +533,21 @@ find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) target_link_libraries(XS ${CURL_LIBRARIES}) -if (SLIC3R_STATIC AND CMAKE_SYSTEM_NAME STREQUAL "Linux") - # As of now, our build system produces a statically linked libcurl, - # which links the OpenSSL library dynamically. - find_package(OpenSSL REQUIRED) - message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") - message("OpenSSL libraries: ${OPENSSL_LIBRARIES}") - include_directories(${OPENSSL_INCLUDE_DIR}) - target_link_libraries(XS ${OPENSSL_LIBRARIES}) +if (SLIC3R_STATIC) + if (NOT APPLE) + # libcurl is always linked dynamically to the system libcurl on OSX. + # On other systems, libcurl is linked statically if SLIC3R_STATIC is set. + add_definitions(-DCURL_STATICLIB) + endif() + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + # As of now, our build system produces a statically linked libcurl, + # which links the OpenSSL library dynamically. + find_package(OpenSSL REQUIRED) + message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") + message("OpenSSL libraries: ${OPENSSL_LIBRARIES}") + include_directories(${OPENSSL_INCLUDE_DIR}) + target_link_libraries(XS ${OPENSSL_LIBRARIES}) + endif() endif() ## OPTIONAL packages