From 861b2bc3c4b2b590ef5f536ddf8ccdf9185724e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 2 Aug 2026 19:20:07 -0300 Subject: [PATCH] Fix driver-dependent patterned border rasterization Snap dotted and dashed border geometry to physical pixels so centered patterns cannot begin on ambiguous half-pixel coordinates. This makes rectangular dot placement deterministic across llvmpipe and macOS GPU drivers. Update the old Reddit golden image to use the snapped dot phase. Optimize compile-time header dependencies - Extract lightweight singleton declarations. - Remove unnecessary core.hpp, stream, pack, and container includes. - Add explicit direct dependencies across system, network, graphics, and syntax headers. - Forward-declare stream and pack types where possible. - Normalize selected network headers to LF. --- .../html/eepp-ui-reddit-old-thread.webp | Bin 62934 -> 63024 bytes include/eepp/graphics/base.hpp | 2 +- include/eepp/graphics/drawableresource.hpp | 4 +- include/eepp/graphics/image.hpp | 11 +- include/eepp/graphics/ninepatch.hpp | 2 +- include/eepp/graphics/resource.hpp | 4 +- include/eepp/graphics/shaderprogram.hpp | 1 + include/eepp/graphics/systemfontresolver.hpp | 2 + include/eepp/graphics/texture.hpp | 3 +- include/eepp/network/ftp.hpp | 804 +++++++++--------- include/eepp/network/http.hpp | 6 +- include/eepp/network/ipaddress.hpp | 388 ++++----- include/eepp/network/socket.hpp | 240 +++--- include/eepp/network/socketselector.hpp | 336 ++++---- include/eepp/network/tcpsocket.hpp | 456 +++++----- include/eepp/network/uri.hpp | 6 +- include/eepp/system/condition.hpp | 2 +- include/eepp/system/filesystem.hpp | 4 +- include/eepp/system/log.hpp | 4 +- include/eepp/system/md5.hpp | 5 +- include/eepp/system/mutex.hpp | 2 +- include/eepp/system/pack.hpp | 1 + include/eepp/system/resourceloader.hpp | 3 +- include/eepp/system/singleton.hpp | 19 +- include/eepp/system/singletondeclarations.hpp | 24 + include/eepp/system/threadlocal.hpp | 2 +- include/eepp/system/translator.hpp | 6 +- include/eepp/ui/doc/syntaxcolorscheme.hpp | 9 +- include/eepp/ui/doc/syntaxdefinition.hpp | 2 + .../eepp/ui/doc/syntaxdefinitionmanager.hpp | 13 +- include/eepp/window/cursor.hpp | 3 +- include/eepp/window/platformimpl.hpp | 2 +- src/eepp/graphics/textureatlas.cpp | 1 + src/eepp/graphics/texturepackernode.cpp | 1 + src/eepp/network/uri.cpp | 1 + src/eepp/system/threadpool.cpp | 1 + src/eepp/system/virtualfilesystem.cpp | 1 + src/eepp/ui/doc/syntaxdefinitionmanager.cpp | 2 + src/eepp/ui/uiborderdrawable.cpp | 25 +- src/eepp/window/backend.hpp | 2 +- 40 files changed, 1236 insertions(+), 1164 deletions(-) create mode 100644 include/eepp/system/singletondeclarations.hpp diff --git a/bin/unit_tests/assets/html/eepp-ui-reddit-old-thread.webp b/bin/unit_tests/assets/html/eepp-ui-reddit-old-thread.webp index 8affeff14bf9258dea0d8e203443a191a1ebb18b..31db9e4957a850549f04490ff987d325f02c7753 100644 GIT binary patch literal 63024 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 ROa7ag@jrSO!$V*J{}1k0oLB$= literal 62934 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 kyT`S= +#include #include #include diff --git a/include/eepp/graphics/drawableresource.hpp b/include/eepp/graphics/drawableresource.hpp index 9a2754284..0141912a5 100644 --- a/include/eepp/graphics/drawableresource.hpp +++ b/include/eepp/graphics/drawableresource.hpp @@ -1,9 +1,11 @@ #ifndef EE_GRAPHICS_DRAWABLERESOURCE_HPP #define EE_GRAPHICS_DRAWABLERESOURCE_HPP -#include +#include #include +#include #include +#include #include namespace EE { namespace Graphics { diff --git a/include/eepp/graphics/image.hpp b/include/eepp/graphics/image.hpp index 97afec088..bceaf6e60 100644 --- a/include/eepp/graphics/image.hpp +++ b/include/eepp/graphics/image.hpp @@ -1,7 +1,8 @@ #ifndef EE_GRAPHICSCIMAGE_HPP #define EE_GRAPHICSCIMAGE_HPP -#include +#include +#include #include #include @@ -315,11 +316,11 @@ class EE_API Image { /** Overload the assignment operator to ensure the image copy */ Image& operator=( const Image& right ); - /** @brief Move constructor */ - Image( Image&& other ) noexcept; + /** @brief Move constructor */ + Image( Image&& other ) noexcept; - /** @brief Move assignment operator */ - Image& operator=( Image&& other ) noexcept; + /** @brief Move assignment operator */ + Image& operator=( Image&& other ) noexcept; virtual ~Image(); diff --git a/include/eepp/graphics/ninepatch.hpp b/include/eepp/graphics/ninepatch.hpp index 6c7102b1d..7adec70fd 100644 --- a/include/eepp/graphics/ninepatch.hpp +++ b/include/eepp/graphics/ninepatch.hpp @@ -1,7 +1,7 @@ #ifndef EE_GRAPHICS_NINEPATCH_HPP #define EE_GRAPHICS_NINEPATCH_HPP -#include +#include #include #include diff --git a/include/eepp/graphics/resource.hpp b/include/eepp/graphics/resource.hpp index ac603e37e..407bf1354 100644 --- a/include/eepp/graphics/resource.hpp +++ b/include/eepp/graphics/resource.hpp @@ -7,7 +7,9 @@ #include #include -#include +#include +#include +#include namespace EE { namespace Graphics { diff --git a/include/eepp/graphics/shaderprogram.hpp b/include/eepp/graphics/shaderprogram.hpp index 901c48408..46c277e17 100644 --- a/include/eepp/graphics/shaderprogram.hpp +++ b/include/eepp/graphics/shaderprogram.hpp @@ -1,6 +1,7 @@ #ifndef EE_GRAPHICSCSHADERPROGRAM_H #define EE_GRAPHICSCSHADERPROGRAM_H +#include #include #include diff --git a/include/eepp/graphics/systemfontresolver.hpp b/include/eepp/graphics/systemfontresolver.hpp index 358f1e9d1..bfd7bf551 100644 --- a/include/eepp/graphics/systemfontresolver.hpp +++ b/include/eepp/graphics/systemfontresolver.hpp @@ -2,6 +2,8 @@ #define EE_GRAPHICS_SYSTEMFONTRESOLVER_HPP #include +#include +#include #include #include #include diff --git a/include/eepp/graphics/texture.hpp b/include/eepp/graphics/texture.hpp index e3395ee79..b1dc5ea6c 100644 --- a/include/eepp/graphics/texture.hpp +++ b/include/eepp/graphics/texture.hpp @@ -1,8 +1,9 @@ #ifndef EE_GRAPHICSCTEXTURE_H #define EE_GRAPHICSCTEXTURE_H -#include +#include #include +#include #include #include #include diff --git a/include/eepp/network/ftp.hpp b/include/eepp/network/ftp.hpp index 7582fc6d9..773ad285a 100644 --- a/include/eepp/network/ftp.hpp +++ b/include/eepp/network/ftp.hpp @@ -1,402 +1,402 @@ -#ifndef EE_NETWORKCFTP_HPP -#define EE_NETWORKCFTP_HPP - -#include -#include -#include -#include -#include -#include - -using namespace EE::System; - -namespace EE { namespace Network { - -class IpAddress; - -/** @brief A FTP client */ -class EE_API Ftp : NonCopyable { - public: - /** @brief Enumeration of transfer modes */ - enum TransferMode { - Binary, ///< Binary mode (file is transferred as a sequence of bytes) - Ascii, ///< Text mode using ASCII encoding - Ebcdic ///< Text mode using EBCDIC encoding - }; - - /** @brief Define a FTP response */ - class EE_API Response { - public: - /** @brief Status codes possibly returned by a FTP response */ - enum Status { - // 1xx: the requested action is being initiated, - // expect another reply before proceeding with a new command - RestartMarkerReply = 110, ///< Restart marker reply - ServiceReadySoon = 120, ///< Service ready in N minutes - DataConnectionAlreadyOpened = - 125, ///< Data connection already opened, transfer starting - OpeningDataConnection = 150, ///< File status ok, about to open data connection - - // 2xx: the requested action has been successfully completed - Ok = 200, ///< Command ok - PointlessCommand = 202, ///< Command not implemented - SystemStatus = 211, ///< System status, or system help reply - DirectoryStatus = 212, ///< Directory status - FileStatus = 213, ///< File status - HelpMessage = 214, ///< Help message - SystemType = 215, ///< NAME system type, where NAME is an official system name from the - ///< list in the Assigned Numbers document - ServiceReady = 220, ///< Service ready for new user - ClosingConnection = 221, ///< Service closing control connection - DataConnectionOpened = 225, ///< Data connection open, no transfer in progress - ClosingDataConnection = - 226, ///< Closing data connection, requested file action successful - EnteringPassiveMode = 227, ///< Entering passive mode - LoggedIn = 230, ///< User logged in, proceed. Logged out if appropriate - FileActionOk = 250, ///< Requested file action ok - DirectoryOk = 257, ///< PATHNAME created - - // 3xx: the command has been accepted, but the requested action - // is dormant, pending receipt of further information - NeedPassword = 331, ///< User name ok, need password - NeedAccountToLogIn = 332, ///< Need account for login - NeedInformation = 350, ///< Requested file action pending further information - - // 4xx: the command was not accepted and the requested action did not take place, - // but the error condition is temporary and the action may be requested again - ServiceUnavailable = 421, ///< Service not available, closing control connection - DataConnectionUnavailable = 425, ///< Can't open data connection - TransferAborted = 426, ///< Connection closed, transfer aborted - FileActionAborted = 450, ///< Requested file action not taken - LocalError = 451, ///< Requested action aborted, local error in processing - InsufficientStorageSpace = 452, ///< Requested action not taken; insufficient storage - ///< space in system, file unavailable - - // 5xx: the command was not accepted and - // the requested action did not take place - CommandUnknown = 500, ///< Syntax error, command unrecognized - ParametersUnknown = 501, ///< Syntax error in parameters or arguments - CommandNotImplemented = 502, ///< Command not implemented - BadCommandSequence = 503, ///< Bad sequence of commands - ParameterNotImplemented = 504, ///< Command not implemented for that parameter - NotLoggedIn = 530, ///< Not logged in - NeedAccountToStore = 532, ///< Need account for storing files - FileUnavailable = 550, ///< Requested action not taken, file unavailable - PageTypeUnknown = 551, ///< Requested action aborted, page type unknown - NotEnoughMemory = 552, ///< Requested file action aborted, exceeded storage allocation - FilenameNotAllowed = 553, ///< Requested action not taken, file name not allowed - - // 10xx: Custom codes - InvalidResponse = 1000, ///< Response is not a valid FTP one - ConnectionFailed = 1001, ///< Connection with server failed - ConnectionClosed = 1002, ///< Connection with server closed - InvalidFile = 1003 ///< Invalid file to upload / download - }; - - /** @brief Default constructor - ** - ** This constructor is used by the FTP client to build - ** the response. - ** - ** @param code Response status code - ** @param message Response message */ - explicit Response( Status code = InvalidResponse, const std::string& message = "" ); - - /** @brief Check if the status code means a success - ** - ** This function is defined for convenience, it is - ** equivalent to testing if the status code is < 400. - ** - ** @return True if the status is a success, false if it is a failure */ - bool isOk() const; - - /** @brief Get the status code of the response - ** - ** @return Status code */ - Status getStatus() const; - - /** @brief Get the full message contained in the response - ** @return The response message */ - const std::string& getMessage() const; - - private: - // Member data - Status mStatus; ///< Status code returned from the server - std::string mMessage; ///< Last message received from the server - }; - - /** @brief Specialization of FTP response returning a directory */ - class EE_API DirectoryResponse : public Response { - public: - /** @brief Default constructor - ** @param response Source response */ - DirectoryResponse( const Response& response ); - - /** @brief Get the directory returned in the response - ** @return Directory name */ - const std::string& getDirectory() const; - - private: - // Member data - std::string mDirectory; ///< Directory extracted from the response message - }; - - /** @brief Specialization of FTP response returning a filename listing */ - class EE_API ListingResponse : public Response { - public: - /** @brief Default constructor - ** - ** @param response Source response - ** @param data Data containing the raw listing */ - ListingResponse( const Response& response, const std::string& data ); - - /** @brief Return the array of directory/file names - ** - ** @return Array containing the requested listing */ - const std::vector& getListing() const; - - private: - // Member data - std::vector mListing; ///< Directory/file names extracted from the data - }; - - Ftp(); - - /** @brief Destructor - ** Automatically closes the connection with the server if - ** it is still opened. */ - ~Ftp(); - - /** @brief Connect to the specified FTP server - ** The port has a default value of 21, which is the standard - ** port used by the FTP protocol. You shouldn't use a different - ** value, unless you really know what you do. - ** This function tries to connect to the server so it may take - ** a while to complete, especially if the server is not - ** reachable. To avoid blocking your application for too long, - ** you can use a timeout. The default value, Time::Zero, means that the - ** system timeout will be used (which is usually pretty long). - ** @param server Hostname or address of the FTP server to connect to - ** @param port Port used for the connection - ** @param useTLS force TLS connection for FTPS. - ** @param validateCertificate Enables certificate validation for https request - ** @param validateHostname Enables hostname validation for https request - ** @param timeout Maximum time to wait - ** @return Server response to the request - ** @see disconnect */ - Response connect( const std::string& server, unsigned short port = 21, bool useTLS = false, - bool validateCertificate = true, bool validateHostname = true, - const Time& timeout = Time::Zero ); - - /** @brief Close the connection with the server - ** @return Server response to the request - ** @see connect */ - Response disconnect(); - - /** @brief Log in using an anonymous account - ** Logging in is mandatory after connecting to the server. - ** Users that are not logged in cannot perform any operation. - ** @return Server response to the request */ - Response login(); - - /** @brief Log in using a username and a password - ** Logging in is mandatory after connecting to the server. - ** Users that are not logged in cannot perform any operation. - ** @param name User name - ** @param password Password - ** @return Server response to the request */ - Response login( const std::string& name, const std::string& password ); - - /** @brief Send a null command to keep the connection alive - ** This command is useful because the server may close the - ** connection automatically if no command is sent. - ** @return Server response to the request */ - Response keepAlive(); - - /** @brief Get the current working directory - ** The working directory is the root path for subsequent - ** operations involving directories and/or filenames. - ** @return Server response to the request - ** @see getDirectoryListing, changeDirectory, parentDirectory */ - DirectoryResponse getWorkingDirectory(); - - /** @brief Get the contents of the given directory - ** This function retrieves the sub-directories and files - ** contained in the given directory. It is not recursive. - ** The @a directory parameter is relative to the current - ** working directory. - ** @param directory Directory to list - ** @return Server response to the request - ** @see getWorkingDirectory, changeDirectory, parentDirectory */ - ListingResponse getDirectoryListing( const std::string& directory = "" ); - - /** @brief Change the current working directory - ** The new directory must be relative to the current one. - ** @param directory New working directory - ** @return Server response to the request - ** @see getWorkingDirectory, getDirectoryListing, parentDirectory */ - Response changeDirectory( const std::string& directory ); - - /** @brief Go to the parent directory of the current one - ** @return Server response to the request - ** @see getWorkingDirectory, getDirectoryListing, changeDirectory */ - Response parentDirectory(); - - /** @brief Create a new directory - ** The new directory is created as a child of the current - ** working directory. - ** @param name Name of the directory to create - ** @return Server response to the request - ** @see deleteDirectory */ - Response createDirectory( const std::string& name ); - - /** @brief Remove an existing directory - ** The directory to remove must be relative to the - ** current working directory. - ** Use this function with caution, the directory will - ** be removed permanently! - ** @param name Name of the directory to remove - ** @return Server response to the request - ** @see createDirectory */ - Response deleteDirectory( const std::string& name ); - - /** @brief Rename an existing file - ** The filenames must be relative to the current working - ** directory. - ** @param file File to rename - ** @param newName New name of the file - ** @return Server response to the request - ** @see deleteFile */ - Response renameFile( const std::string& file, const std::string& newName ); - - /** @brief Remove an existing file - ** The file name must be relative to the current working - ** directory. - ** Use this function with caution, the file will be - ** removed permanently! - ** @param name File to remove - ** @return Server response to the request - ** @see RenameFile */ - Response deleteFile( const std::string& name ); - - /** @brief Download a file from the server - ** The filename of the distant file is relative to the - ** current working directory of the server, and the local - ** destination path is relative to the current directory - ** of your application. - ** If a file with the same filename as the distant file - ** already exists in the local destination path, it will - ** be overwritten. - ** @param remoteFile Filename of the distant file to download - ** @param localPath Where to put to file on the local computer - ** @param mode Transfer mode - ** @return Server response to the request - ** @see upload */ - Response download( const std::string& remoteFile, const std::string& localPath, - TransferMode mode = Binary ); - - /** @brief Upload a file to the server - ** The name of the local file is relative to the current - ** working directory of your application, and the - ** remote path is relative to the current directory of the - ** FTP server. - ** @param localFile Path of the local file to upload - ** @param remotePath Where to put to file on the server - ** @param mode Transfer mode - ** @param append Pass true to append to or false to overwrite the remote file if it already - *exists - ** @return Server response to the request - ** @see download */ - Response upload( const std::string& localFile, const std::string& remotePath, - TransferMode mode = Binary, bool append = false ); - - /** @return The server hostname (available only after connect). */ - const std::string& getHostname() const; - - /** @return True if connection is using TLS (available only after connect). */ - const bool& isTLS() const; - - private: - /** @brief Send a command to the FTP server - ** @param command Command to send - ** @param parameter Command parameter - ** @return Server response to the request */ - Response sendCommand( const std::string& command, const std::string& parameter = "" ); - - /** @brief Receive a response from the server - ** This function must be called after each call to - ** SendCommand that expects a response. - ** @return Server response to the request */ - Response getResponse(); - - /** @brief Utility class for exchanging data with the server on the data channel */ - class DataChannel; - friend class DataChannel; - - // Member data - TcpSocket* mCommandSocket; ///< Socket holding the control connection with the server - std::string mReceiveBuffer; ///< Received command data that is yet to be processed - std::string mHostName; - bool mConnected; - bool mIsTLS; -}; - -}} // namespace EE::Network - -#endif // EE_NETWORKCFTP_HPP - -/** -@class EE::Network::Ftp - -Ftp is a very simple FTP client that allows you -to communicate with a FTP server. The FTP protocol allows -you to manipulate a remote file system (list files, -upload, download, create, remove, ...). -Using the FTP client consists of 4 parts: -@li Connecting to the FTP server -@li Logging in (either as a registered user or anonymously) -@li Sending commands to the server -@li Disconnecting (this part can be done implicitly by the destructor) -Every command returns a FTP response, which contains the -status code as well as a message from the server. Some -commands such as getWorkingDirectory and getDirectoryListing -return additional data, and use a class derived from -Ftp::Response to provide this data. -All commands, especially upload and download, may take some -time to complete. This is important to know if you don't want -to block your application while the server is completing -the task. -Usage example: -@code -// Create a new FTP client -Ftp ftp; - -// Connect to the server -Ftp::Response response = ftp.connect("ftp://ftp.myserver.com"); -if (response.isOk()) - std::cout << "Connected" << std::endl; - -// Log in -response = ftp.login("laurent", "dF6Zm89D"); -if (response.isOk()) - std::cout << "Logged in" << std::endl; - -// Print the working directory -Ftp::DirectoryResponse directory = ftp.getWorkingDirectory(); -if (directory.isOk()) - std::cout << "Working directory: " << directory.getDirectory() << std::endl; - -// Create a new directory -response = ftp.createDirectory("files"); -if (response.isOk()) - std::cout << "Created new directory" << std::endl; - -// Upload a file to this new directory -response = ftp.upload("local-path/file.txt", "files", Ftp::Ascii); -if (response.isOk()) - std::cout << "File uploaded" << std::endl; - -// Disconnect from the server (optional) -ftp.disconnect(); -@endcode -*/ +#ifndef EE_NETWORKCFTP_HPP +#define EE_NETWORKCFTP_HPP + +#include +#include +#include +#include +#include +#include + +using namespace EE::System; + +namespace EE { namespace Network { + +class IpAddress; + +/** @brief A FTP client */ +class EE_API Ftp : NonCopyable { + public: + /** @brief Enumeration of transfer modes */ + enum TransferMode { + Binary, ///< Binary mode (file is transferred as a sequence of bytes) + Ascii, ///< Text mode using ASCII encoding + Ebcdic ///< Text mode using EBCDIC encoding + }; + + /** @brief Define a FTP response */ + class EE_API Response { + public: + /** @brief Status codes possibly returned by a FTP response */ + enum Status { + // 1xx: the requested action is being initiated, + // expect another reply before proceeding with a new command + RestartMarkerReply = 110, ///< Restart marker reply + ServiceReadySoon = 120, ///< Service ready in N minutes + DataConnectionAlreadyOpened = + 125, ///< Data connection already opened, transfer starting + OpeningDataConnection = 150, ///< File status ok, about to open data connection + + // 2xx: the requested action has been successfully completed + Ok = 200, ///< Command ok + PointlessCommand = 202, ///< Command not implemented + SystemStatus = 211, ///< System status, or system help reply + DirectoryStatus = 212, ///< Directory status + FileStatus = 213, ///< File status + HelpMessage = 214, ///< Help message + SystemType = 215, ///< NAME system type, where NAME is an official system name from the + ///< list in the Assigned Numbers document + ServiceReady = 220, ///< Service ready for new user + ClosingConnection = 221, ///< Service closing control connection + DataConnectionOpened = 225, ///< Data connection open, no transfer in progress + ClosingDataConnection = + 226, ///< Closing data connection, requested file action successful + EnteringPassiveMode = 227, ///< Entering passive mode + LoggedIn = 230, ///< User logged in, proceed. Logged out if appropriate + FileActionOk = 250, ///< Requested file action ok + DirectoryOk = 257, ///< PATHNAME created + + // 3xx: the command has been accepted, but the requested action + // is dormant, pending receipt of further information + NeedPassword = 331, ///< User name ok, need password + NeedAccountToLogIn = 332, ///< Need account for login + NeedInformation = 350, ///< Requested file action pending further information + + // 4xx: the command was not accepted and the requested action did not take place, + // but the error condition is temporary and the action may be requested again + ServiceUnavailable = 421, ///< Service not available, closing control connection + DataConnectionUnavailable = 425, ///< Can't open data connection + TransferAborted = 426, ///< Connection closed, transfer aborted + FileActionAborted = 450, ///< Requested file action not taken + LocalError = 451, ///< Requested action aborted, local error in processing + InsufficientStorageSpace = 452, ///< Requested action not taken; insufficient storage + ///< space in system, file unavailable + + // 5xx: the command was not accepted and + // the requested action did not take place + CommandUnknown = 500, ///< Syntax error, command unrecognized + ParametersUnknown = 501, ///< Syntax error in parameters or arguments + CommandNotImplemented = 502, ///< Command not implemented + BadCommandSequence = 503, ///< Bad sequence of commands + ParameterNotImplemented = 504, ///< Command not implemented for that parameter + NotLoggedIn = 530, ///< Not logged in + NeedAccountToStore = 532, ///< Need account for storing files + FileUnavailable = 550, ///< Requested action not taken, file unavailable + PageTypeUnknown = 551, ///< Requested action aborted, page type unknown + NotEnoughMemory = 552, ///< Requested file action aborted, exceeded storage allocation + FilenameNotAllowed = 553, ///< Requested action not taken, file name not allowed + + // 10xx: Custom codes + InvalidResponse = 1000, ///< Response is not a valid FTP one + ConnectionFailed = 1001, ///< Connection with server failed + ConnectionClosed = 1002, ///< Connection with server closed + InvalidFile = 1003 ///< Invalid file to upload / download + }; + + /** @brief Default constructor + ** + ** This constructor is used by the FTP client to build + ** the response. + ** + ** @param code Response status code + ** @param message Response message */ + explicit Response( Status code = InvalidResponse, const std::string& message = "" ); + + /** @brief Check if the status code means a success + ** + ** This function is defined for convenience, it is + ** equivalent to testing if the status code is < 400. + ** + ** @return True if the status is a success, false if it is a failure */ + bool isOk() const; + + /** @brief Get the status code of the response + ** + ** @return Status code */ + Status getStatus() const; + + /** @brief Get the full message contained in the response + ** @return The response message */ + const std::string& getMessage() const; + + private: + // Member data + Status mStatus; ///< Status code returned from the server + std::string mMessage; ///< Last message received from the server + }; + + /** @brief Specialization of FTP response returning a directory */ + class EE_API DirectoryResponse : public Response { + public: + /** @brief Default constructor + ** @param response Source response */ + DirectoryResponse( const Response& response ); + + /** @brief Get the directory returned in the response + ** @return Directory name */ + const std::string& getDirectory() const; + + private: + // Member data + std::string mDirectory; ///< Directory extracted from the response message + }; + + /** @brief Specialization of FTP response returning a filename listing */ + class EE_API ListingResponse : public Response { + public: + /** @brief Default constructor + ** + ** @param response Source response + ** @param data Data containing the raw listing */ + ListingResponse( const Response& response, const std::string& data ); + + /** @brief Return the array of directory/file names + ** + ** @return Array containing the requested listing */ + const std::vector& getListing() const; + + private: + // Member data + std::vector mListing; ///< Directory/file names extracted from the data + }; + + Ftp(); + + /** @brief Destructor + ** Automatically closes the connection with the server if + ** it is still opened. */ + ~Ftp(); + + /** @brief Connect to the specified FTP server + ** The port has a default value of 21, which is the standard + ** port used by the FTP protocol. You shouldn't use a different + ** value, unless you really know what you do. + ** This function tries to connect to the server so it may take + ** a while to complete, especially if the server is not + ** reachable. To avoid blocking your application for too long, + ** you can use a timeout. The default value, Time::Zero, means that the + ** system timeout will be used (which is usually pretty long). + ** @param server Hostname or address of the FTP server to connect to + ** @param port Port used for the connection + ** @param useTLS force TLS connection for FTPS. + ** @param validateCertificate Enables certificate validation for https request + ** @param validateHostname Enables hostname validation for https request + ** @param timeout Maximum time to wait + ** @return Server response to the request + ** @see disconnect */ + Response connect( const std::string& server, unsigned short port = 21, bool useTLS = false, + bool validateCertificate = true, bool validateHostname = true, + const Time& timeout = Time::Zero ); + + /** @brief Close the connection with the server + ** @return Server response to the request + ** @see connect */ + Response disconnect(); + + /** @brief Log in using an anonymous account + ** Logging in is mandatory after connecting to the server. + ** Users that are not logged in cannot perform any operation. + ** @return Server response to the request */ + Response login(); + + /** @brief Log in using a username and a password + ** Logging in is mandatory after connecting to the server. + ** Users that are not logged in cannot perform any operation. + ** @param name User name + ** @param password Password + ** @return Server response to the request */ + Response login( const std::string& name, const std::string& password ); + + /** @brief Send a null command to keep the connection alive + ** This command is useful because the server may close the + ** connection automatically if no command is sent. + ** @return Server response to the request */ + Response keepAlive(); + + /** @brief Get the current working directory + ** The working directory is the root path for subsequent + ** operations involving directories and/or filenames. + ** @return Server response to the request + ** @see getDirectoryListing, changeDirectory, parentDirectory */ + DirectoryResponse getWorkingDirectory(); + + /** @brief Get the contents of the given directory + ** This function retrieves the sub-directories and files + ** contained in the given directory. It is not recursive. + ** The @a directory parameter is relative to the current + ** working directory. + ** @param directory Directory to list + ** @return Server response to the request + ** @see getWorkingDirectory, changeDirectory, parentDirectory */ + ListingResponse getDirectoryListing( const std::string& directory = "" ); + + /** @brief Change the current working directory + ** The new directory must be relative to the current one. + ** @param directory New working directory + ** @return Server response to the request + ** @see getWorkingDirectory, getDirectoryListing, parentDirectory */ + Response changeDirectory( const std::string& directory ); + + /** @brief Go to the parent directory of the current one + ** @return Server response to the request + ** @see getWorkingDirectory, getDirectoryListing, changeDirectory */ + Response parentDirectory(); + + /** @brief Create a new directory + ** The new directory is created as a child of the current + ** working directory. + ** @param name Name of the directory to create + ** @return Server response to the request + ** @see deleteDirectory */ + Response createDirectory( const std::string& name ); + + /** @brief Remove an existing directory + ** The directory to remove must be relative to the + ** current working directory. + ** Use this function with caution, the directory will + ** be removed permanently! + ** @param name Name of the directory to remove + ** @return Server response to the request + ** @see createDirectory */ + Response deleteDirectory( const std::string& name ); + + /** @brief Rename an existing file + ** The filenames must be relative to the current working + ** directory. + ** @param file File to rename + ** @param newName New name of the file + ** @return Server response to the request + ** @see deleteFile */ + Response renameFile( const std::string& file, const std::string& newName ); + + /** @brief Remove an existing file + ** The file name must be relative to the current working + ** directory. + ** Use this function with caution, the file will be + ** removed permanently! + ** @param name File to remove + ** @return Server response to the request + ** @see RenameFile */ + Response deleteFile( const std::string& name ); + + /** @brief Download a file from the server + ** The filename of the distant file is relative to the + ** current working directory of the server, and the local + ** destination path is relative to the current directory + ** of your application. + ** If a file with the same filename as the distant file + ** already exists in the local destination path, it will + ** be overwritten. + ** @param remoteFile Filename of the distant file to download + ** @param localPath Where to put to file on the local computer + ** @param mode Transfer mode + ** @return Server response to the request + ** @see upload */ + Response download( const std::string& remoteFile, const std::string& localPath, + TransferMode mode = Binary ); + + /** @brief Upload a file to the server + ** The name of the local file is relative to the current + ** working directory of your application, and the + ** remote path is relative to the current directory of the + ** FTP server. + ** @param localFile Path of the local file to upload + ** @param remotePath Where to put to file on the server + ** @param mode Transfer mode + ** @param append Pass true to append to or false to overwrite the remote file if it already + *exists + ** @return Server response to the request + ** @see download */ + Response upload( const std::string& localFile, const std::string& remotePath, + TransferMode mode = Binary, bool append = false ); + + /** @return The server hostname (available only after connect). */ + const std::string& getHostname() const; + + /** @return True if connection is using TLS (available only after connect). */ + const bool& isTLS() const; + + private: + /** @brief Send a command to the FTP server + ** @param command Command to send + ** @param parameter Command parameter + ** @return Server response to the request */ + Response sendCommand( const std::string& command, const std::string& parameter = "" ); + + /** @brief Receive a response from the server + ** This function must be called after each call to + ** SendCommand that expects a response. + ** @return Server response to the request */ + Response getResponse(); + + /** @brief Utility class for exchanging data with the server on the data channel */ + class DataChannel; + friend class DataChannel; + + // Member data + TcpSocket* mCommandSocket; ///< Socket holding the control connection with the server + std::string mReceiveBuffer; ///< Received command data that is yet to be processed + std::string mHostName; + bool mConnected; + bool mIsTLS; +}; + +}} // namespace EE::Network + +#endif // EE_NETWORKCFTP_HPP + +/** +@class EE::Network::Ftp + +Ftp is a very simple FTP client that allows you +to communicate with a FTP server. The FTP protocol allows +you to manipulate a remote file system (list files, +upload, download, create, remove, ...). +Using the FTP client consists of 4 parts: +@li Connecting to the FTP server +@li Logging in (either as a registered user or anonymously) +@li Sending commands to the server +@li Disconnecting (this part can be done implicitly by the destructor) +Every command returns a FTP response, which contains the +status code as well as a message from the server. Some +commands such as getWorkingDirectory and getDirectoryListing +return additional data, and use a class derived from +Ftp::Response to provide this data. +All commands, especially upload and download, may take some +time to complete. This is important to know if you don't want +to block your application while the server is completing +the task. +Usage example: +@code +// Create a new FTP client +Ftp ftp; + +// Connect to the server +Ftp::Response response = ftp.connect("ftp://ftp.myserver.com"); +if (response.isOk()) + std::cout << "Connected" << std::endl; + +// Log in +response = ftp.login("laurent", "dF6Zm89D"); +if (response.isOk()) + std::cout << "Logged in" << std::endl; + +// Print the working directory +Ftp::DirectoryResponse directory = ftp.getWorkingDirectory(); +if (directory.isOk()) + std::cout << "Working directory: " << directory.getDirectory() << std::endl; + +// Create a new directory +response = ftp.createDirectory("files"); +if (response.isOk()) + std::cout << "Created new directory" << std::endl; + +// Upload a file to this new directory +response = ftp.upload("local-path/file.txt", "files", Ftp::Ascii); +if (response.isOk()) + std::cout << "File uploaded" << std::endl; + +// Disconnect from the server (optional) +ftp.disconnect(); +@endcode +*/ diff --git a/include/eepp/network/http.hpp b/include/eepp/network/http.hpp index b3d313a24..7082cd53c 100644 --- a/include/eepp/network/http.hpp +++ b/include/eepp/network/http.hpp @@ -2,8 +2,10 @@ #define EE_NETWORKCHTTP_HPP #include -#include +#include +#include #include +#include #include #include #include @@ -186,7 +188,7 @@ class EE_API Http : NonCopyable, public std::enable_shared_from_this { ///< target resource. Patch, ///< The PATCH method is used to apply partial modifications to a resource. Connect ///< The CONNECT method starts two-way communications with the requested - ///< resource. It can be used to open a tunnel. + ///< resource. It can be used to open a tunnel. }; /** @brief Enumerate the available states for a request */ diff --git a/include/eepp/network/ipaddress.hpp b/include/eepp/network/ipaddress.hpp index 7ae0db374..c901c1680 100644 --- a/include/eepp/network/ipaddress.hpp +++ b/include/eepp/network/ipaddress.hpp @@ -1,194 +1,194 @@ -#ifndef EE_NETWORKCIPADDRESS_HPP -#define EE_NETWORKCIPADDRESS_HPP - -#include -#include -using namespace EE::System; - -#include -#include -#include - -namespace EE { namespace Network { - -/** @brief Encapsulate an IPv4 network address */ -class EE_API IpAddress { - public: - /** @brief Default constructor - ** This constructor creates an empty (invalid) address */ - IpAddress(); - - /** @brief Construct the address from a string - ** Here @a address can be either a decimal address - ** (ex: "192.168.1.56") or a network name (ex: "localhost"). - ** @param address IP address or network name */ - IpAddress( const std::string& address ); - - /** @brief Construct the address from a string - ** Here @a address can be either a decimal address - ** (ex: "192.168.1.56") or a network name (ex: "localhost"). - ** This is equivalent to the constructor taking a std::string - ** parameter, it is defined for convenience so that the - ** implicit conversions from literal strings to IpAddress work. - ** @param address IP address or network name */ - IpAddress( const char* address ); - - /** @brief Construct the address from 4 bytes - ** Calling IpAddress(a, b, c, d) is equivalent to calling - ** IpAddress("a.b.c.d"), but safer as it doesn't have to - ** parse a string to get the address components. - ** @param byte0 First byte of the address - ** @param byte1 Second byte of the address - ** @param byte2 Third byte of the address - ** @param byte3 Fourth byte of the address */ - IpAddress( Uint8 byte0, Uint8 byte1, Uint8 byte2, Uint8 byte3 ); - - /** @brief Construct the address from a 32-bits integer - ** This constructor uses the internal representation of - ** the address directly. It should be used for optimization - ** purposes, and only if you got that representation from - ** IpAddress::toInteger(). - ** @param address 4 bytes of the address packed into a 32-bits integer - ** @see ToInteger */ - explicit IpAddress( Uint32 address ); - - /** @brief Get a string representation of the address - ** The returned string is the decimal representation of the - ** IP address (like "192.168.1.56"), even if it was constructed - ** from a host name. - ** @return String representation of the address - ** @see ToInteger */ - std::string toString() const; - - /** @brief Get an integer representation of the address - ** The returned number is the internal representation of the - ** address, and should be used for optimization purposes only - ** (like sending the address through a socket). - ** The integer produced by this function can then be converted - ** back to a IpAddress with the proper constructor. - ** @return 32-bits unsigned integer representation of the address - ** @see ToString */ - Uint32 toInteger() const; - - /** @brief Get the computer's local address - ** The local address is the address of the computer from the - ** LAN point of view, i.e. something like 192.168.1.56. It is - ** meaningful only for communications over the local network. - ** Unlike GetPublicAddress, this function is fast and may be - ** used safely anywhere. - ** @return Local IP address of the computer - ** @see GetPublicAddress */ - static IpAddress getLocalAddress(); - - /** @brief Get the computer's public address - ** The public address is the address of the computer from the - ** internet point of view, i.e. something like 89.54.1.169. - ** It is necessary for communications over the world wide web. - ** The only way to get a public address is to ask it to a - ** distant website; as a consequence, this function depends on - ** both your network connection and the server, and may be - ** very slow. You should use it as few as possible. Because - ** this function depends on the network connection and on a distant - ** server, you may use a time limit if you don't want your program - ** to be possibly stuck waiting in case there is a problem; this - ** limit is deactivated by default. - ** @param timeout Maximum time to wait - ** @return Public IP address of the computer - ** @see GetLocalAddress */ - static IpAddress getPublicAddress( Time timeout = Time::Zero ); - - // Static member data - static const IpAddress None; ///< Value representing an empty/invalid address - static const IpAddress Any; ///< Value representing any address (0.0.0.0) - static const IpAddress - LocalHost; ///< The "localhost" address (for connecting a computer to itself locally) - static const IpAddress Broadcast; ///< The "broadcast" address (for sending UDP messages to - ///< everyone on a local network) - private: - friend EE_API bool operator<( const IpAddress& left, const IpAddress& right ); - - /** @brief Resolve the given address string - ** @param address Address string */ - void resolve( const std::string& address ); - - // Member data - Uint32 mAddress; ///< Address stored as an unsigned 32 bits integer - bool mValid; ///< Is the address valid? -}; - -/** @brief Overload of == operator to compare two IP addresses -** @param left Left operand (a IP address) -** @param right Right operand (a IP address) -** @return True if both addresses are equal */ -EE_API bool operator==( const IpAddress& left, const IpAddress& right ); - -/** @brief Overload of != operator to compare two IP addresses -** @param left Left operand (a IP address) -** @param right Right operand (a IP address) -** @return True if both addresses are different */ -EE_API bool operator!=( const IpAddress& left, const IpAddress& right ); - -/** @brief Overload of < operator to compare two IP addresses -** @param left Left operand (a IP address) -** @param right Right operand (a IP address) -** @return True if @a left is lesser than @a right */ -EE_API bool operator<( const IpAddress& left, const IpAddress& right ); - -/** @brief Overload of > operator to compare two IP addresses -** @param left Left operand (a IP address) -** @param right Right operand (a IP address) -** @return True if @a left is greater than @a right */ -EE_API bool operator>( const IpAddress& left, const IpAddress& right ); - -/** @brief Overload of <= operator to compare two IP addresses -** @param left Left operand (a IP address) -** @param right Right operand (a IP address) -** @return True if @a left is lesser or equal than @a right */ -EE_API bool operator<=( const IpAddress& left, const IpAddress& right ); - -/** @brief Overload of >= operator to compare two IP addresses -** @param left Left operand (a IP address) -** @param right Right operand (a IP address) -** @return True if @a left is greater or equal than @a right */ -EE_API bool operator>=( const IpAddress& left, const IpAddress& right ); - -/** @brief Overload of >> operator to extract an IP address from an input stream -** @param stream Input stream -** @param address IP address to extract -** @return Reference to the input stream */ -EE_API std::istream& operator>>( std::istream& stream, IpAddress& address ); - -/** @brief Overload of << operator to print an IP address to an output stream -** @param stream Output stream -** @param address IP address to print -** @return Reference to the output stream */ -EE_API std::ostream& operator<<( std::ostream& stream, const IpAddress& address ); - -}} // namespace EE::Network - -#endif // EE_NETWORKCIPADDRESS_HPP - -/** -@class EE::Network::IpAddress - -IpAddress is a utility class for manipulating network -addresses. It provides a set a implicit constructors and -conversion functions to easily build or transform an IP -address from/to various representations. - -Usage example: -@code -IpAddress a0; // an invalid address -IpAddress a1 = IpAddress::None; // an invalid address (same as a0) -IpAddress a2("127.0.0.1"); // the local host address -IpAddress a3 = IpAddress::Broadcast; // the broadcast address -IpAddress a4(192, 168, 1, 56); // a local address -IpAddress a5("my_computer"); // a local address created from a network name -IpAddress a6("89.54.1.169"); // a distant address -IpAddress a7("www.google.com"); // a distant address created from a network name -IpAddress a8 = IpAddress::getLocalAddress(); // my address on the local network -IpAddress a9 = IpAddress::getPublicAddress(); // my address on the internet -@endcode -Note that IpAddress currently doesn't support IPv6 -nor other types of network addresses. -*/ +#ifndef EE_NETWORKCIPADDRESS_HPP +#define EE_NETWORKCIPADDRESS_HPP + +#include +#include +using namespace EE::System; + +#include +#include +#include + +namespace EE { namespace Network { + +/** @brief Encapsulate an IPv4 network address */ +class EE_API IpAddress { + public: + /** @brief Default constructor + ** This constructor creates an empty (invalid) address */ + IpAddress(); + + /** @brief Construct the address from a string + ** Here @a address can be either a decimal address + ** (ex: "192.168.1.56") or a network name (ex: "localhost"). + ** @param address IP address or network name */ + IpAddress( const std::string& address ); + + /** @brief Construct the address from a string + ** Here @a address can be either a decimal address + ** (ex: "192.168.1.56") or a network name (ex: "localhost"). + ** This is equivalent to the constructor taking a std::string + ** parameter, it is defined for convenience so that the + ** implicit conversions from literal strings to IpAddress work. + ** @param address IP address or network name */ + IpAddress( const char* address ); + + /** @brief Construct the address from 4 bytes + ** Calling IpAddress(a, b, c, d) is equivalent to calling + ** IpAddress("a.b.c.d"), but safer as it doesn't have to + ** parse a string to get the address components. + ** @param byte0 First byte of the address + ** @param byte1 Second byte of the address + ** @param byte2 Third byte of the address + ** @param byte3 Fourth byte of the address */ + IpAddress( Uint8 byte0, Uint8 byte1, Uint8 byte2, Uint8 byte3 ); + + /** @brief Construct the address from a 32-bits integer + ** This constructor uses the internal representation of + ** the address directly. It should be used for optimization + ** purposes, and only if you got that representation from + ** IpAddress::toInteger(). + ** @param address 4 bytes of the address packed into a 32-bits integer + ** @see ToInteger */ + explicit IpAddress( Uint32 address ); + + /** @brief Get a string representation of the address + ** The returned string is the decimal representation of the + ** IP address (like "192.168.1.56"), even if it was constructed + ** from a host name. + ** @return String representation of the address + ** @see ToInteger */ + std::string toString() const; + + /** @brief Get an integer representation of the address + ** The returned number is the internal representation of the + ** address, and should be used for optimization purposes only + ** (like sending the address through a socket). + ** The integer produced by this function can then be converted + ** back to a IpAddress with the proper constructor. + ** @return 32-bits unsigned integer representation of the address + ** @see ToString */ + Uint32 toInteger() const; + + /** @brief Get the computer's local address + ** The local address is the address of the computer from the + ** LAN point of view, i.e. something like 192.168.1.56. It is + ** meaningful only for communications over the local network. + ** Unlike GetPublicAddress, this function is fast and may be + ** used safely anywhere. + ** @return Local IP address of the computer + ** @see GetPublicAddress */ + static IpAddress getLocalAddress(); + + /** @brief Get the computer's public address + ** The public address is the address of the computer from the + ** internet point of view, i.e. something like 89.54.1.169. + ** It is necessary for communications over the world wide web. + ** The only way to get a public address is to ask it to a + ** distant website; as a consequence, this function depends on + ** both your network connection and the server, and may be + ** very slow. You should use it as few as possible. Because + ** this function depends on the network connection and on a distant + ** server, you may use a time limit if you don't want your program + ** to be possibly stuck waiting in case there is a problem; this + ** limit is deactivated by default. + ** @param timeout Maximum time to wait + ** @return Public IP address of the computer + ** @see GetLocalAddress */ + static IpAddress getPublicAddress( Time timeout = Time::Zero ); + + // Static member data + static const IpAddress None; ///< Value representing an empty/invalid address + static const IpAddress Any; ///< Value representing any address (0.0.0.0) + static const IpAddress + LocalHost; ///< The "localhost" address (for connecting a computer to itself locally) + static const IpAddress Broadcast; ///< The "broadcast" address (for sending UDP messages to + ///< everyone on a local network) + private: + friend EE_API bool operator<( const IpAddress& left, const IpAddress& right ); + + /** @brief Resolve the given address string + ** @param address Address string */ + void resolve( const std::string& address ); + + // Member data + Uint32 mAddress; ///< Address stored as an unsigned 32 bits integer + bool mValid; ///< Is the address valid? +}; + +/** @brief Overload of == operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if both addresses are equal */ +EE_API bool operator==( const IpAddress& left, const IpAddress& right ); + +/** @brief Overload of != operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if both addresses are different */ +EE_API bool operator!=( const IpAddress& left, const IpAddress& right ); + +/** @brief Overload of < operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if @a left is lesser than @a right */ +EE_API bool operator<( const IpAddress& left, const IpAddress& right ); + +/** @brief Overload of > operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if @a left is greater than @a right */ +EE_API bool operator>( const IpAddress& left, const IpAddress& right ); + +/** @brief Overload of <= operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if @a left is lesser or equal than @a right */ +EE_API bool operator<=( const IpAddress& left, const IpAddress& right ); + +/** @brief Overload of >= operator to compare two IP addresses +** @param left Left operand (a IP address) +** @param right Right operand (a IP address) +** @return True if @a left is greater or equal than @a right */ +EE_API bool operator>=( const IpAddress& left, const IpAddress& right ); + +/** @brief Overload of >> operator to extract an IP address from an input stream +** @param stream Input stream +** @param address IP address to extract +** @return Reference to the input stream */ +EE_API std::istream& operator>>( std::istream& stream, IpAddress& address ); + +/** @brief Overload of << operator to print an IP address to an output stream +** @param stream Output stream +** @param address IP address to print +** @return Reference to the output stream */ +EE_API std::ostream& operator<<( std::ostream& stream, const IpAddress& address ); + +}} // namespace EE::Network + +#endif // EE_NETWORKCIPADDRESS_HPP + +/** +@class EE::Network::IpAddress + +IpAddress is a utility class for manipulating network +addresses. It provides a set a implicit constructors and +conversion functions to easily build or transform an IP +address from/to various representations. + +Usage example: +@code +IpAddress a0; // an invalid address +IpAddress a1 = IpAddress::None; // an invalid address (same as a0) +IpAddress a2("127.0.0.1"); // the local host address +IpAddress a3 = IpAddress::Broadcast; // the broadcast address +IpAddress a4(192, 168, 1, 56); // a local address +IpAddress a5("my_computer"); // a local address created from a network name +IpAddress a6("89.54.1.169"); // a distant address +IpAddress a7("www.google.com"); // a distant address created from a network name +IpAddress a8 = IpAddress::getLocalAddress(); // my address on the local network +IpAddress a9 = IpAddress::getPublicAddress(); // my address on the internet +@endcode +Note that IpAddress currently doesn't support IPv6 +nor other types of network addresses. +*/ diff --git a/include/eepp/network/socket.hpp b/include/eepp/network/socket.hpp index a7603a5ad..89691e655 100644 --- a/include/eepp/network/socket.hpp +++ b/include/eepp/network/socket.hpp @@ -1,125 +1,125 @@ -#ifndef EE_NETWORKCSOCKET_HPP -#define EE_NETWORKCSOCKET_HPP - -#include -#include -#include - -namespace EE { namespace Network { -class SocketSelector; - -/** @brief Base class for all the socket types */ -class EE_API Socket : NonCopyable { - public: - /** @brief Status codes that may be returned by socket functions */ - enum Status { - Done, ///< The socket has sent / received the data - NotReady, ///< The socket is not ready to send / receive data yet - Partial, ///< The socket sent a part of the data - Disconnected, ///< The TCP socket has been disconnected - Error ///< An unexpected error happened - }; - - /** @brief Some special values used by sockets */ - enum { - AnyPort = 0 ///< Special value that tells the system to pick any available port - }; - - /** @brief Destructor */ - virtual ~Socket(); - - /** @brief Set the blocking state of the socket - ** In blocking mode, calls will not return until they have - ** completed their task. For example, a call to Receive in - ** blocking mode won't return until some data was actually - ** received. - ** In non-blocking mode, calls will always return immediately, - ** using the return code to signal whether there was data - ** available or not. - ** By default, all sockets are blocking. - ** @param blocking True to set the socket as blocking, false for non-blocking - ** @see IsBlocking */ - void setBlocking( bool blocking ); - - /** @brief Tell whether the socket is in blocking or non-blocking mode - ** @return True if the socket is blocking, false otherwise - ** @see SetBlocking */ - bool isBlocking() const; - - protected: - /** @brief Types of protocols that the socket can use */ - enum Type { - Tcp, ///< TCP protocol - Udp ///< UDP protocol - }; - - /** @brief Default constructor - ** This constructor can only be accessed by derived classes. - ** @param type Type of the socket (TCP or UDP) */ - Socket( Type type ); - - /** @brief Return the internal handle of the socket - ** The returned handle may be invalid if the socket - ** was not created yet (or already destroyed). - ** This function can only be accessed by derived classes. - ** @return The internal (OS-specific) handle of the socket */ - SocketHandle getHandle() const; - +#ifndef EE_NETWORKCSOCKET_HPP +#define EE_NETWORKCSOCKET_HPP + +#include +#include +#include + +namespace EE { namespace Network { +class SocketSelector; + +/** @brief Base class for all the socket types */ +class EE_API Socket : NonCopyable { + public: + /** @brief Status codes that may be returned by socket functions */ + enum Status { + Done, ///< The socket has sent / received the data + NotReady, ///< The socket is not ready to send / receive data yet + Partial, ///< The socket sent a part of the data + Disconnected, ///< The TCP socket has been disconnected + Error ///< An unexpected error happened + }; + + /** @brief Some special values used by sockets */ + enum { + AnyPort = 0 ///< Special value that tells the system to pick any available port + }; + + /** @brief Destructor */ + virtual ~Socket(); + + /** @brief Set the blocking state of the socket + ** In blocking mode, calls will not return until they have + ** completed their task. For example, a call to Receive in + ** blocking mode won't return until some data was actually + ** received. + ** In non-blocking mode, calls will always return immediately, + ** using the return code to signal whether there was data + ** available or not. + ** By default, all sockets are blocking. + ** @param blocking True to set the socket as blocking, false for non-blocking + ** @see IsBlocking */ + void setBlocking( bool blocking ); + + /** @brief Tell whether the socket is in blocking or non-blocking mode + ** @return True if the socket is blocking, false otherwise + ** @see SetBlocking */ + bool isBlocking() const; + + protected: + /** @brief Types of protocols that the socket can use */ + enum Type { + Tcp, ///< TCP protocol + Udp ///< UDP protocol + }; + + /** @brief Default constructor + ** This constructor can only be accessed by derived classes. + ** @param type Type of the socket (TCP or UDP) */ + Socket( Type type ); + + /** @brief Return the internal handle of the socket + ** The returned handle may be invalid if the socket + ** was not created yet (or already destroyed). + ** This function can only be accessed by derived classes. + ** @return The internal (OS-specific) handle of the socket */ + SocketHandle getHandle() const; + /** @brief Create the internal representation of the socket /// ** This function can only be accessed by derived classes. ** @return True when a valid socket exists, false when socket creation failed. */ bool create(); - - /** @brief Create the internal representation of the socket from a socket handle - ** This function can only be accessed by derived classes. - ** @param handle OS-specific handle of the socket to wrap */ - void create( SocketHandle handle ); - - /** @brief Close the socket gracefully - ** This function can only be accessed by derived classes. */ - void close(); - - protected: - friend class SocketSelector; - // Member data - Type mType; ///< Type of the socket (TCP or UDP) - SocketHandle mSocket; ///< Socket descriptor - bool mIsBlocking; ///< Current blocking mode of the socket -}; - -}} // namespace EE::Network - -#endif // EE_NETWORKCSOCKET_HPP - -/** -@class EE::Network::Socket - -This class mainly defines internal stuff to be used by -derived classes. - -The only public features that it defines, and which -is therefore common to all the socket classes, is the -blocking state. All sockets can be set as blocking or -non-blocking. - -In blocking mode, socket functions will hang until -the operation completes, which means that the entire -program (well, in fact the current thread if you use -multiple ones) will be stuck waiting for your socket -operation to complete. - -In non-blocking mode, all the socket functions will -return immediately. If the socket is not ready to complete -the requested operation, the function simply returns -the proper status code (Socket::NotReady). -The default mode, which is blocking, is the one that is -generally used, in combination with threads or selectors. - -The non-blocking mode is rather used in real-time -applications that run an endless loop that can poll -the socket often enough, and cannot afford blocking -this loop. - -@see EE::Network::TcpListener, EE::Network::TcpSocket, EE::Network::UdpSocket -*/ + + /** @brief Create the internal representation of the socket from a socket handle + ** This function can only be accessed by derived classes. + ** @param handle OS-specific handle of the socket to wrap */ + void create( SocketHandle handle ); + + /** @brief Close the socket gracefully + ** This function can only be accessed by derived classes. */ + void close(); + + protected: + friend class SocketSelector; + // Member data + Type mType; ///< Type of the socket (TCP or UDP) + SocketHandle mSocket; ///< Socket descriptor + bool mIsBlocking; ///< Current blocking mode of the socket +}; + +}} // namespace EE::Network + +#endif // EE_NETWORKCSOCKET_HPP + +/** +@class EE::Network::Socket + +This class mainly defines internal stuff to be used by +derived classes. + +The only public features that it defines, and which +is therefore common to all the socket classes, is the +blocking state. All sockets can be set as blocking or +non-blocking. + +In blocking mode, socket functions will hang until +the operation completes, which means that the entire +program (well, in fact the current thread if you use +multiple ones) will be stuck waiting for your socket +operation to complete. + +In non-blocking mode, all the socket functions will +return immediately. If the socket is not ready to complete +the requested operation, the function simply returns +the proper status code (Socket::NotReady). +The default mode, which is blocking, is the one that is +generally used, in combination with threads or selectors. + +The non-blocking mode is rather used in real-time +applications that run an endless loop that can poll +the socket often enough, and cannot afford blocking +this loop. + +@see EE::Network::TcpListener, EE::Network::TcpSocket, EE::Network::UdpSocket +*/ diff --git a/include/eepp/network/socketselector.hpp b/include/eepp/network/socketselector.hpp index 71981acc6..b21e707cf 100644 --- a/include/eepp/network/socketselector.hpp +++ b/include/eepp/network/socketselector.hpp @@ -1,168 +1,168 @@ -#ifndef EE_NETWORKCSOCKETSELECTOR_HPP -#define EE_NETWORKCSOCKETSELECTOR_HPP - -#include -#include -using namespace EE::System; - -namespace EE { namespace Network { - -class Socket; - -/** Multiplexer that allows to read from multiple sockets */ -class EE_API SocketSelector { - public: - /** @brief Default constructor */ - SocketSelector(); - - /** @brief Copy constructor - ** @param copy Instance to copy */ - SocketSelector( const SocketSelector& copy ); - - /** @brief Destructor */ - ~SocketSelector(); - - /** @brief Add a new socket to the selector - ** This function keeps a weak reference to the socket, - ** so you have to make sure that the socket is not destroyed - ** while it is stored in the selector. - ** This function does nothing if the socket is not valid. - ** @param socket Reference to the socket to add - ** @see Remove, Clear */ - void add( Socket& socket ); - - /** @brief Remove a socket from the selector - ** This function doesn't destroy the socket, it simply - ** removes the reference that the selector has to it. - ** @param socket Reference to the socket to remove - ** @see Add, Clear */ - void remove( Socket& socket ); - - /** @brief Remove all the sockets stored in the selector - ** This function doesn't destroy any instance, it simply - ** removes all the references that the selector has to - ** external sockets. - ** @see Add, Remove */ - void clear(); - - /** @brief Wait until one or more sockets are ready to receive - ** This function returns as soon as at least one socket has - ** some data available to be received. To know which sockets are - ** ready, use the isReady function. - ** If you use a timeout and no socket is ready before the timeout - ** is over, the function returns false. - ** @param timeout Maximum time to wait, (use Time::Zero for infinity) - ** @return True if there are sockets ready, false otherwise - ** @see IsReady */ - bool wait( Time timeout = Time::Zero ); - - /** @brief Test a socket to know if it is ready to receive data - ** This function must be used after a call to Wait, to know - ** which sockets are ready to receive data. If a socket is - ** ready, a call to receive will never block because we know - ** that there is data available to read. - ** Note that if this function returns true for a TcpListener, - ** this means that it is ready to accept a new connection. - ** @param socket Socket to test - ** @return True if the socket is ready to read, false otherwise - ** @see IsReady */ - bool isReady( Socket& socket ) const; - - /** @brief Overload of assignment operator - ** @param right Instance to assign - ** @return Reference to self */ - SocketSelector& operator=( const SocketSelector& right ); - - private: - struct SocketSelectorImpl; - - // Member data - SocketSelectorImpl* - mImpl; ///< Opaque pointer to the implementation (which requires OS-specific types) -}; - -}} // namespace EE::Network - -#endif // EE_NETWORKCSOCKETSELECTOR_HPP - -/** -@class EE::Network::SocketSelector - -Socket selectors provide a way to wait until some data is -available on a set of sockets, instead of just one. This -is convenient when you have multiple sockets that may -possibly receive data, but you don't know which one will -be ready first. In particular, it avoids to use a thread -for each socket; with selectors, a single thread can handle -all the sockets. - -All types of sockets can be used in a selector: -@li EE::NetworkTcpListener -@li EE::NetworkTcpSocket -@li EE::NetworkUdpSocket - -A selector doesn't store its own copies of the sockets -(socket classes are not copyable anyway), it simply keeps -a reference to the original sockets that you pass to the -"add" function. Therefore, you can't use the selector as a -socket container, you must store them outside and make sure -that they are alive as long as they are used in the selector. - -Using a selector is simple: -@li populate the selector with all the sockets that you want to observe -@li make it wait until there is data available on any of the sockets -@li test each socket to find out which ones are ready - -Usage example: -@code -// Create a socket to listen to new connections -TcpListener listener; -listener.listen(55001); - -// Create a list to store the future clients -std::vector clients; - -// Create a selector -SocketSelector selector; - -// Add the listener to the selector -selector.add(listener); - -// Endless loop that waits for new connections -while (running) { - // Make the selector wait for data on any socket - if (selector.wait()) { - // Test the listener - if (selector.isReady(listener)) { - // The listener is ready: there is a pending connection - TcpSocket* client = new TcpSocket; - if (listener.accept(*client) == Socket::Done) { - // Add the new client to the clients list - clients.push_back(client); - - // Add the new client to the selector so that we will - // be notified when he sends something - selector.add(*client); - } else { - // Error, we won't get a new connection, delete the socket - delete client; - } - } else { - // The listener socket is not ready, test all other sockets (the clients) - for (std::vector::iterator it = clients.begin(); it != clients.end(); ++it) { - TcpSocket& client = **it; - if (selector.isReady(client)) { - // The client has sent some data, we can receive it - Packet packet; - if (client.Receive(packet) == Socket::Done) { - ... - } - } - } - } - } -} -@endcode - -@see EE::Network::Socket -*/ +#ifndef EE_NETWORKCSOCKETSELECTOR_HPP +#define EE_NETWORKCSOCKETSELECTOR_HPP + +#include +#include +#include +using namespace EE::System; + +namespace EE { namespace Network { + +class Socket; + +/** Multiplexer that allows to read from multiple sockets */ +class EE_API SocketSelector { + public: + /** @brief Default constructor */ + SocketSelector(); + + /** @brief Copy constructor + ** @param copy Instance to copy */ + SocketSelector( const SocketSelector& copy ); + + /** @brief Destructor */ + ~SocketSelector(); + + /** @brief Add a new socket to the selector + ** This function keeps a weak reference to the socket, + ** so you have to make sure that the socket is not destroyed + ** while it is stored in the selector. + ** This function does nothing if the socket is not valid. + ** @param socket Reference to the socket to add + ** @see Remove, Clear */ + void add( Socket& socket ); + + /** @brief Remove a socket from the selector + ** This function doesn't destroy the socket, it simply + ** removes the reference that the selector has to it. + ** @param socket Reference to the socket to remove + ** @see Add, Clear */ + void remove( Socket& socket ); + + /** @brief Remove all the sockets stored in the selector + ** This function doesn't destroy any instance, it simply + ** removes all the references that the selector has to + ** external sockets. + ** @see Add, Remove */ + void clear(); + + /** @brief Wait until one or more sockets are ready to receive + ** This function returns as soon as at least one socket has + ** some data available to be received. To know which sockets are + ** ready, use the isReady function. + ** If you use a timeout and no socket is ready before the timeout + ** is over, the function returns false. + ** @param timeout Maximum time to wait, (use Time::Zero for infinity) + ** @return True if there are sockets ready, false otherwise + ** @see IsReady */ + bool wait( Time timeout = Time::Zero ); + + /** @brief Test a socket to know if it is ready to receive data + ** This function must be used after a call to Wait, to know + ** which sockets are ready to receive data. If a socket is + ** ready, a call to receive will never block because we know + ** that there is data available to read. + ** Note that if this function returns true for a TcpListener, + ** this means that it is ready to accept a new connection. + ** @param socket Socket to test + ** @return True if the socket is ready to read, false otherwise + ** @see IsReady */ + bool isReady( Socket& socket ) const; + + /** @brief Overload of assignment operator + ** @param right Instance to assign + ** @return Reference to self */ + SocketSelector& operator=( const SocketSelector& right ); + + private: + struct SocketSelectorImpl; + + // Member data + SocketSelectorImpl* + mImpl; ///< Opaque pointer to the implementation (which requires OS-specific types) +}; + +}} // namespace EE::Network + +#endif // EE_NETWORKCSOCKETSELECTOR_HPP + +/** +@class EE::Network::SocketSelector + +Socket selectors provide a way to wait until some data is +available on a set of sockets, instead of just one. This +is convenient when you have multiple sockets that may +possibly receive data, but you don't know which one will +be ready first. In particular, it avoids to use a thread +for each socket; with selectors, a single thread can handle +all the sockets. + +All types of sockets can be used in a selector: +@li EE::NetworkTcpListener +@li EE::NetworkTcpSocket +@li EE::NetworkUdpSocket + +A selector doesn't store its own copies of the sockets +(socket classes are not copyable anyway), it simply keeps +a reference to the original sockets that you pass to the +"add" function. Therefore, you can't use the selector as a +socket container, you must store them outside and make sure +that they are alive as long as they are used in the selector. + +Using a selector is simple: +@li populate the selector with all the sockets that you want to observe +@li make it wait until there is data available on any of the sockets +@li test each socket to find out which ones are ready + +Usage example: +@code +// Create a socket to listen to new connections +TcpListener listener; +listener.listen(55001); + +// Create a list to store the future clients +std::vector clients; + +// Create a selector +SocketSelector selector; + +// Add the listener to the selector +selector.add(listener); + +// Endless loop that waits for new connections +while (running) { + // Make the selector wait for data on any socket + if (selector.wait()) { + // Test the listener + if (selector.isReady(listener)) { + // The listener is ready: there is a pending connection + TcpSocket* client = new TcpSocket; + if (listener.accept(*client) == Socket::Done) { + // Add the new client to the clients list + clients.push_back(client); + + // Add the new client to the selector so that we will + // be notified when he sends something + selector.add(*client); + } else { + // Error, we won't get a new connection, delete the socket + delete client; + } + } else { + // The listener socket is not ready, test all other sockets (the clients) + for (std::vector::iterator it = clients.begin(); it != clients.end(); ++it) +{ TcpSocket& client = **it; if (selector.isReady(client)) { + // The client has sent some data, we can receive it + Packet packet; + if (client.Receive(packet) == Socket::Done) { + ... + } + } + } + } + } +} +@endcode + +@see EE::Network::Socket +*/ diff --git a/include/eepp/network/tcpsocket.hpp b/include/eepp/network/tcpsocket.hpp index 3e90d6a70..2375d461c 100644 --- a/include/eepp/network/tcpsocket.hpp +++ b/include/eepp/network/tcpsocket.hpp @@ -1,227 +1,229 @@ -#ifndef EE_NETWORKCTCPSOCKET_HPP -#define EE_NETWORKCTCPSOCKET_HPP - -#include -#include -#include -using namespace EE::System; - -namespace EE { namespace Network { - -class TcpListener; -class IpAddress; -class Packet; - -/** @brief Specialized socket using the TCP protocol */ -class EE_API TcpSocket : public Socket { - public: - static TcpSocket* New(); - - /** @brief Default constructor */ - TcpSocket(); - - virtual ~TcpSocket(); - - /** @brief Get the port to which the socket is bound locally - ** If the socket is not connected, this function returns 0. - ** @return Port to which the socket is bound - ** @see Connect, GetRemotePort */ - unsigned short getLocalPort() const; - - /** @brief Get the address of the connected peer - ** It the socket is not connected, this function returns - ** IpAddress::None. - ** @return Address of the remote peer - ** @see GetRemotePort */ - IpAddress getRemoteAddress() const; - - /** @brief Get the port of the connected peer to which - the socket is connected - ** If the socket is not connected, this function returns 0. - ** @return Remote port to which the socket is connected - ** @see GetRemoteAddress */ - unsigned short getRemotePort() const; - - /** @brief Connect the socket to a remote peer - ** In blocking mode, this function may take a while, especially - ** if the remote peer is not reachable. The last parameter allows - ** you to stop trying to connect after a given timeout. - ** If the socket was previously connected, it is first disconnected. - ** @param remoteAddress Address of the remote peer - ** @param remotePort Port of the remote peer - ** @param timeout Optional maximum time to wait - ** @return Status code - ** @see Disconnect */ - virtual Status connect( const IpAddress& remoteAddress, unsigned short remotePort, - Time timeout = Time::Zero ); - - /** @brief Disconnect the socket from its remote peer - ** This function gracefully closes the connection. If the - ** socket is not connected, this function has no effect. - ** @see Connect */ - virtual void disconnect(); - - /** @brief Send raw data to the remote peer - ** To be able to handle partial sends over non-blocking - ** sockets, use the send(const void*, std::size_t, std::size_t&) - ** overload instead. - ** - ** This function will fail if the socket is not connected. - ** - ** @param data Pointer to the sequence of bytes to send - ** @param size Number of bytes to send - ** @return Status code - ** @see Receive */ - virtual Status send( const void* data, std::size_t size ); - - /** @brief Send raw data to the remote peer - ** This function will fail if the socket is not connected. - ** @param data Pointer to the sequence of bytes to send - ** @param size Number of bytes to send - ** @param sent The number of bytes sent will be written here - ** @return Status code - ** @see receive */ - virtual Status send( const void* data, std::size_t size, std::size_t& sent ); - - /** @brief Receive raw data from the remote peer - ** In blocking mode, this function will wait until some - ** bytes are actually received. - ** This function will fail if the socket is not connected. - ** @param data Pointer to the array to fill with the received bytes - ** @param size Maximum number of bytes that can be received - ** @param received This variable is filled with the actual number of bytes received - ** @return Status code - ** @see Send */ - virtual Status receive( void* data, std::size_t size, std::size_t& received ); - - /** @brief Send a formatted packet of data to the remote peer - * - ** In non-blocking mode, if this function returns sf::Socket::Partial, - ** you \em must retry sending the same unmodified packet before sending - ** anything else in order to guarantee the packet arrives at the remote - ** peer uncorrupted. - ** - ** This function will fail if the socket is not connected. - ** @param packet Packet to send - ** @return Status code - ** @see Receive */ - virtual Status send( Packet& packet ); - - /** @brief Receive a formatted packet of data from the remote peer - ** In blocking mode, this function will wait until the whole packet - ** has been received. - ** This function will fail if the socket is not connected. - ** @param packet Packet to fill with the received data - ** @return Status code - ** @see Send */ - virtual Status receive( Packet& packet ); - - /** Set the send timeout. Only callable after connect ( after the socket - ** has been initialized ). */ - void setSendTimeout( const Time& timeout ); - - /** Set the receive timeout Only callable after connect ( after the socket - ** has been initialized ). */ - void setReceiveTimeout( const Time& timeout ); - - typedef std::function ReadFn; - - /** @brief Starts a new thread to receive all stdout and stderr data */ - void startAsyncRead( ReadFn readFn = nullptr ); - - private: - friend class TcpListener; - - /** @brief Structure holding the data of a pending packet */ - struct PendingPacket { - PendingPacket(); - - Uint32 Size; ///< Data of packet size - std::size_t SizeReceived; ///< Number of size bytes received so far - std::vector Data; ///< Data of the packet - }; - - // Member data - PendingPacket mPendingPacket; ///< Temporary data of the packet currently being received - std::thread mReadThread; -}; - -}} // namespace EE::Network - -#endif // EE_NETWORKCTCPSOCKET_HPP - -/** -@class EE::Network::TcpSocket - -TCP is a connected protocol, which means that a TCP -socket can only communicate with the host it is connected -to. It can't send or receive anything if it is not connected. - -The TCP protocol is reliable but adds a slight overhead. -It ensures that your data will always be received in order -and without errors (no data corrupted, lost or duplicated). - -When a socket is connected to a remote host, you can -retrieve information about this host with the -GetRemoteAddress and GetRemotePort functions. You can -also get the local port to which the socket is bound -(which is automatically chosen when the socket is connected), -with the GetLocalPort function. - -Sending and receiving data can use either the low-level -or the high-level functions. The low-level functions -process a raw sequence of bytes, and cannot ensure that -one call to Send will exactly match one call to Receive -at the other end of the socket. - -The high-level interface uses packets (see Packet), -which are easier to use and provide more safety regarding -the data that is exchanged. You can look at the Packet -class to get more details about how they work. - -The socket is automatically disconnected when it is destroyed, -but if you want to explicitly close the connection while -the socket instance is still alive, you can call disconnect. - -Usage example: -@code -// ----- The client ----- - -// Create a socket and connect it to 192.168.1.50 on port 55001 -TcpSocket socket; -socket.connect("192.168.1.50", 55001); - -// Send a message to the connected host -std::string message = "Hi, I am a client"; -socket.send(message.c_str(), message.size() + 1); - -// Receive an answer from the server -char buffer[1024]; -std::size_t received = 0; -socket.receive(buffer, sizeof(buffer), received); -std::cout << "The server said: " << buffer << std::endl; - -// ----- The server ----- - -// Create a listener to wait for incoming connections on port 55001 -TcpListener listener; -listener.listen(55001); - -// Wait for a connection -TcpSocket socket; -listener.accept(socket); -std::cout << "New client connected: " << socket.getRemoteAddress() << std::endl; - -// Receive a message from the client -char buffer[1024]; -std::size_t received = 0; -socket.receive(buffer, sizeof(buffer), received); -std::cout << "The client said: " << buffer << std::endl; - -// Send an answer -std::string message = "Welcome, client"; -socket.send(message.c_str(), message.size() + 1); -@endcode - -@see EE::Network::Socket, EE::Network::UdpSocket, EE::Network::Packet -*/ +#ifndef EE_NETWORKCTCPSOCKET_HPP +#define EE_NETWORKCTCPSOCKET_HPP + +#include +#include +#include +#include +#include +using namespace EE::System; + +namespace EE { namespace Network { + +class TcpListener; +class IpAddress; +class Packet; + +/** @brief Specialized socket using the TCP protocol */ +class EE_API TcpSocket : public Socket { + public: + static TcpSocket* New(); + + /** @brief Default constructor */ + TcpSocket(); + + virtual ~TcpSocket(); + + /** @brief Get the port to which the socket is bound locally + ** If the socket is not connected, this function returns 0. + ** @return Port to which the socket is bound + ** @see Connect, GetRemotePort */ + unsigned short getLocalPort() const; + + /** @brief Get the address of the connected peer + ** It the socket is not connected, this function returns + ** IpAddress::None. + ** @return Address of the remote peer + ** @see GetRemotePort */ + IpAddress getRemoteAddress() const; + + /** @brief Get the port of the connected peer to which + the socket is connected + ** If the socket is not connected, this function returns 0. + ** @return Remote port to which the socket is connected + ** @see GetRemoteAddress */ + unsigned short getRemotePort() const; + + /** @brief Connect the socket to a remote peer + ** In blocking mode, this function may take a while, especially + ** if the remote peer is not reachable. The last parameter allows + ** you to stop trying to connect after a given timeout. + ** If the socket was previously connected, it is first disconnected. + ** @param remoteAddress Address of the remote peer + ** @param remotePort Port of the remote peer + ** @param timeout Optional maximum time to wait + ** @return Status code + ** @see Disconnect */ + virtual Status connect( const IpAddress& remoteAddress, unsigned short remotePort, + Time timeout = Time::Zero ); + + /** @brief Disconnect the socket from its remote peer + ** This function gracefully closes the connection. If the + ** socket is not connected, this function has no effect. + ** @see Connect */ + virtual void disconnect(); + + /** @brief Send raw data to the remote peer + ** To be able to handle partial sends over non-blocking + ** sockets, use the send(const void*, std::size_t, std::size_t&) + ** overload instead. + ** + ** This function will fail if the socket is not connected. + ** + ** @param data Pointer to the sequence of bytes to send + ** @param size Number of bytes to send + ** @return Status code + ** @see Receive */ + virtual Status send( const void* data, std::size_t size ); + + /** @brief Send raw data to the remote peer + ** This function will fail if the socket is not connected. + ** @param data Pointer to the sequence of bytes to send + ** @param size Number of bytes to send + ** @param sent The number of bytes sent will be written here + ** @return Status code + ** @see receive */ + virtual Status send( const void* data, std::size_t size, std::size_t& sent ); + + /** @brief Receive raw data from the remote peer + ** In blocking mode, this function will wait until some + ** bytes are actually received. + ** This function will fail if the socket is not connected. + ** @param data Pointer to the array to fill with the received bytes + ** @param size Maximum number of bytes that can be received + ** @param received This variable is filled with the actual number of bytes received + ** @return Status code + ** @see Send */ + virtual Status receive( void* data, std::size_t size, std::size_t& received ); + + /** @brief Send a formatted packet of data to the remote peer + * + ** In non-blocking mode, if this function returns sf::Socket::Partial, + ** you \em must retry sending the same unmodified packet before sending + ** anything else in order to guarantee the packet arrives at the remote + ** peer uncorrupted. + ** + ** This function will fail if the socket is not connected. + ** @param packet Packet to send + ** @return Status code + ** @see Receive */ + virtual Status send( Packet& packet ); + + /** @brief Receive a formatted packet of data from the remote peer + ** In blocking mode, this function will wait until the whole packet + ** has been received. + ** This function will fail if the socket is not connected. + ** @param packet Packet to fill with the received data + ** @return Status code + ** @see Send */ + virtual Status receive( Packet& packet ); + + /** Set the send timeout. Only callable after connect ( after the socket + ** has been initialized ). */ + void setSendTimeout( const Time& timeout ); + + /** Set the receive timeout Only callable after connect ( after the socket + ** has been initialized ). */ + void setReceiveTimeout( const Time& timeout ); + + typedef std::function ReadFn; + + /** @brief Starts a new thread to receive all stdout and stderr data */ + void startAsyncRead( ReadFn readFn = nullptr ); + + private: + friend class TcpListener; + + /** @brief Structure holding the data of a pending packet */ + struct PendingPacket { + PendingPacket(); + + Uint32 Size; ///< Data of packet size + std::size_t SizeReceived; ///< Number of size bytes received so far + std::vector Data; ///< Data of the packet + }; + + // Member data + PendingPacket mPendingPacket; ///< Temporary data of the packet currently being received + std::thread mReadThread; +}; + +}} // namespace EE::Network + +#endif // EE_NETWORKCTCPSOCKET_HPP + +/** +@class EE::Network::TcpSocket + +TCP is a connected protocol, which means that a TCP +socket can only communicate with the host it is connected +to. It can't send or receive anything if it is not connected. + +The TCP protocol is reliable but adds a slight overhead. +It ensures that your data will always be received in order +and without errors (no data corrupted, lost or duplicated). + +When a socket is connected to a remote host, you can +retrieve information about this host with the +GetRemoteAddress and GetRemotePort functions. You can +also get the local port to which the socket is bound +(which is automatically chosen when the socket is connected), +with the GetLocalPort function. + +Sending and receiving data can use either the low-level +or the high-level functions. The low-level functions +process a raw sequence of bytes, and cannot ensure that +one call to Send will exactly match one call to Receive +at the other end of the socket. + +The high-level interface uses packets (see Packet), +which are easier to use and provide more safety regarding +the data that is exchanged. You can look at the Packet +class to get more details about how they work. + +The socket is automatically disconnected when it is destroyed, +but if you want to explicitly close the connection while +the socket instance is still alive, you can call disconnect. + +Usage example: +@code +// ----- The client ----- + +// Create a socket and connect it to 192.168.1.50 on port 55001 +TcpSocket socket; +socket.connect("192.168.1.50", 55001); + +// Send a message to the connected host +std::string message = "Hi, I am a client"; +socket.send(message.c_str(), message.size() + 1); + +// Receive an answer from the server +char buffer[1024]; +std::size_t received = 0; +socket.receive(buffer, sizeof(buffer), received); +std::cout << "The server said: " << buffer << std::endl; + +// ----- The server ----- + +// Create a listener to wait for incoming connections on port 55001 +TcpListener listener; +listener.listen(55001); + +// Wait for a connection +TcpSocket socket; +listener.accept(socket); +std::cout << "New client connected: " << socket.getRemoteAddress() << std::endl; + +// Receive a message from the client +char buffer[1024]; +std::size_t received = 0; +socket.receive(buffer, sizeof(buffer), received); +std::cout << "The client said: " << buffer << std::endl; + +// Send an answer +std::string message = "Welcome, client"; +socket.send(message.c_str(), message.size() + 1); +@endcode + +@see EE::Network::Socket, EE::Network::UdpSocket, EE::Network::Packet +*/ diff --git a/include/eepp/network/uri.hpp b/include/eepp/network/uri.hpp index ef8bce336..66c248d9d 100644 --- a/include/eepp/network/uri.hpp +++ b/include/eepp/network/uri.hpp @@ -1,7 +1,11 @@ #ifndef EE_NETWORK_URI_HPP #define EE_NETWORK_URI_HPP -#include +#include +#include +#include +#include +#include #include namespace EE { namespace Network { diff --git a/include/eepp/system/condition.hpp b/include/eepp/system/condition.hpp index 85e8e26de..fa8612f55 100644 --- a/include/eepp/system/condition.hpp +++ b/include/eepp/system/condition.hpp @@ -1,7 +1,7 @@ #ifndef EE_SYSTEMCCONDITION_HPP #define EE_SYSTEMCCONDITION_HPP -#include +#include #include namespace EE { namespace System { diff --git a/include/eepp/system/filesystem.hpp b/include/eepp/system/filesystem.hpp index e4a1c5e46..18c31a5e5 100644 --- a/include/eepp/system/filesystem.hpp +++ b/include/eepp/system/filesystem.hpp @@ -1,9 +1,11 @@ #ifndef EE_SYSTEM_FILESYSTEM_HPP #define EE_SYSTEM_FILESYSTEM_HPP -#include +#include +#include #include #include +#include #include #include diff --git a/include/eepp/system/log.hpp b/include/eepp/system/log.hpp index 324774f1c..213442b3e 100644 --- a/include/eepp/system/log.hpp +++ b/include/eepp/system/log.hpp @@ -1,6 +1,7 @@ #ifndef EE_SYSTEM_LOG_H #define EE_SYSTEM_LOG_H +#include #include #include #include @@ -87,8 +88,7 @@ class EE_API Log : protected Mutex { } /** @brief Writes a formatted string to the log */ - template - void writef( std::string_view format, Args&&... args ) { + template void writef( std::string_view format, Args&&... args ) { auto result = String::format( format, FormatArg>::get( std::forward( args ) )... ); write( result ); diff --git a/include/eepp/system/md5.hpp b/include/eepp/system/md5.hpp index dc3daa171..c325bc3f6 100644 --- a/include/eepp/system/md5.hpp +++ b/include/eepp/system/md5.hpp @@ -1,9 +1,10 @@ #ifndef EE_SYSTEM_MD5_HPP #define EE_SYSTEM_MD5_HPP -#include -#include #include +#include +#include +#include namespace EE { namespace System { diff --git a/include/eepp/system/mutex.hpp b/include/eepp/system/mutex.hpp index 851eaa3a1..37247b45d 100644 --- a/include/eepp/system/mutex.hpp +++ b/include/eepp/system/mutex.hpp @@ -1,7 +1,7 @@ #ifndef EE_SYSTEMCMUTEX_H #define EE_SYSTEMCMUTEX_H -#include +#include #include #include diff --git a/include/eepp/system/pack.hpp b/include/eepp/system/pack.hpp index 552d27405..261157e7a 100644 --- a/include/eepp/system/pack.hpp +++ b/include/eepp/system/pack.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace EE { namespace System { diff --git a/include/eepp/system/resourceloader.hpp b/include/eepp/system/resourceloader.hpp index 108f02afd..791889179 100644 --- a/include/eepp/system/resourceloader.hpp +++ b/include/eepp/system/resourceloader.hpp @@ -2,8 +2,9 @@ #define EE_SYSTEMCRESOURCELOADER #include -#include +#include #include +#include #include #include diff --git a/include/eepp/system/singleton.hpp b/include/eepp/system/singleton.hpp index f0843916e..2010b75dc 100644 --- a/include/eepp/system/singleton.hpp +++ b/include/eepp/system/singleton.hpp @@ -2,9 +2,12 @@ #define EE_SYSTEMSINGLETON_H #include -#include +#include +#include +#include #include #include +#include /** Internally we gonna use the macro singleton because it works with the engine compiled as dynamic * libraries. @@ -21,20 +24,6 @@ * creates one internal-linkage singleton per translation unit. */ -#define SINGLETON_DECLARE_HEADERS( T ) \ - public: \ - static T* createSingleton(); \ - \ - static T* existsSingleton(); \ - \ - static bool isShuttingDown(); \ - \ - static T* instance(); \ - \ - static void destroySingleton(); \ - \ - static void detachSingleton(); - #define SINGLETON_DECLARE_IMPLEMENTATION( T ) \ \ static std::atomic ms_singleton{ NULL }; \ diff --git a/include/eepp/system/singletondeclarations.hpp b/include/eepp/system/singletondeclarations.hpp new file mode 100644 index 000000000..0e8c015f7 --- /dev/null +++ b/include/eepp/system/singletondeclarations.hpp @@ -0,0 +1,24 @@ +#ifndef EE_SYSTEMSINGLETONDECLARATIONS_HPP +#define EE_SYSTEMSINGLETONDECLARATIONS_HPP + +/** Declarations shared by the macro-based singleton implementations. + * + * Keep this header lightweight. The implementation macro and the Singleton template + * require the allocation and synchronization machinery from singleton.hpp, while + * singleton-owning public headers only need these declarations. + */ +#define SINGLETON_DECLARE_HEADERS( T ) \ + public: \ + static T* createSingleton(); \ + \ + static T* existsSingleton(); \ + \ + static bool isShuttingDown(); \ + \ + static T* instance(); \ + \ + static void destroySingleton(); \ + \ + static void detachSingleton(); + +#endif diff --git a/include/eepp/system/threadlocal.hpp b/include/eepp/system/threadlocal.hpp index a3cf2b7f9..a68dbaa47 100644 --- a/include/eepp/system/threadlocal.hpp +++ b/include/eepp/system/threadlocal.hpp @@ -1,7 +1,7 @@ #ifndef EE_SYSTEMCTHREADLOCAL_HPP #define EE_SYSTEMCTHREADLOCAL_HPP -#include +#include #include namespace EE { namespace System { namespace Private { diff --git a/include/eepp/system/translator.hpp b/include/eepp/system/translator.hpp index 930af796f..cfe2857cc 100644 --- a/include/eepp/system/translator.hpp +++ b/include/eepp/system/translator.hpp @@ -1,7 +1,11 @@ #ifndef EE_SYSTEM_STRINGLOCALERESOURCE_HPP #define EE_SYSTEM_STRINGLOCALERESOURCE_HPP -#include +#include +#include + +#include +#include namespace pugi { class xml_node; diff --git a/include/eepp/ui/doc/syntaxcolorscheme.hpp b/include/eepp/ui/doc/syntaxcolorscheme.hpp index 417ab1620..428a86699 100644 --- a/include/eepp/ui/doc/syntaxcolorscheme.hpp +++ b/include/eepp/ui/doc/syntaxcolorscheme.hpp @@ -2,10 +2,15 @@ #define EE_UI_DOC_SYNTAXCOLORSCHEME_HPP #include -#include -#include #include +namespace EE { namespace System { + +class IOStream; +class Pack; + +}} // namespace EE::System + using namespace EE::System; namespace EE { namespace UI { namespace Doc { diff --git a/include/eepp/ui/doc/syntaxdefinition.hpp b/include/eepp/ui/doc/syntaxdefinition.hpp index a3ea5f077..f835d83eb 100644 --- a/include/eepp/ui/doc/syntaxdefinition.hpp +++ b/include/eepp/ui/doc/syntaxdefinition.hpp @@ -2,11 +2,13 @@ #define EE_UI_DOC_DEFINITION_HPP #include +#include #include #include #include #include +#include #include #include #include diff --git a/include/eepp/ui/doc/syntaxdefinitionmanager.hpp b/include/eepp/ui/doc/syntaxdefinitionmanager.hpp index 4bbcae8a8..328807d7f 100644 --- a/include/eepp/ui/doc/syntaxdefinitionmanager.hpp +++ b/include/eepp/ui/doc/syntaxdefinitionmanager.hpp @@ -2,14 +2,21 @@ #define EE_UI_DOC_SYNTAXSTYLEMANAGER_HPP #include -#include -#include -#include +#include +#include +#include #include #include #include #include +namespace EE { namespace System { + +class IOStream; +class Pack; + +}} // namespace EE::System + using namespace EE::System; namespace EE { namespace UI { namespace Doc { diff --git a/include/eepp/window/cursor.hpp b/include/eepp/window/cursor.hpp index bf3867526..1ed36a130 100644 --- a/include/eepp/window/cursor.hpp +++ b/include/eepp/window/cursor.hpp @@ -1,7 +1,8 @@ #ifndef EE_WINDOWCCURSOR_HPP #define EE_WINDOWCCURSOR_HPP -#include +#include +#include #include #include using namespace EE::Math; diff --git a/include/eepp/window/platformimpl.hpp b/include/eepp/window/platformimpl.hpp index 16fa0c1ed..25270ebba 100644 --- a/include/eepp/window/platformimpl.hpp +++ b/include/eepp/window/platformimpl.hpp @@ -1,7 +1,7 @@ #ifndef EE_WINDOWCPLATFORMIMPL_HPP #define EE_WINDOWCPLATFORMIMPL_HPP -#include +#include #include using namespace EE::Math; diff --git a/src/eepp/graphics/textureatlas.cpp b/src/eepp/graphics/textureatlas.cpp index 843b8fa40..e06c9df96 100644 --- a/src/eepp/graphics/textureatlas.cpp +++ b/src/eepp/graphics/textureatlas.cpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/src/eepp/graphics/texturepackernode.cpp b/src/eepp/graphics/texturepackernode.cpp index 1289fb15f..1feb5a6ba 100644 --- a/src/eepp/graphics/texturepackernode.cpp +++ b/src/eepp/graphics/texturepackernode.cpp @@ -1,3 +1,4 @@ +#include #include namespace EE { namespace Graphics { namespace Private { diff --git a/src/eepp/network/uri.cpp b/src/eepp/network/uri.cpp index cf8fe1ba1..ff71993f5 100644 --- a/src/eepp/network/uri.cpp +++ b/src/eepp/network/uri.cpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/src/eepp/system/threadpool.cpp b/src/eepp/system/threadpool.cpp index d1cf57b47..ae203a233 100644 --- a/src/eepp/system/threadpool.cpp +++ b/src/eepp/system/threadpool.cpp @@ -1,4 +1,5 @@ #include +#include #include namespace EE { namespace System { diff --git a/src/eepp/system/virtualfilesystem.cpp b/src/eepp/system/virtualfilesystem.cpp index eb74bdfb8..3a771ffed 100644 --- a/src/eepp/system/virtualfilesystem.cpp +++ b/src/eepp/system/virtualfilesystem.cpp @@ -1,3 +1,4 @@ +#include #include namespace EE { namespace System { diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index e6caa7b72..a02a32547 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/eepp/ui/uiborderdrawable.cpp b/src/eepp/ui/uiborderdrawable.cpp index 4d0eebbe8..a72d73507 100644 --- a/src/eepp/ui/uiborderdrawable.cpp +++ b/src/eepp/ui/uiborderdrawable.cpp @@ -73,23 +73,34 @@ void UIBorderDrawable::draw( const Vector2f& position, const Sizef& size ) { if ( border.width <= 0 || ( border.style != BorderStyle::Dotted && border.style != BorderStyle::Dashed ) ) return; + // These are axis-aligned physical-pixel primitives. A centered odd-width pattern on an + // even-length side otherwise starts at a half pixel, whose coverage is + // driver-dependent. Snap the side bounds and choose the lower integer for an + // unavoidable asymmetric remainder so every backend rasterizes the same pixels. + const Float snappedStart = std::round( start ); + const Float snappedLimit = std::round( start + length ); + const Float snappedFixed = std::round( fixed ); + const Float snappedLength = snappedLimit - snappedStart; const Float segment = border.style == BorderStyle::Dotted ? border.width : border.width * 3.f; const Float step = segment * 2.f; - if ( segment <= 0.f || length <= 0.f ) + if ( segment <= 0.f || snappedLength <= 0.f ) return; const Uint32 count = - eemax( 1, static_cast( std::ceil( length / step ) ) ); + eemax( 1, static_cast( std::ceil( snappedLength / step ) ) ); const Float used = ( count - 1 ) * step + segment; - Float cursor = start + eemax( 0.f, ( length - used ) * 0.5f ); + Float cursor = + std::floor( snappedStart + eemax( 0.f, ( snappedLength - used ) * 0.5f ) ); Primitives primitive; primitive.setColor( border.color ); - for ( Uint32 i = 0; i < count && cursor < start + length; ++i, cursor += step ) { - const Float end = eemin( cursor + segment, start + length ); + for ( Uint32 i = 0; i < count && cursor < snappedLimit; ++i, cursor += step ) { + const Float end = eemin( cursor + segment, snappedLimit ); if ( horizontal ) { - primitive.drawRectangle( Rectf( cursor, fixed, end, fixed + border.width ) ); + primitive.drawRectangle( + Rectf( cursor, snappedFixed, end, snappedFixed + border.width ) ); } else { - primitive.drawRectangle( Rectf( fixed, cursor, fixed + border.width, end ) ); + primitive.drawRectangle( + Rectf( snappedFixed, cursor, snappedFixed + border.width, end ) ); } } }; diff --git a/src/eepp/window/backend.hpp b/src/eepp/window/backend.hpp index 7fa20f1f4..73e8b8c9b 100644 --- a/src/eepp/window/backend.hpp +++ b/src/eepp/window/backend.hpp @@ -1,7 +1,7 @@ #ifndef EE_WINDOWCBACKEND_HPP #define EE_WINDOWCBACKEND_HPP -#include +#include namespace EE { namespace Window { namespace Backend {