From dc9f464f84dd1a322613b78c263e8f80227d702f Mon Sep 17 00:00:00 2001 From: ZavaruKitsu Date: Mon, 3 Jul 2023 03:26:46 +0300 Subject: [PATCH] feat: progress AyuSync implementation fix: build errors --- Telegram/CMakeLists.txt | 1 + .../SourceFiles/ayu/database/ayu_database.cpp | 5 +- Telegram/SourceFiles/ayu/libs/process.hpp | 1041 +++++++++++++++++ .../SourceFiles/ayu/libs/sqlite/sqlite_orm.h | 38 +- .../ayu/sync/ayu_sync_controller.cpp | 61 +- .../ayu/sync/ayu_sync_controller.h | 28 +- .../ayu/sync/utils/ayu_pipe_wrapper.cpp | 1 - .../ayu/sync/utils/process_utils.hpp | 86 ++ .../ayu/ui/settings/settings_ayu.cpp | 21 + .../ayu/ui/settings/settings_ayu.h | 8 +- 10 files changed, 1261 insertions(+), 29 deletions(-) create mode 100644 Telegram/SourceFiles/ayu/libs/process.hpp create mode 100644 Telegram/SourceFiles/ayu/sync/utils/process_utils.hpp diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 789e25d33..c0bfa0f24 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -122,6 +122,7 @@ PRIVATE ayu/sync/models.h ayu/sync/utils/ayu_pipe_wrapper.cpp ayu/sync/utils/ayu_pipe_wrapper.h + ayu/sync/utils/process_utils.hpp ayu/libs/pipe.hpp ayu/libs/json.hpp ayu/libs/json_ext.hpp diff --git a/Telegram/SourceFiles/ayu/database/ayu_database.cpp b/Telegram/SourceFiles/ayu/database/ayu_database.cpp index 03703fac3..5f370c498 100644 --- a/Telegram/SourceFiles/ayu/database/ayu_database.cpp +++ b/Telegram/SourceFiles/ayu/database/ayu_database.cpp @@ -7,9 +7,10 @@ #include "ayu_database.h" +#include + #include "entities.h" -#include "ayu/sqlite/sqlite_orm.h" -#include "QtCore/QString" +#include "ayu/libs/sqlite/sqlite_orm.h" #include "main/main_session.h" using namespace sqlite_orm; diff --git a/Telegram/SourceFiles/ayu/libs/process.hpp b/Telegram/SourceFiles/ayu/libs/process.hpp new file mode 100644 index 000000000..c6cc8822b --- /dev/null +++ b/Telegram/SourceFiles/ayu/libs/process.hpp @@ -0,0 +1,1041 @@ +/////////////////////////////////////////////////////////// +/// Copyright 2019 Alexy Pellegrini +/// +/// Permission is hereby granted, free of charge, +/// to any person obtaining a copy of this software +/// and associated documentation files (the "Software"), +/// to deal in the Software without restriction, +/// including without limitation the rights to use, +/// copy, modify, merge, publish, distribute, sublicense, +/// and/or sell copies of the Software, and to permit +/// persons to whom the Software is furnished to do so, +/// subject to the following conditions: +/// +/// The above copyright notice and this permission notice +/// shall be included in all copies or substantial portions +/// of the Software. +/// +/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +/// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +/// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +/// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +/// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +/// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +/// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +/// OR OTHER DEALINGS IN THE SOFTWARE. +/////////////////////////////////////////////////////////// + +#ifndef NOT_ENOUGH_STANDARDS_PROCESS +#define NOT_ENOUGH_STANDARDS_PROCESS + +#if defined(_WIN32) + #define NES_WIN32_PROCESS + #ifndef NOMINMAX + #define NOMINMAX + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include +#elif defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) + #define NES_POSIX_PROCESS + #include + #include + #include + #include + #include +#else + #error "Not enough standards does not support this environment." +#endif + +#if __has_include("pipe.hpp") + #define NES_PROCESS_PIPE_EXTENSION + #include "pipe.hpp" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(NES_WIN32_PROCESS) + +namespace nes +{ + +class process; + +namespace impl +{ + +enum class id_t : DWORD{}; + +constexpr bool operator==(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) == static_cast(rhs); +} + +constexpr bool operator!=(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) != static_cast(rhs); +} + +constexpr bool operator<(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) < static_cast(rhs); +} + +constexpr bool operator<=(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) <= static_cast(rhs); +} + +constexpr bool operator>(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) > static_cast(rhs); +} + +constexpr bool operator>=(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) >= static_cast(rhs); +} + +struct auto_handle +{ +public: + constexpr auto_handle() = default; + auto_handle(HANDLE h) + :m_handle{h} + { + + } + + ~auto_handle() + { + if(m_handle != INVALID_HANDLE_VALUE) + CloseHandle(m_handle); + } + + auto_handle(const auto_handle&) = delete; + auto_handle& operator=(const auto_handle&) = delete; + + auto_handle(auto_handle&& other) noexcept + :m_handle{std::exchange(other.m_handle, INVALID_HANDLE_VALUE)} + { + + } + + auto_handle& operator=(auto_handle&& other) noexcept + { + m_handle = std::exchange(other.m_handle, m_handle); + + return *this; + } + + HANDLE release() noexcept + { + return std::exchange(m_handle, INVALID_HANDLE_VALUE); + } + + operator HANDLE() const noexcept + { + return m_handle; + } + + HANDLE* operator&() noexcept + { + return &m_handle; + } + + const HANDLE* operator&() const noexcept + { + return &m_handle; + } + + operator bool() const noexcept + { + return m_handle != INVALID_HANDLE_VALUE; + } + +private: + HANDLE m_handle{INVALID_HANDLE_VALUE}; +}; + +} + +enum class process_options : std::uint32_t +{ + none = 0x00, +#ifdef NES_PROCESS_PIPE_EXTENSION + grab_stdout = 0x10, + grab_stderr = 0x20, + grab_stdin = 0x40 +#endif +}; + +constexpr process_options operator&(process_options left, process_options right) noexcept +{ + return static_cast(static_cast(left) & static_cast(right)); +} + +constexpr process_options& operator&=(process_options& left, process_options right) noexcept +{ + left = left & right; + return left; +} + +constexpr process_options operator|(process_options left, process_options right) noexcept +{ + return static_cast(static_cast(left) | static_cast(right)); +} + +constexpr process_options& operator|=(process_options& left, process_options right) noexcept +{ + left = left | right; + return left; +} + +constexpr process_options operator^(process_options left, process_options right) noexcept +{ + return static_cast(static_cast(left) ^ static_cast(right)); +} + +constexpr process_options& operator^=(process_options& left, process_options right) noexcept +{ + left = left ^ right; + return left; +} + +constexpr process_options operator~(process_options value) noexcept +{ + return static_cast(~static_cast(value)); +} + +class process +{ +public: + using native_handle_type = HANDLE; + using return_code_type = DWORD; + using id = impl::id_t; + +public: + constexpr process() noexcept = default; + + explicit process(const std::string& path, const std::string& working_directory) + :process{path, {}, working_directory, {}}{} + + explicit process(const std::string& path, process_options options) + :process{path, {}, {}, options}{} + + explicit process(const std::string& path, const std::vector& args, process_options options) + :process{path, args, {}, options}{} + + explicit process(const std::string& path, const std::string& working_directory, process_options options) + :process{path, {}, working_directory, options}{} + + explicit process(const std::string& path, std::vector args = std::vector{}, const std::string& working_directory = std::string{}, process_options options [[maybe_unused]] = process_options{}) + { + assert(!std::empty(path) && "nes::process::process called with empty path."); + + SECURITY_ATTRIBUTES security_attributes{}; + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = TRUE; + security_attributes.lpSecurityDescriptor = nullptr; + + #ifdef NES_PROCESS_PIPE_EXTENSION + impl::auto_handle stdin_rd{}; + impl::auto_handle stdout_rd{}; + impl::auto_handle stderr_rd{}; + impl::auto_handle stdin_wr{}; + impl::auto_handle stdout_wr{}; + impl::auto_handle stderr_wr{}; + + if(static_cast(options & process_options::grab_stdin)) + if(!CreatePipe(&stdin_rd, &stdin_wr, &security_attributes, 0) || !SetHandleInformation(stdin_wr, HANDLE_FLAG_INHERIT, 0)) + throw std::runtime_error{"Failed to create stdin pipe. " + get_error_message()}; + + if(static_cast(options & process_options::grab_stdout)) + if(!CreatePipe(&stdout_rd, &stdout_wr, &security_attributes, 0) || !SetHandleInformation(stdout_rd, HANDLE_FLAG_INHERIT, 0)) + throw std::runtime_error{"Failed to create stdout pipe. " + get_error_message()}; + + if(static_cast(options & process_options::grab_stderr)) + if(!CreatePipe(&stderr_rd, &stderr_wr, &security_attributes, 0) || !SetHandleInformation(stderr_rd, HANDLE_FLAG_INHERIT, 0)) + throw std::runtime_error{"Failed to create stderr pipe. " + get_error_message()}; + #endif + + args.insert(std::begin(args), path); + + auto format_arg = [](const std::wstring& arg) -> std::wstring + { + if(arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) + return arg; + + std::wstring out{L"\""}; + for(auto it = std::cbegin(arg); it != std::cend(arg); ++it) + { + if(*it == L'\\') + { + std::size_t count{1}; + while(++it != std::cend(arg) && *it == L'\\') + ++count; + + if(it == std::cend(arg)) + { + out.append(count * 2, L'\\'); + break; + } + else if(*it == L'\"') + { + out.append(count * 2 + 1, L'\\'); + out.push_back(L'\"'); + } + else + { + out.append(count, L'\\'); + out.push_back(*it); + } + } + else if(*it == L'\"') + { + out.push_back(L'\\'); + out.push_back(*it); + } + else + { + out.push_back(*it); + } + } + out.push_back(L'\"'); + + return out; + }; + + std::wstring args_str{}; + for(auto&& arg : args) + args_str += format_arg(to_wide(arg)) + L" "; + + const std::wstring native_working_directory{to_wide(working_directory)}; + const std::wstring native_path{to_wide(path)}; + + STARTUPINFOW startup_info{}; + startup_info.cb = sizeof(STARTUPINFOW); + #ifdef NES_PROCESS_PIPE_EXTENSION + startup_info.hStdInput = stdin_rd; + startup_info.hStdOutput = stdout_wr; + startup_info.hStdError = stderr_wr; + if(static_cast(options) != 0) + startup_info.dwFlags = STARTF_USESTDHANDLES; + #endif + + PROCESS_INFORMATION process_info{}; + if(!CreateProcessW(std::data(native_path), null_or_data(args_str), nullptr, nullptr, TRUE, 0, nullptr, null_or_data(native_working_directory), &startup_info, &process_info)) + throw std::runtime_error{"Failed to create process. " + get_error_message()}; + + m_id = static_cast(process_info.dwProcessId); + m_handle = process_info.hProcess; + m_thread_handle = process_info.hThread; + + #ifdef NES_PROCESS_PIPE_EXTENSION + if(static_cast(options & process_options::grab_stdin)) + { + pipe_streambuf buffer{stdin_wr.release(), std::ios_base::out}; + m_stdin_stream.reset(new pipe_ostream{std::move(buffer)}); + } + + if(static_cast(options & process_options::grab_stdout)) + { + pipe_streambuf buffer{stdout_rd.release(), std::ios_base::in}; + m_stdout_stream.reset(new pipe_istream{std::move(buffer)}); + } + + if(static_cast(options & process_options::grab_stderr)) + { + pipe_streambuf buffer{stderr_rd.release(), std::ios_base::in}; + m_stderr_stream.reset(new pipe_istream{std::move(buffer)}); + } + #endif + } + + ~process() + { + assert(!joinable() && "nes::process::~process() called with joinable() returning true."); + + if(joinable()) + std::terminate(); + } + + process(const process&) = delete; + process& operator=(const process&) = delete; + + process(process&& other) noexcept + :m_id{std::exchange(other.m_id, id{})} + ,m_return_code{std::exchange(other.m_return_code, return_code_type{})} + ,m_handle{std::move(other.m_handle)} + ,m_thread_handle{std::move(other.m_thread_handle)} +#ifdef NES_PROCESS_PIPE_EXTENSION + ,m_stdin_stream{std::move(other.m_stdin_stream)} + ,m_stdout_stream{std::move(other.m_stdout_stream)} + ,m_stderr_stream{std::move(other.m_stderr_stream)} +#endif + { + + } + + process& operator=(process&& other) noexcept + { + if(joinable()) + std::terminate(); + + m_id = std::exchange(other.m_id, m_id); + m_return_code = std::exchange(other.m_return_code, m_return_code); + m_handle = std::move(other.m_handle); + m_thread_handle = std::move(other.m_thread_handle); + #ifdef NES_PROCESS_PIPE_EXTENSION + m_stdin_stream = std::move(other.m_stdin_stream); + m_stdout_stream = std::move(other.m_stdout_stream); + m_stderr_stream = std::move(other.m_stderr_stream); + #endif + + return *this; + } + + void join() + { + assert(joinable() && "nes::process::join() called with joinable() returning false."); + + if(WaitForSingleObject(m_handle, INFINITE)) + throw std::runtime_error{"Failed to join the process. " + get_error_message()}; + + if(!GetExitCodeProcess(m_handle, reinterpret_cast(&m_return_code))) + throw std::runtime_error{"Failed to get the return code of the process. " + get_error_message()}; + + close_process(); + } + + bool joinable() const noexcept + { + return m_handle; + } + + bool active() const + { + if(!m_handle) + return false; + + DWORD result = WaitForSingleObject(m_handle, 0); + if(result == WAIT_FAILED) + throw std::runtime_error{"Failed to get the state of the process. " + get_error_message()}; + + return result == WAIT_TIMEOUT; + } + + void detach() + { + assert(joinable() && "nes::process::detach() called with joinable() returning false."); + + close_process(); + } + + bool kill() + { + assert(joinable() && "nes::process::kill() called with joinable() returning false."); + + if(!TerminateProcess(m_handle, 1)) + return false; + + join(); + + return true; + } + + return_code_type return_code() const noexcept + { + assert(!joinable() && "nes::process::return_code() called with joinable() returning true."); + + return m_return_code; + } + + native_handle_type native_handle() const noexcept + { + return m_handle; + } + + id get_id() const noexcept + { + return m_id; + } + +#ifdef NES_PROCESS_PIPE_EXTENSION + pipe_ostream& stdin_stream() noexcept + { + return *m_stdin_stream; + } + + pipe_istream& stdout_stream() noexcept + { + return *m_stdout_stream; + } + + pipe_istream& stderr_stream() noexcept + { + return *m_stderr_stream; + } +#endif + +private: + std::wstring to_wide(const std::string& path) + { + assert(std::size(path) < 0x7FFFFFFFu && "Wrong path."); + + if(std::empty(path)) + return {}; + + std::wstring out_path{}; + out_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); + + if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(out_path), static_cast(std::size(out_path)))) + throw std::runtime_error{"Failed to convert the path to wide."}; + + return out_path; + } + + std::string get_error_message() const + { + return "#" + std::to_string(GetLastError()); + } + + void close_process() + { + m_id = id{}; + CloseHandle(m_handle.release()); + CloseHandle(m_thread_handle.release()); + } + + wchar_t* null_or_data(std::wstring& str) + { + return std::empty(str) ? nullptr : std::data(str); + } + + const wchar_t* null_or_data(const std::wstring& str) + { + return std::empty(str) ? nullptr : std::data(str); + } + +private: + id m_id{}; + return_code_type m_return_code{}; + impl::auto_handle m_handle{}; + impl::auto_handle m_thread_handle{}; +#ifdef NES_PROCESS_PIPE_EXTENSION + std::unique_ptr m_stdin_stream{}; + std::unique_ptr m_stdout_stream{}; + std::unique_ptr m_stderr_stream{}; +#endif +}; + +namespace this_process +{ + +inline process::id get_id() noexcept +{ + return process::id{GetCurrentProcessId()}; +} + +inline std::string working_directory() +{ + const DWORD size{GetCurrentDirectoryW(0, nullptr)}; + + std::wstring native_path{}; + native_path.resize(static_cast(size)); + GetCurrentDirectoryW(size, std::data(native_path)); + native_path.pop_back(); //Because GetCurrentDirectoryW adds a null terminator + + std::transform(std::begin(native_path), std::end(native_path), std::begin(native_path), [](wchar_t c){return c == L'\\' ? L'/' : c;}); + + std::string path{}; + path.resize(static_cast(WideCharToMultiByte(CP_UTF8, 0, std::data(native_path), static_cast(std::size(native_path)), nullptr, 0, nullptr, nullptr))); + + if(!WideCharToMultiByte(CP_UTF8, 0, std::data(native_path), static_cast(std::size(native_path)), std::data(path), static_cast(std::size(path)), nullptr, nullptr)) + throw std::runtime_error{"Failed to convert the path to UTF-8."}; + + return path; +} + +inline bool change_working_directory(const std::string& path) +{ + std::wstring native_path{}; + native_path.resize(static_cast(MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), nullptr, 0))); + + if(!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, std::data(path), static_cast(std::size(path)), std::data(native_path), static_cast(std::size(native_path)))) + throw std::runtime_error{"Failed to convert the path to wide."}; + + return SetCurrentDirectoryW(std::data(native_path)); +} + +} + +} + +template +std::basic_ostream& operator<<(std::basic_ostream& os, nes::process::id id) +{ + return os << static_cast(id); +} + +namespace std +{ + template<> + struct hash + { + using argument_type = nes::process::id; + using result_type = std::size_t; + + result_type operator()(const argument_type& s) const noexcept + { + return std::hash{}(static_cast(s)); + } + }; +} + +#elif defined(NES_POSIX_PROCESS) + +namespace nes +{ + +class process; + +namespace impl +{ + +enum class id_t : pid_t{}; + +constexpr bool operator==(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) == static_cast(rhs); +} + +constexpr bool operator!=(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) != static_cast(rhs); +} + +constexpr bool operator<(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) < static_cast(rhs); +} + +constexpr bool operator<=(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) <= static_cast(rhs); +} + +constexpr bool operator>(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) > static_cast(rhs); +} + +constexpr bool operator>=(id_t lhs, id_t rhs) noexcept +{ + return static_cast(lhs) >= static_cast(rhs); +} + +#ifdef NES_PROCESS_PIPE_EXTENSION +struct auto_handle +{ + constexpr auto_handle() = default; + auto_handle(int h) + :m_handle{h} + { + + } + + ~auto_handle() + { + if(m_handle) + close(m_handle); + } + + auto_handle(const auto_handle&) = delete; + auto_handle& operator=(const auto_handle&) = delete; + + auto_handle(auto_handle&& other) noexcept + :m_handle{std::exchange(other.m_handle, -1)} + { + + } + + auto_handle& operator=(auto_handle&& other) noexcept + { + m_handle = std::exchange(other.m_handle, m_handle); + + return *this; + } + + int release() noexcept + { + return std::exchange(m_handle, -1); + } + + operator int() noexcept + { + return m_handle; + } + + operator int() const noexcept + { + return m_handle; + } + + int* operator&() noexcept + { + return &m_handle; + } + + const int* operator&() const noexcept + { + return &m_handle; + } + + operator bool() const noexcept + { + return m_handle != -1; + } + + int m_handle{-1}; +}; +#endif + +} + +enum class process_options : std::uint32_t +{ + none = 0x00, +#ifdef NES_PROCESS_PIPE_EXTENSION + grab_stdout = 0x10, + grab_stderr = 0x20, + grab_stdin = 0x40 +#endif +}; + +constexpr process_options operator&(process_options left, process_options right) noexcept +{ + return static_cast(static_cast(left) & static_cast(right)); +} + +constexpr process_options& operator&=(process_options& left, process_options right) noexcept +{ + left = left & right; + return left; +} + +constexpr process_options operator|(process_options left, process_options right) noexcept +{ + return static_cast(static_cast(left) | static_cast(right)); +} + +constexpr process_options& operator|=(process_options& left, process_options right) noexcept +{ + left = left | right; + return left; +} + +constexpr process_options operator^(process_options left, process_options right) noexcept +{ + return static_cast(static_cast(left) ^ static_cast(right)); +} + +constexpr process_options& operator^=(process_options& left, process_options right) noexcept +{ + left = left ^ right; + return left; +} + +constexpr process_options operator~(process_options value) noexcept +{ + return static_cast(~static_cast(value)); +} + +class process +{ +public: + using native_handle_type = pid_t; + using return_code_type = int; + using id = impl::id_t; + +public: + constexpr process() noexcept = default; + + explicit process(const std::string& path, const std::string& working_directory) + :process{path, {}, working_directory, {}}{} + + explicit process(const std::string& path, process_options options) + :process{path, {}, {}, options}{} + + explicit process(const std::string& path, const std::vector& args, process_options options) + :process{path, args, {}, options}{} + + explicit process(const std::string& path, const std::string& working_directory, process_options options) + :process{path, {}, working_directory, options}{} + + explicit process(const std::string& path, std::vector args = std::vector{}, const std::string& working_directory = std::string{}, process_options options [[maybe_unused]] = process_options{}) + { + assert(!std::empty(path) && "nes::process::process called with empty path."); + + args.insert(std::begin(args), path); + + std::vector native_args{}; + native_args.resize(std::size(args) + 1); + for(std::size_t i{}; i < std::size(args); ++i) + native_args[i] = std::data(args[i]); + + #ifdef NES_PROCESS_PIPE_EXTENSION + impl::auto_handle stdin_fd[2]{}; + impl::auto_handle stdout_fd[2]{}; + impl::auto_handle stderr_fd[2]{}; + + if(static_cast(options & process_options::grab_stdin) && pipe(reinterpret_cast(stdin_fd))) + throw std::runtime_error{"Failed to create stdin pipe. " + std::string{strerror(errno)}}; + + if(static_cast(options & process_options::grab_stdout) && pipe(reinterpret_cast(stdout_fd))) + throw std::runtime_error{"Failed to create stdout pipe. " + std::string{strerror(errno)}}; + + if(static_cast(options & process_options::grab_stderr) && pipe(reinterpret_cast(stderr_fd))) + throw std::runtime_error{"Failed to create stderr pipe. " + std::string{strerror(errno)}}; + + const bool standard_streams{static_cast(options & process_options::grab_stdin) || static_cast(options & process_options::grab_stdout) || static_cast(options & process_options::grab_stderr)}; + #else + constexpr bool standard_streams{false}; + #endif + + const pid_t id{fork()}; + if(id < 0) + { + throw std::runtime_error{"Failed to create process. " + std::string{strerror(errno)}}; + } + else if(id == 0) + { + + #ifdef NES_PROCESS_PIPE_EXTENSION + if(static_cast(options & process_options::grab_stdin)) + if(dup2(stdin_fd[0], 0) == -1) + _exit(EXIT_FAILURE); + + if(static_cast(options & process_options::grab_stdout)) + if(dup2(stdout_fd[1], 1) == -1) + _exit(EXIT_FAILURE); + + if(static_cast(options & process_options::grab_stderr)) + if(dup2(stderr_fd[1], 2) == -1) + _exit(EXIT_FAILURE); + #endif + + if(!std::empty(working_directory)) + if(chdir(std::data(working_directory))) + _exit(EXIT_FAILURE); + + execv(std::data(path), std::data(native_args)); + _exit(EXIT_FAILURE); + } + + m_id = id; + + #ifdef NES_PROCESS_PIPE_EXTENSION + if(static_cast(options & process_options::grab_stdin)) + m_stdin_stream.reset(new pipe_ostream{pipe_streambuf{stdin_fd[1].release(), std::ios_base::out}}); + + if(static_cast(options & process_options::grab_stdout)) + m_stdout_stream.reset(new pipe_istream{pipe_streambuf{stdout_fd[0].release(), std::ios_base::in}}); + + if(static_cast(options & process_options::grab_stderr)) + m_stderr_stream.reset(new pipe_istream{pipe_streambuf{stderr_fd[0].release(), std::ios_base::in}}); + #endif + } + + ~process() + { + assert(!joinable() && "nes::process::~process() called with joinable() returning true."); + + if(joinable()) + std::terminate(); + } + + process(const process&) = delete; + process& operator=(const process&) = delete; + + process(process&& other) noexcept + :m_id{std::exchange(other.m_id, -1)} + ,m_return_code{std::exchange(other.m_return_code, return_code_type{})} +#ifdef NES_PROCESS_PIPE_EXTENSION + ,m_stdin_stream{std::move(other.m_stdin_stream)} + ,m_stdout_stream{std::move(other.m_stdout_stream)} + ,m_stderr_stream{std::move(other.m_stderr_stream)} +#endif + { + + } + + process& operator=(process&& other) noexcept + { + if(joinable()) + std::terminate(); + + m_id = std::exchange(other.m_id, m_id); + m_return_code = std::exchange(other.m_return_code, m_return_code); + #ifdef NES_PROCESS_PIPE_EXTENSION + m_stdin_stream = std::move(other.m_stdin_stream); + m_stdout_stream = std::move(other.m_stdout_stream); + m_stderr_stream = std::move(other.m_stderr_stream); + #endif + + return *this; + } + + void join() + { + assert(joinable() && "nes::process::join() called with joinable() returning false."); + + int return_code{}; + if(waitpid(m_id, &return_code, 0) == -1) + throw std::runtime_error{"Failed to join the process. " + std::string{strerror(errno)}}; + + m_id = -1; + m_return_code = WEXITSTATUS(return_code); + } + + bool joinable() const noexcept + { + return m_id != -1; + } + + bool active() const + { + return ::kill(m_id, 0) != ESRCH; + } + + void detach() + { + assert(joinable() && "nes::process::detach() called with joinable() returning false."); + m_id = -1; + } + + bool kill() + { + assert(joinable() && "nes::process::kill() called with joinable() returning false."); + + if(::kill(m_id, SIGTERM)) + return false; + + join(); + + return true; + } + + return_code_type return_code() const noexcept + { + assert(!joinable() && "nes::process::return_code() called with joinable() returning true."); + + return m_return_code; + } + + native_handle_type native_handle() const noexcept + { + return m_id; + } + + id get_id() const noexcept + { + return static_cast(m_id); + } + +#ifdef NES_PROCESS_PIPE_EXTENSION + pipe_ostream& stdin_stream() noexcept + { + return *m_stdin_stream; + } + + pipe_istream& stdout_stream() noexcept + { + return *m_stdout_stream; + } + + pipe_istream& stderr_stream() noexcept + { + return *m_stderr_stream; + } +#endif + +private: + native_handle_type m_id{}; + return_code_type m_return_code{}; +#ifdef NES_PROCESS_PIPE_EXTENSION + std::unique_ptr m_stdin_stream{}; + std::unique_ptr m_stdout_stream{}; + std::unique_ptr m_stderr_stream{}; +#endif +}; + +namespace this_process +{ + +inline process::id get_id() noexcept +{ + return process::id{getpid()}; +} + +inline std::string working_directory() +{ + std::string path{}; + path.resize(256); + + while(!getcwd(std::data(path), std::size(path))) + { + if(errno == ERANGE) + path.resize(std::size(path) * 2); + else + throw std::runtime_error{"Failed to get the current working directory. " + std::string{strerror(errno)}}; + } + + path.resize(path.find_first_of('\0')); + + return path; +} + +inline bool change_working_directory(const std::string& path) +{ + return chdir(std::data(path)) == 0; +} + +} + +} + +template +std::basic_ostream& operator<<(std::basic_ostream& os, nes::process::id id) +{ + return os << static_cast(id); +} + +namespace std +{ + template<> + struct hash + { + using argument_type = nes::process::id; + using result_type = std::size_t; + + result_type operator()(const argument_type& s) const noexcept + { + return std::hash{}(static_cast(s)); + } + }; +} + + +#endif + +#endif diff --git a/Telegram/SourceFiles/ayu/libs/sqlite/sqlite_orm.h b/Telegram/SourceFiles/ayu/libs/sqlite/sqlite_orm.h index 17a9bc9cb..53e11f569 100644 --- a/Telegram/SourceFiles/ayu/libs/sqlite/sqlite_orm.h +++ b/Telegram/SourceFiles/ayu/libs/sqlite/sqlite_orm.h @@ -362,7 +362,7 @@ namespace sqlite_orm { } #pragma once -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::error_code, std::system_error #include // std::string @@ -7458,7 +7458,7 @@ namespace sqlite_orm { } #pragma once -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::unique_ptr #include // std::integral_constant @@ -7917,7 +7917,7 @@ namespace sqlite_orm { } #pragma once -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::enable_if_t, std::is_arithmetic, std::is_same, std::true_type, std::false_type, std::make_index_sequence, std::index_sequence #include // std::default_delete @@ -8371,7 +8371,7 @@ namespace sqlite_orm { } #pragma once -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::enable_if_t, std::is_arithmetic, std::is_same, std::enable_if #include // atof, atoi, atoll @@ -8789,7 +8789,7 @@ namespace sqlite_orm { } #pragma once -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::string #include // std::move @@ -9978,7 +9978,7 @@ namespace sqlite_orm { } #pragma once -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::unique_ptr/shared_ptr, std::make_unique/std::make_shared #include // std::system_error @@ -10019,12 +10019,12 @@ namespace sqlite_orm { // #include "mapped_row_extractor.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" // #include "object_from_column_builder.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::is_member_object_pointer // #include "functional/static_magic.h" @@ -10282,7 +10282,7 @@ namespace sqlite_orm { // #include "function.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include #include // std::string #include // std::tuple @@ -10809,7 +10809,7 @@ namespace sqlite_orm { // #include "view.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::string #include // std::forward, std::move #include // std::tuple, std::make_tuple @@ -10820,7 +10820,7 @@ namespace sqlite_orm { // #include "iterator.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::shared_ptr, std::unique_ptr, std::make_shared #include // std::decay #include // std::move @@ -10946,7 +10946,7 @@ namespace sqlite_orm { // #include "prepared_statement.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::unique_ptr #include // std::iterator_traits @@ -10964,7 +10964,7 @@ namespace sqlite_orm { // #include "connection_holder.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include #include // std::string @@ -12893,7 +12893,7 @@ namespace sqlite_orm { // #include "storage_base.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::function, std::bind #include // std::string @@ -12914,7 +12914,7 @@ namespace sqlite_orm { // #include "pragma.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::string #include // std::function @@ -13556,7 +13556,7 @@ namespace sqlite_orm { // #include "limit_accessor.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::map #include // std::function @@ -13782,7 +13782,7 @@ namespace sqlite_orm { // #include "backup.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::system_error #include // std::string @@ -13860,7 +13860,7 @@ namespace sqlite_orm { // #include "values_to_tuple.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" #include // std::index_sequence, std::make_index_sequence #include // std::tuple, std::tuple_size, std::get @@ -13871,7 +13871,7 @@ namespace sqlite_orm { // #include "arg_values.h" -#include "ayu/sqlite/sqlite3.h" +#include "ayu/libs/sqlite/sqlite3.h" // #include "row_extractor.h" diff --git a/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.cpp b/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.cpp index 97c5f06a3..01cd2e039 100644 --- a/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.cpp +++ b/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.cpp @@ -6,6 +6,65 @@ // Copyright @Radolyn, 2023 #include "ayu_sync_controller.h" +#include "ayu/libs/process.hpp" +#include "ayu/sync/utils/process_utils.hpp" + +#include +#include namespace AyuSync { -} // AyuSync \ No newline at end of file + std::optional controller = std::nullopt; + + bool isAgentDownloaded() { + return std::filesystem::exists(AgentPath); + } + + bool isAgentRunning() { + return is_process_running(AgentFilename); + } + + void initialize() { + if (controller.has_value()) { + return; + } + + controller = ayu_sync_controller(); + } + + ayu_sync_controller &getControllerInstance() { + initialize(); + return controller.value(); + } + + void ayu_sync_controller::initializeAgent() { + if (!isAgentDownloaded()) { + return; + } + + if (!isAgentRunning()) { + auto configPath = std::filesystem::absolute("./tdata/sync_preferences.json"); + auto process = nes::process{AgentPath, {configPath.string()}}; + process.detach(); + } + + pipe = std::make_unique(); + + std::thread receiverThread(&ayu_sync_controller::receiver, this); + } + + void ayu_sync_controller::receiver() { + while (true) { + auto p = pipe->receive(); + invokeHandler(p); + } + } + + void ayu_sync_controller::invokeHandler(json p) { + DEBUG_LOG(("Invoking handler on %1").arg(p.dump().c_str())); + + auto userId = p["userId"].get(); + auto type = p["type"].get(); + + DEBUG_LOG(("userId: %1, type: %1").arg(userId).arg(type.c_str())); + } +} \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.h b/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.h index 138eda5a4..a9dd95e72 100644 --- a/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.h +++ b/Telegram/SourceFiles/ayu/sync/ayu_sync_controller.h @@ -7,10 +7,34 @@ #pragma once +#include "ayu/libs/json.hpp" +#include "utils/ayu_pipe_wrapper.h" + +using json = nlohmann::json; + +const std::string AgentFilename = +#ifdef _WIN32 + "AyuSyncAgent.exe"; +#else + "AyuSyncAgent"; +#endif + +const std::string AgentPath = "./AyuSync/" + AgentFilename; + namespace AyuSync { - class ayu_sync_controller { + public: + void initializeAgent(); + void invokeHandler(json p); + private: + void receiver(); + + std::unique_ptr pipe; }; -} // AyuSync + ayu_sync_controller &getControllerInstance(); + + bool isAgentDownloaded(); + bool isAgentRunning(); +} diff --git a/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.cpp b/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.cpp index e5aabff67..4db9937b2 100644 --- a/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.cpp +++ b/Telegram/SourceFiles/ayu/sync/utils/ayu_pipe_wrapper.cpp @@ -6,7 +6,6 @@ // Copyright @Radolyn, 2023 #include -#include #include "ayu_pipe_wrapper.h" #include "ayu/libs/bit_converter.hpp" diff --git a/Telegram/SourceFiles/ayu/sync/utils/process_utils.hpp b/Telegram/SourceFiles/ayu/sync/utils/process_utils.hpp new file mode 100644 index 000000000..958c0c485 --- /dev/null +++ b/Telegram/SourceFiles/ayu/sync/utils/process_utils.hpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#include +#include + +#else +#include +#include +#endif + +// A function to check if a process is running by its name +// Bing AI generated +bool is_process_running(const std::string &name) { +#ifdef _WIN32 + // Create a snapshot of all processes + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + std::cerr << "Failed to create snapshot\n"; + return false; + } + // Iterate over the processes and compare the names + PROCESSENTRY32 entry; + entry.dwSize = sizeof(entry); + if (!Process32First(snapshot, &entry)) { + std::cerr << "Failed to get first process\n"; + CloseHandle(snapshot); + return false; + } + do { + std::wstring_convert> converter; + std::string entry_name = converter.to_bytes(entry.szExeFile); + if (name == entry_name) { + // Found a match + CloseHandle(snapshot); + return true; + } + } while (Process32Next(snapshot, &entry)); + // No match found + CloseHandle(snapshot); + return false; +#else + // Open the /proc directory + DIR* dir = opendir("/proc"); + if (dir == nullptr) { + std::cerr << "Failed to open /proc\n"; + return false; + } + // Read the subdirectories + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + // Check if the subdirectory is a number (pid) + std::string pid = entry->d_name; + if (std::all_of(pid.begin(), pid.end(), isdigit)) { + // Read the /proc/pid/cmdline file + std::string cmdline_file = "/proc/" + pid + "/cmdline"; + std::ifstream file(cmdline_file); + if (file.is_open()) { + // Get the first word of the file (process name) + std::string process_name; + std::getline(file, process_name, '\0'); + // Remove the path if present + size_t pos = process_name.rfind('/'); + if (pos != std::string::npos) { + process_name = process_name.substr(pos + 1); + } + // Compare the names + if (name == process_name) { + // Found a match + closedir(dir); + return true; + } + } + } + } + // No match found + closedir(dir); + return false; +#endif +} \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp index ab02316ef..20ff89bc8 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp @@ -5,6 +5,7 @@ // // Copyright @Radolyn, 2023 +#include "ayu/sync/ayu_sync_controller.h" #include "ayu/ui/boxes/edit_edited_mark.h" #include "ayu/ui/boxes/edit_deleted_mark.h" #include "ayu/ayu_settings.h" @@ -325,6 +326,23 @@ namespace Settings { }); } + void Ayu::SetupAyuSync(not_null container) { + AddSubsectionTitle(container, rpl::single(QString("AyuSync"))); + + auto text = AyuSync::isAgentDownloaded() ? + QString("Open preferences") : + QString("Download agent"); + + AddButton( + container, + rpl::single(text), + st::settingsButtonNoIcon + )->addClickHandler([=] { + auto controller = &AyuSync::getControllerInstance(); + controller->initializeAgent(); + }); + } + void Ayu::SetupBetaFunctions(not_null container) { auto settings = &AyuSettings::getInstance(); @@ -393,6 +411,9 @@ namespace Settings { AddSkip(container); SetupCustomization(container, controller); + AddSkip(container); + SetupAyuSync(container); + AddSkip(container); SetupBetaFunctions(container); diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.h b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.h index cf6826c88..1ad2a0700 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.h +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.h @@ -31,14 +31,14 @@ namespace Settings { void SetupQoLToggles(not_null container); - void - SetupCustomization(not_null container, not_null controller); + void SetupCustomization(not_null container, not_null controller); - void - SetupShowPeerId(not_null container, not_null controller); + void SetupShowPeerId(not_null container, not_null controller); void SetupRecentStickersLimitSlider(not_null container); + void SetupAyuSync(not_null container); + void SetupBetaFunctions(not_null container); void SetupAyuGramSettings(not_null container, not_null null);