Files
tcp-chat/src/cli/ClientCLI/ClientCLI.c

340 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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-<SIZE>:<DATA>):\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;
}