340 lines
11 KiB
C
340 lines
11 KiB
C
#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;
|
||
}
|