aboutsummaryrefslogtreecommitdiff
path: root/comp3331/server/src/client/client.cc
diff options
context:
space:
mode:
Diffstat (limited to 'comp3331/server/src/client/client.cc')
-rw-r--r--comp3331/server/src/client/client.cc519
1 files changed, 519 insertions, 0 deletions
diff --git a/comp3331/server/src/client/client.cc b/comp3331/server/src/client/client.cc
new file mode 100644
index 0000000..9ec2ab2
--- /dev/null
+++ b/comp3331/server/src/client/client.cc
@@ -0,0 +1,519 @@
+#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<std::string, std::string>&
+get_address_port(const char* const address = nullptr,
+ const char* const port = nullptr) {
+ static std::pair<std::string, std::string> pair = [&]() {
+ return std::make_pair<std::string, std::string>(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<std::string>;
+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<std::string,
+ decltype(&handle_create_thread)>
+ 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<std::string> args = [&]() { // string split args
+ std::vector<std::string> 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::uint16_t>(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