#include "client/client.hh" namespace client { // Args passed to handle functions, the input line split by spacebars. Similar // to argv, the first is the program name (or in this case command name). static std::string read_cin(const char* const msg) noexcept { std::cout << msg << ": "; std::string line; std::getline(std::cin, line); return line; } static std::pair& get_address_port(const char* const address = nullptr, const char* const port = nullptr) { static std::pair pair = [&]() { return std::make_pair(address, port); }(); return pair; } static shared::contents_t urecv_contents(shared::connection& connection) { const auto ret = [&]() { for (;;) { const auto ret = shared::urecv_packet(connection.get_socket()); if (connection.should_discard_packet(ret->packet)) { continue; } return ret; } }(); const shared::contents_t contents = shared::packet_to_contents(ret->packet); // error checking early out const auto find_it = contents.find("success"); if (find_it != std::end(contents)) { if (find_it->second == "error") { throw std::runtime_error(contents.find("message")->second); } } return contents; } static shared::socket_t get_new_rsock() { const auto& addr_port = get_address_port(); const auto info = [&]() { addrinfo a{}; a.ai_flags = AI_PASSIVE; a.ai_family = AF_INET; a.ai_socktype = SOCK_STREAM; return shared::make_addrinfo(addr_port.first.c_str(), addr_port.second.c_str(), std::move(a)); }(); const shared::socket_t socket = shared::make_socket(info); shared::connect_socket(socket, info); return socket; } using args_t = std::vector; static void handle_create_thread(const args_t& args, shared::connection& connection) { if (args.size() != 2) { throw std::invalid_argument("invalid syntax"); } const std::string& thread_title = args[1]; { // send request const shared::contents_t contents{ {"thread_title", thread_title}, }; connection.send_packet(shared::contents_to_packet(contents, "CRT")); } const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); const auto success_it = contents.find("success"); if (message_it == std::end(contents) || success_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; } static void handle_post_message(const args_t& args, shared::connection& connection) { if (args.size() < 3) { throw std::invalid_argument("invalid syntax"); } const std::string& thread_title = args[1]; const std::string message = [&]() { std::string message = args[2]; for (auto i = 3ul; i < args.size(); ++i) { message += ' ' + args[i]; } return message; }(); connection.send_packet(shared::contents_to_packet( {{"thread_title", thread_title}, {"message", message}}, "MSG")); const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); if (message_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; } static void handle_delete_message(const args_t& args, shared::connection& connection) { if (args.size() != 3) { throw std::invalid_argument("invalid syntax"); } const std::string& thread_title = args[1]; const std::string& message_number = args[2]; try { std::stoi(message_number); } catch (...) { throw std::invalid_argument{"failed to parse \"" + message_number + "\" as an integer"}; } connection.send_packet(shared::contents_to_packet( {{"thread_title", thread_title}, {"message_number", message_number}}, "DLT")); const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); if (message_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; } static void handle_edit_message(const args_t& args, shared::connection& connection) { if (args.size() < 4) { throw std::invalid_argument("invalid syntax"); } const std::string& thread_title = args[1]; const std::string& message_number = args[2]; const std::string message = [&]() { std::string message = args[3]; for (auto i = 4ul; i < args.size(); ++i) { message += ' ' + args[i]; } return message; }(); try { std::stoi(message_number); } catch (...) { throw std::invalid_argument{"failed to parse \"" + message_number + "\" as an integer"}; } connection.send_packet( shared::contents_to_packet({{"thread_title", thread_title}, {"message_number", message_number}, {"message", message}}, "EDT")); const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); if (message_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; } static void handle_list_threads(const args_t& args, shared::connection& connection) { if (args.size() != 1) { throw std::invalid_argument("invalid syntax"); } { connection.send_packet(shared::contents_to_packet({}, "LST")); } const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); if (message_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; } static void handle_read_thread(const args_t& args, shared::connection& connection) { if (args.size() != 2) { throw std::invalid_argument("invalid syntax"); } const std::string& thread_title = args[1]; { // send request const shared::contents_t contents{ {"thread_title", thread_title}, }; connection.send_packet(shared::contents_to_packet(contents, "RDT")); } const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); const auto success_it = contents.find("success"); if (message_it == std::end(contents) || success_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; } static void handle_upload_file(const args_t& args, shared::connection& connection) { if (args.size() != 3) { throw std::invalid_argument("invalid syntax"); } const std::string& thread_title = args[1]; const std::string& filename = args[2]; const std::string file_contents = [&]() -> std::string { std::ifstream in{"./" + filename}; if (!in.is_open()) { throw std::invalid_argument("file not found"); } std::stringstream ss; ss << in.rdbuf(); return ss.str(); }(); { const shared::contents_t contents{{"thread_title", thread_title}, {"filename", filename}}; connection.send_packet(shared::contents_to_packet(contents, "UPD")); } { const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); const auto success_it = contents.find("success"); if (message_it == std::end(contents) || success_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; if (success_it->second != "true") { return; } } { // send file const shared::socket_t rsock = get_new_rsock(); shared::scoped_function close_rsock{[rsock]() { close(rsock); }}; const shared::contents_t contents{{"file_contents", file_contents}}; const shared::packet packet = shared::contents_to_packet(contents, "UPD"); shared::send_packet(packet, rsock); } { const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); const auto success_it = contents.find("success"); if (message_it == std::end(contents) || success_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; } } static void handle_download_file(const args_t& args, shared::connection& connection) { if (args.size() != 3) { throw std::invalid_argument("invalid syntax"); } const std::string& thread_title = args[1]; const std::string& filename = args[2]; { const shared::contents_t contents{{"thread_title", thread_title}, {"filename", filename}}; connection.send_packet(shared::contents_to_packet(contents, "DWN")); } { const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); const auto success_it = contents.find("success"); if (message_it == std::end(contents) || success_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; if (success_it->second != "true") { return; } } const auto file_contents = []() { const shared::socket_t rsock = get_new_rsock(); shared::scoped_function close_rsock{[rsock]() { close(rsock); }}; const auto packet = shared::rrecv_packet(rsock); return packet_to_contents(*packet); }(); const auto data_it = file_contents.find("file_contents"); const auto message_it = file_contents.find("message"); if (data_it == std::end(file_contents) || message_it == std::end(file_contents)) { return; } std::ofstream out{filename, std::ios_base::trunc}; if (!out.is_open()) { throw std::runtime_error{"failed to write file " + filename}; } out << data_it->second; std::cout << message_it->second; } static void handle_remove_thread(const args_t& args, shared::connection& connection) { if (args.size() != 2) { throw std::invalid_argument("invalid syntax"); } const auto& thread_title = args[1]; { const shared::contents_t contents{ {"thread_title", thread_title}, }; connection.send_packet(shared::contents_to_packet(contents, "RMV")); } const shared::contents_t contents = urecv_contents(connection); const auto message_it = contents.find("message"); const auto success_it = contents.find("success"); if (message_it == std::end(contents) || success_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; return; } std::cout << message_it->second; } static void authenticate(shared::connection& connection) { const auto send_auth_var = [&](const char* const varname) { for (;;) { // send username if (shared::should_exit) { throw shared::should_exit_exception{}; } const std::string var = read_cin(varname); if (var.length() == 0) { continue; } { // request server for status on username const shared::contents_t contents{{varname, var}}; connection.send_packet( shared::contents_to_packet(contents, "ATH")); } const shared::contents_t contents = urecv_contents(connection); const auto success_it = contents.find("success"); const auto message_it = contents.find("message"); if (message_it == std::end(contents) || success_it == std::end(contents)) { std::cout << "\treceived bad response from the server, try again\n"; continue; } std::cout << message_it->second; if (success_it->second == "true") { break; } } }; send_auth_var("username"); send_auth_var("password"); } static void interact(shared::connection& connection) { static const std::unordered_map commands{{"CRT", handle_create_thread}, {"MSG", handle_post_message}, {"DLT", handle_delete_message}, {"EDT", handle_edit_message}, {"LST", handle_list_threads}, {"RDT", handle_read_thread}, {"UPD", handle_upload_file}, {"DWN", handle_download_file}, {"RMV", handle_remove_thread}}; const auto print_commands = [&]() { std::cout << "\tavailable commands: "; for (const auto& pair : commands) { const auto& key = pair.first; std::cout << key << ' '; } std::cout << "(or \'XIT\' to quit)\n"; }; print_commands(); for (;;) { if (shared::should_exit) { throw shared::should_exit_exception{}; } const std::string input = read_cin("command"); if (input.length() <= 0) { continue; } const std::vector args = [&]() { // string split args std::vector ret; std::stringstream ss{std::move(input)}; std::string arg; while (std::getline(ss, arg, ' ')) { if (arg.size() <= 0) { continue; } ret.emplace_back(std::move(arg)); } return ret; }(); if (args.size() <= 0) { continue; } const std::string command = [&]() { std::string command; std::transform(std::begin(args[0]), std::end(args[0]), std::back_inserter(command), [](const char c) { return toupper(c); }); return command; }(); if (command == "XIT") { break; } const auto find_it = commands.find(command); if (find_it == std::end(commands)) { // unknown command, print commands print_commands(); continue; } try { const auto& func = find_it->second; func(args, connection); } catch (const std::invalid_argument& e) { std::cout << "\tbad arguments: " << e.what() << '\n'; } catch (const std::runtime_error& e) { std::cout << "\truntime error: " << e.what() << '\n'; } } } void do_client(const char* const address, const char* const port) { const shared::socket_t usock = [&]() -> shared::socket_t { get_address_port(address, port); const auto info = [&]() { // no designated initialisers :( addrinfo a{}; a.ai_flags = AI_PASSIVE; a.ai_family = AF_INET; a.ai_socktype = SOCK_DGRAM; return shared::make_addrinfo("0.0.0.0", 0, std::move(a)); }(); const shared::socket_t socket = shared::make_socket(info); shared::bind_socket(socket, info); return socket; }(); shared::connection connection = [&]() { sockaddr_in dest{}; dest.sin_family = AF_INET; dest.sin_port = htons(static_cast(std::stoi(port))); inet_aton(address, &dest.sin_addr); return shared::connection{usock, std::move(dest)}; }(); try { authenticate(connection); interact(connection); } catch (const shared::should_exit_exception&) { // exit gracefully on should_exit flag } // upload exit before leaving connection.send_packet(shared::contents_to_packet({}, "XIT")); } } // namespace client