#include "ClientCLI.h" #include "tlibc/filesystem.h" #include "tlibc/term.h" #include "network/tcp-chat-protocol/v1.h" #include "tim.h" static const str greeting_art = STR( " ^,,^ ╱|\n" " ( •·•) Meum! (o.o`7\n" " / ` | Meum... |`˜ \\\n" "\\(_,J J L l`,)/\n" ); static const str farewell_art = STR( " ^,,^ ╱|\n" " ( -.-) (>,<`7\n" " / ` | Goodbye! |`˜ \\\n" "\\(_,J J L l`,)/\n" ); #define is_alias(LITERAL) str_equals(command, STR(LITERAL)) Result(bool) start_screen(Array(char) username_buf, Array(char) password_buf, str* out_username, str* out_password); static Result(void) ClientCLI_openUserDB(ClientCLI* self); static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop); static Result(SavedServer*) ClientCLI_joinNewServer(ClientCLI* self); static Result(SavedServer*) ClientCLI_selectServerFromCache(ClientCLI* self); static Result(void) ClientCLI_showSavedServer(ClientCLI* self, SavedServer* server); static Result(void) ClientCLI_register(ClientCLI* self); static Result(void) ClientCLI_login(ClientCLI* self); void ClientCLI_destroy(ClientCLI* self){ if(!self) return; Client_free(self->client); ClientQueries_free(self->queries); tsqlite_connection_close(self->db); List_SavedServer_destroyWithElements(&self->saved_servers, SavedServer_destroy); } void ClientCLI_construct(ClientCLI* self){ zeroStruct(self); self->saved_servers = List_SavedServer_alloc(0); } Result(void) ClientCLI_run(ClientCLI* self) { Deferral(32); // ask username and password Array(char) username_buf = Array_char_alloc(USERNAME_SIZE_MAX + 1); Array(char) password_buf = Array_char_alloc(PASSWORD_SIZE_MAX + 1); Defer( Array_char_destroy(&username_buf); Array_char_destroy(&password_buf); ); str username = str_null, password = str_null; try(bool start, i, start_screen(username_buf, password_buf, &username, &password)); if(!start){ Return RESULT_VOID; } tim_reset_terminal(); term_clear(); printf(FMT_str"\n", greeting_art.len, greeting_art.data); // create client try(self->client, p, Client_create(username, password)); // erase password from memory Array_char_memset(&password_buf, 0); // init db try_void(ClientCLI_openUserDB(self)); char input_buf[1024]; str command_input = str_null; bool stop = false; while(!stop){ sleepMsec(50); printf("> "); try_void(term_readLine(input_buf, sizeof(input_buf))); command_input = str_from_cstr(input_buf); str_trim(&command_input, true); if(command_input.len == 0) continue; ResultVar(void) com_result = ClientCLI_execCommand(self, command_input, &stop); if(com_result.error){ Error_addCallPos(com_result.error, ErrorCallPos_here()); str e_str = Error_toStr(com_result.error); printf("\n"FMT_str"\n", e_str.len, e_str.data); str_destroy(e_str); Error_free(com_result.error); } } Return RESULT_VOID; } static Result(void) ClientCLI_openUserDB(ClientCLI* self){ Deferral(8); str username = Client_getUserName(self->client); // TODO: encrypt user database // Array(u8) user_data_key = Client_getUserDataKey(self->client); // build database file path try(char* user_dir, p, path_getUserDir()); Defer(free(user_dir)); char* db_path = strcat_malloc( user_dir, path_seps".local"path_seps"tcp-chat-client"path_seps"user-db"path_seps, username.data, ".sqlite" ); Defer(free(db_path)); printf("loading database '%s'\n", db_path); try(self->db, p, ClientDatabase_open(db_path)); try(self->queries, p, ClientQueries_compile(self->db)); // load whole servers table to list try_void(SavedServer_getAll(self->queries, &self->saved_servers)); Return RESULT_VOID; } static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop){ Deferral(64); if(is_alias("q") || is_alias("quit") || is_alias("exit")){ printf(FMT_str"\n", farewell_art.len, farewell_art.data); *stop = true; } else if(is_alias("clear")){ term_clear(); } else if(is_alias("h") || is_alias("help")){ printf( "COMMANDS:\n" "Without connection:\n" " h, help Show this message.\n" " q, quit, exit Close the program.\n" " clear Clear the screen.\n" "Connection:\n" " j, join Join a new server and select it.\n" " s, select Select a server you joined before.\n" "After connection:\n" " r, register Create account on selected server\n" " l, login Authorize on selected server\n" "Authorized:\n" ); } else if (is_alias("j") || is_alias("join")){ // ask address and key, connect to server try_void(ClientCLI_joinNewServer(self)); } else if(is_alias("s") || is_alias("select")){ // show scrollable list of servers, get selected one try_void(ClientCLI_selectServerFromCache(self)); } else if(is_alias("r") || is_alias("register")){ try_void(ClientCLI_register(self)); } else if(is_alias("l") || is_alias("login")){ try_void(ClientCLI_login(self)); // TODO: call Client_runIO(): // function with infinite loop which sends and receives messages // with navigation across server channels } else { printf("ERROR: unknown command.\n" "Use 'h' to see list of avaliable commands\n"); } Return RESULT_VOID; } static Result(void) ClientCLI_joinNewServer(ClientCLI* self){ Deferral(8); bool success = false; // ask server address const u32 address_alloc_size = HOSTADDR_SIZE_MAX + 1; str address = str_construct((char*)malloc(address_alloc_size), address_alloc_size, true); Defer(if(!success) str_destroy(address)); printf("Enter server address (ip:port):\n"); try_void(term_readLine(address.data, address.len)); address.len = strlen(address.data); str_trim(&address, true); // ask server public key const u32 server_pk_alloc_size = PUBLIC_KEY_BASE64_SIZE_MAX + 1; str server_pk = str_construct((char*)malloc(server_pk_alloc_size), server_pk_alloc_size, true); Defer(if(!success) str_destroy(server_pk)); printf("Enter server public key (RSA-Public-:):\n"); try_void(term_readLine(server_pk.data, server_pk.len)); server_pk.len = strlen(server_pk.data); str_trim(&server_pk, true); printf("Connecting to server...\n"); try_void(Client_connect(self->client, address.data, server_pk.data)); printf("Connection established\n"); str server_name = str_null; try_void(Client_getServerName(self->client, &server_name)); Defer(if(!success) str_destroy(server_name)); str server_description = str_null; try_void(Client_getServerDescription(self->client, &server_description)); Defer(if(!success) str_destroy(server_description)); SavedServer server = SavedServer_construct( address, server_pk, server_name, server_description ); try_void(SavedServer_createOrUpdate(self->queries, &server)); List_SavedServer_pushMany(&self->saved_servers, &server, 1); try_void(ClientCLI_showSavedServer(self, &server)); success = true; Return RESULT_VOID; } static Result(void) ClientCLI_selectServerFromCache(ClientCLI* self){ Deferral(8); bool success = false; u32 servers_count = self->saved_servers.len; if(servers_count == 0){ printf("No saved servers found\n"); Return RESULT_VOID; } for(u32 i = 0; i < servers_count; i++){ SavedServer* server = &self->saved_servers.data[i]; printf("[%02u] "FMT_str" "FMT_str"\n", i, str_unwrap(server->address), str_unwrap(server->name)); } char buf[32]; u32 selected_i = -1; while(true) { printf("Type 'q' to cancel\n"); printf("Select server number: "); try_void(term_readLine(buf, sizeof(buf))); str input_line = str_from_cstr(buf); str_trim(&input_line, true); if(str_equals(input_line, STR("q"))){ Return RESULT_VOID; } if(sscanf(buf, FMT_u32, &selected_i) != 1){ printf("ERROR: not a number\n"); } else if(selected_i >= servers_count){ printf("ERROR: not a server number\n"); } else break; } SavedServer* selected_server = &self->saved_servers.data[selected_i]; printf("Connecting to '"FMT_str"'...\n", str_unwrap(selected_server->address)); try_void(Client_connect(self->client, selected_server->address.data, selected_server->pk_base64.data)); printf("Connection established\n"); // update server name bool server_info_changed = false; str updated_server_name = str_null; try_void(Client_getServerName(self->client, &updated_server_name)); Defer(if(!success) str_destroy(updated_server_name)); if(!str_equals(updated_server_name, selected_server->name)){ server_info_changed = true; selected_server->name = updated_server_name; } // update server description str updated_server_description = str_null; try_void(Client_getServerDescription(self->client, &updated_server_description)); Defer(if(!success) str_destroy(updated_server_description)); if(!str_equals(updated_server_description, selected_server->description)){ server_info_changed = true; selected_server->description = updated_server_description; } if(server_info_changed){ try_void(SavedServer_createOrUpdate(self->queries, selected_server)); } try_void(ClientCLI_showSavedServer(self, selected_server)); success = true; Return RESULT_VOID; } static Result(void) ClientCLI_showSavedServer(ClientCLI* self, SavedServer* server){ Deferral(8); (void)self; printf("Server Name: "FMT_str"\n", str_unwrap(server->name)); printf("Host Address: "FMT_str"\n", str_unwrap(server->address)); printf("Description:\n"FMT_str"\n\n", str_unwrap(server->description)); printf("Public Key:\n" FMT_str"\n\n", str_unwrap(server->pk_base64)); printf("Type 'register' if you don't have an account on the server.\n"); printf("Type 'login' to authorize on the server.\n"); Return RESULT_VOID; } static Result(void) ClientCLI_register(ClientCLI* self){ Deferral(8); i64 user_id = 0; try_void(Client_register(self->client, &user_id)); printf("Registered successfully\n"); printf("user_id: "FMT_i64"\n", user_id); try_assert(user_id > 0); // TODO: use user_id somewhere Return RESULT_VOID; } static Result(void) ClientCLI_login(ClientCLI* self){ Deferral(8); i64 user_id = 0, landing_channel_id = 0; try_void(Client_login(self->client, &user_id, &landing_channel_id)); printf("Authorized successfully\n"); printf("user_id: "FMT_i64", landing_channel_id: "FMT_i64"\n", user_id, landing_channel_id); try_assert(user_id > 0); // TODO: use user_id, landing_channel_id somewhere Return RESULT_VOID; }