turned ClientCLI to a state machine
This commit is contained in:
2
dependencies/tim
vendored
2
dependencies/tim
vendored
Submodule dependencies/tim updated: 6d0190c9c0...2a685dfcd0
2
dependencies/tlibc
vendored
2
dependencies/tlibc
vendored
Submodule dependencies/tlibc updated: c4fd22542d...82a6293f21
@@ -2,35 +2,7 @@
|
|||||||
#include "tlibc/filesystem.h"
|
#include "tlibc/filesystem.h"
|
||||||
#include "tlibc/term.h"
|
#include "tlibc/term.h"
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
#include "tim.h"
|
#include <assert.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){
|
void ClientCLI_destroy(ClientCLI* self){
|
||||||
if(!self)
|
if(!self)
|
||||||
@@ -44,296 +16,51 @@ void ClientCLI_destroy(ClientCLI* self){
|
|||||||
|
|
||||||
void ClientCLI_construct(ClientCLI* self){
|
void ClientCLI_construct(ClientCLI* self){
|
||||||
zeroStruct(self);
|
zeroStruct(self);
|
||||||
|
|
||||||
|
self->style.common = (TimStyle){
|
||||||
|
.brd = Color256_MidGray,
|
||||||
|
.bg = Color256_NavyBlue,
|
||||||
|
.fg = Color256_MidGray
|
||||||
|
};
|
||||||
|
self->style.focused = (TimStyle){
|
||||||
|
.brd = Color256_White,
|
||||||
|
.bg = Color256_DeepSkyBlue,
|
||||||
|
.fg = Color256_White
|
||||||
|
};
|
||||||
|
self->style.error = (TimStyle){
|
||||||
|
.brd = Color256_MidGray,
|
||||||
|
.bg = Color256_DarkRed,
|
||||||
|
.fg = Color256_White
|
||||||
|
};
|
||||||
|
|
||||||
self->saved_servers = List_SavedServer_alloc(0);
|
self->saved_servers = List_SavedServer_alloc(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) ClientCLI_run(ClientCLI* self) {
|
void ClientCLI_run(ClientCLI* self) {
|
||||||
Deferral(32);
|
Deferral(32);
|
||||||
|
|
||||||
// ask username and password
|
StartScreenContext start_screen_ctx;
|
||||||
Array(char) username_buf = Array_char_alloc(USERNAME_SIZE_MAX + 1);
|
StartScreenContext_construct(&start_screen_ctx, self);
|
||||||
Array(char) password_buf = Array_char_alloc(PASSWORD_SIZE_MAX + 1);
|
Defer(StartScreenContext_destroy(&start_screen_ctx));
|
||||||
Defer(
|
MainScreenContext main_screen_ctx;
|
||||||
Array_char_destroy(&username_buf);
|
MainScreenContext_construct(&main_screen_ctx, self);
|
||||||
Array_char_destroy(&password_buf);
|
Defer(MainScreenContext_destroy(&main_screen_ctx));
|
||||||
);
|
|
||||||
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();
|
while(tim_run(FPS)){
|
||||||
term_clear();
|
switch(self->state){
|
||||||
printf(FMT_str"\n", greeting_art.len, greeting_art.data);
|
case ClientCLIState_Exit:
|
||||||
|
Return;
|
||||||
// create client
|
default:
|
||||||
try(self->client, p, Client_create(username, password));
|
assert(false && "invalid ClientCLI state");
|
||||||
// erase password from memory
|
break;
|
||||||
Array_char_memset(&password_buf, 0);
|
case ClientCLIState_StartScreen:
|
||||||
|
start_screen(&start_screen_ctx);
|
||||||
// init db
|
break;
|
||||||
try_void(ClientCLI_openUserDB(self));
|
case ClientCLIState_MainScreen:
|
||||||
|
main_screen(&main_screen_ctx);
|
||||||
char input_buf[1024];
|
break;
|
||||||
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;
|
Return;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,25 @@
|
|||||||
#include "tlibc/collections/HashMap.h"
|
#include "tlibc/collections/HashMap.h"
|
||||||
#include "tlibc/collections/List.h"
|
#include "tlibc/collections/List.h"
|
||||||
#include "db/client_db.h"
|
#include "db/client_db.h"
|
||||||
|
#include "tim.h"
|
||||||
|
|
||||||
#define FPS 30
|
#define FPS 30
|
||||||
|
|
||||||
|
typedef enum ClientCLIState {
|
||||||
|
ClientCLIState_StartScreen,
|
||||||
|
ClientCLIState_Exit,
|
||||||
|
ClientCLIState_MainScreen,
|
||||||
|
ClientCLIState_ServerChannels,
|
||||||
|
ClientCLIState_ChannelChat,
|
||||||
|
} ClientCLIState;
|
||||||
|
|
||||||
typedef struct ClientCLI {
|
typedef struct ClientCLI {
|
||||||
|
ClientCLIState state;
|
||||||
|
struct {
|
||||||
|
TimStyle common;
|
||||||
|
TimStyle focused;
|
||||||
|
TimStyle error;
|
||||||
|
} style;
|
||||||
Client* client;
|
Client* client;
|
||||||
tsqlite_connection* db;
|
tsqlite_connection* db;
|
||||||
ClientQueries* queries;
|
ClientQueries* queries;
|
||||||
@@ -16,7 +31,7 @@ typedef struct ClientCLI {
|
|||||||
|
|
||||||
void ClientCLI_construct(ClientCLI* self);
|
void ClientCLI_construct(ClientCLI* self);
|
||||||
void ClientCLI_destroy(ClientCLI* self);
|
void ClientCLI_destroy(ClientCLI* self);
|
||||||
Result(void) ClientCLI_run(ClientCLI* self);
|
void ClientCLI_run(ClientCLI* self);
|
||||||
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -26,3 +41,51 @@ enum {
|
|||||||
Color256_DeepSkyBlue = 0x18,
|
Color256_DeepSkyBlue = 0x18,
|
||||||
Color256_DarkRed = 0x58,
|
Color256_DarkRed = 0x58,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct TextInputState {
|
||||||
|
TimEditState edit;
|
||||||
|
cstr label;
|
||||||
|
struct {
|
||||||
|
TimStyle common;
|
||||||
|
TimStyle focused;
|
||||||
|
} style;
|
||||||
|
TimKey result_key;
|
||||||
|
} TextInputState;
|
||||||
|
|
||||||
|
void TextInputState_construct(TextInputState* ctx, cstr label,
|
||||||
|
Array(char) buf, NULLABLE(cstr) initial_value, bool masked,
|
||||||
|
TimStyle common, TimStyle focused);
|
||||||
|
|
||||||
|
/// tim_edit with tim_label over its upper border.
|
||||||
|
void text_input(TextInputState* ctx, i32 x, i32 y, i32 w, TimStyle style);
|
||||||
|
|
||||||
|
void TimScrollItem_fromTextInputState(TimScrollItem* item, TextInputState* input);
|
||||||
|
|
||||||
|
/// Intended to use in TimScrollItem
|
||||||
|
/// @param data TextInputState*
|
||||||
|
void draw_item_text_input(bool is_selected, TimRect place, void* data);
|
||||||
|
|
||||||
|
|
||||||
|
List_declare(TimScrollItem);
|
||||||
|
|
||||||
|
typedef struct StartScreenContext {
|
||||||
|
ClientCLI* client;
|
||||||
|
char* err_msg; // heap only
|
||||||
|
TextInputState input_username;
|
||||||
|
TextInputState input_password;
|
||||||
|
List(TimScrollItem) scroll_list;
|
||||||
|
TimScrollState scroll_state;
|
||||||
|
} StartScreenContext;
|
||||||
|
|
||||||
|
void StartScreenContext_construct(StartScreenContext* ctx, ClientCLI* client);
|
||||||
|
void StartScreenContext_destroy(StartScreenContext* ctx);
|
||||||
|
void start_screen(StartScreenContext* ctx);
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct MainScreenContext {
|
||||||
|
ClientCLI* client;
|
||||||
|
} MainScreenContext;
|
||||||
|
|
||||||
|
void MainScreenContext_construct(MainScreenContext* ctx, ClientCLI* client);
|
||||||
|
void MainScreenContext_destroy(MainScreenContext* ctx);
|
||||||
|
void main_screen(MainScreenContext* ctx);
|
||||||
|
|||||||
259
src/cli/ClientCLI/main_screen.c
Normal file
259
src/cli/ClientCLI/main_screen.c
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
#include "ClientCLI.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"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
static Result(SavedServer*) joinNewServer(ClientCLI* self);
|
||||||
|
static Result(SavedServer*) selectServerFromCache(ClientCLI* self);
|
||||||
|
static Result(void) showSavedServer(ClientCLI* self, SavedServer* server);
|
||||||
|
static Result(void) registerAtServer(ClientCLI* self);
|
||||||
|
static Result(void) loginAtServer(ClientCLI* self);
|
||||||
|
|
||||||
|
|
||||||
|
void MainScreenContext_construct(MainScreenContext* ctx, ClientCLI* client){
|
||||||
|
zeroStruct(ctx);
|
||||||
|
ctx->client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainScreenContext_destroy(MainScreenContext* ctx){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void main_screen(MainScreenContext* ctx){
|
||||||
|
tim_frame(0, 0, ~0, ~0, ctx->client->style.common);
|
||||||
|
|
||||||
|
if(tim_button("[Esc/Q] Quit", A, A, A, A, ctx->client->style.common)
|
||||||
|
|| tim_is_key_press('q')
|
||||||
|
|| tim_is_key_press(TimKey_Escape))
|
||||||
|
{
|
||||||
|
ctx->client->state = ClientCLIState_Exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
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(joinNewServer(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("s") || is_alias("select")){
|
||||||
|
// show scrollable list of servers, get selected one
|
||||||
|
try_void(selectServerFromCache(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("r") || is_alias("register")){
|
||||||
|
try_void(registerAtServer(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("l") || is_alias("login")){
|
||||||
|
try_void(loginAtServer(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) 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(showSavedServer(self, &server));
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) 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(showSavedServer(self, selected_server));
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) 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) registerAtServer(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) loginAtServer(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;
|
||||||
|
}
|
||||||
@@ -1,149 +1,158 @@
|
|||||||
#include "ClientCLI.h"
|
#include "ClientCLI.h"
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
#include "tim.h"
|
#include "tlibc/filesystem.h"
|
||||||
|
|
||||||
typedef struct TextInputState {
|
static void draw_buttons(bool is_selected, TimRect place, void* data);
|
||||||
TimEditState edit;
|
static Result(void) openUserDB(StartScreenContext* ctx);
|
||||||
cstr label;
|
|
||||||
TimStyle style_default;
|
|
||||||
TimStyle style_focused;
|
|
||||||
TimKey result_key;
|
|
||||||
} TextInputState;
|
|
||||||
|
|
||||||
void draw_item_text_input(bool is_selected, TimRect place, void* data){
|
|
||||||
TextInputState* ctx = data;
|
#define handleError(R) _handleError(ctx, R)
|
||||||
TimStyle style = is_selected ? ctx->style_focused : ctx->style_default;
|
static void _handleError(StartScreenContext* ctx, ResultVar(void) r){
|
||||||
ctx->result_key = tim_edit(&ctx->edit, place.x, place.y, place.w, style);
|
free(ctx->err_msg);
|
||||||
tim_label(ctx->label, place.x + 3, place.y, A, 1, style);
|
ctx->err_msg = Error_toStr(r.error).data;
|
||||||
|
Error_free(r.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct StartScreenContext {
|
static void clearError(StartScreenContext* ctx){
|
||||||
TimStyle style_default;
|
free(ctx->err_msg);
|
||||||
TimStyle style_focused;
|
ctx->err_msg = NULL;
|
||||||
TimStyle style_error;
|
}
|
||||||
TextInputState input_username;
|
|
||||||
TextInputState input_password;
|
|
||||||
bool exit;
|
|
||||||
bool start;
|
|
||||||
char* err_msg; // heap only
|
|
||||||
} StartScreenContext;
|
|
||||||
|
|
||||||
void draw_start_screen_buttons(bool is_selected, TimRect place, void* data){
|
void StartScreenContext_construct(StartScreenContext* ctx, ClientCLI* client){
|
||||||
|
zeroStruct(ctx);
|
||||||
|
ctx->client = client;
|
||||||
|
|
||||||
|
Array(char) username_buf = Array_char_alloc(USERNAME_SIZE_MAX + 1);
|
||||||
|
TextInputState_construct(&ctx->input_username, "[username]",
|
||||||
|
username_buf, NULL, false,
|
||||||
|
ctx->client->style.common, ctx->client->style.focused);
|
||||||
|
|
||||||
|
Array(char) password_buf = Array_char_alloc(PASSWORD_SIZE_MAX + 1);
|
||||||
|
TextInputState_construct(&ctx->input_password, "[password]",
|
||||||
|
password_buf, NULL, true,
|
||||||
|
ctx->client->style.common, ctx->client->style.focused);
|
||||||
|
|
||||||
|
ctx->scroll_list = List_TimScrollItem_alloc(8);
|
||||||
|
|
||||||
|
TimScrollItem item_input_username;
|
||||||
|
TimScrollItem_fromTextInputState(&item_input_username, &ctx->input_username);
|
||||||
|
List_TimScrollItem_push(&ctx->scroll_list, item_input_username);
|
||||||
|
|
||||||
|
TimScrollItem item_input_password;
|
||||||
|
TimScrollItem_fromTextInputState(&item_input_password, &ctx->input_password);
|
||||||
|
List_TimScrollItem_push(&ctx->scroll_list, item_input_password);
|
||||||
|
|
||||||
|
TimScrollItem item_buttons = {
|
||||||
|
.data = ctx,
|
||||||
|
.focus_target = NULL,
|
||||||
|
.h = 3,
|
||||||
|
.draw = draw_buttons
|
||||||
|
};
|
||||||
|
List_TimScrollItem_push(&ctx->scroll_list, item_buttons);
|
||||||
|
|
||||||
|
ctx->scroll_state = (TimScrollState){ .items = ctx->scroll_list.data, .len = ctx->scroll_list.len };
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScreenContext_destroy(StartScreenContext* ctx){
|
||||||
|
free(ctx->input_username.edit.s);
|
||||||
|
free(ctx->input_password.edit.s);
|
||||||
|
free(ctx->err_msg);
|
||||||
|
List_TimScrollItem_destroy(&ctx->scroll_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_screen(StartScreenContext* ctx)
|
||||||
|
{
|
||||||
|
tim_fill(tim_cell(" ", ctx->client->style.common.fg, ctx->client->style.common.bg), 0, 0, A, A);
|
||||||
|
tim_scope(A, A, 40, 13) {
|
||||||
|
tim_scroll(&ctx->scroll_state, 1, 1, ~1, ~1, ctx->client->style.common);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ctx->err_msg){
|
||||||
|
i32 below_scroll = tim->scopes[tim->scope].h/2 + 6;
|
||||||
|
tim_label("ERROR: ", A, below_scroll, A, A, ctx->client->style.error);
|
||||||
|
tim_label(ctx->err_msg, A, below_scroll + 1, A, A, ctx->client->style.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void draw_buttons(bool is_selected, TimRect place, void* data){
|
||||||
StartScreenContext* ctx = data;
|
StartScreenContext* ctx = data;
|
||||||
TimStyle style = is_selected ? ctx->style_focused : ctx->style_default;
|
TimStyle style = is_selected ? ctx->client->style.focused : ctx->client->style.common;
|
||||||
i32 start_w = place.w / 2;
|
i32 start_w = place.w / 2;
|
||||||
i32 dist = 1;
|
i32 dist = 1;
|
||||||
i32 quit_x = place.x + start_w + dist;
|
i32 quit_x = place.x + start_w + dist;
|
||||||
i32 quit_w = place.w - start_w - dist;
|
i32 quit_w = place.w - start_w - dist;
|
||||||
|
if(tim_button("[Esc/Q] Quit", quit_x, place.y, quit_w, A, ctx->client->style.common)
|
||||||
|
|| tim_is_key_press('q')
|
||||||
|
|| ctx->input_username.result_key == TimKey_Escape
|
||||||
|
|| ctx->input_password.result_key == TimKey_Escape)
|
||||||
|
{
|
||||||
|
ctx->client->state = ClientCLIState_Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(tim_button("[Enter] Start", place.x, place.y, start_w, A, style)
|
if(tim_button("[Enter] Start", place.x, place.y, start_w, A, style)
|
||||||
|| tim_is_key_press(TimKey_Enter))
|
|| tim_is_key_press(TimKey_Enter))
|
||||||
{
|
{
|
||||||
ctx->start = true;
|
clearError(ctx);
|
||||||
}
|
|
||||||
else if(tim_button("[Esc/Q] Quit", quit_x, place.y, quit_w, A, ctx->style_default)
|
|
||||||
|| tim_is_key_press('q'))
|
|
||||||
{
|
|
||||||
ctx->exit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(bool) start_screen(Array(char) username_buf, Array(char) password_buf,
|
|
||||||
str* out_username, str* out_password)
|
|
||||||
{
|
|
||||||
Deferral(16);
|
|
||||||
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
StartScreenContext ctx = {
|
|
||||||
.style_default = {
|
|
||||||
.brd = Color256_MidGray,
|
|
||||||
.bg = Color256_NavyBlue,
|
|
||||||
.fg = Color256_MidGray
|
|
||||||
},
|
|
||||||
.style_focused = {
|
|
||||||
.brd = Color256_White,
|
|
||||||
.bg = Color256_DeepSkyBlue,
|
|
||||||
.fg = Color256_White
|
|
||||||
},
|
|
||||||
.style_error = {
|
|
||||||
.brd = Color256_MidGray,
|
|
||||||
.bg = Color256_DarkRed,
|
|
||||||
.fg = Color256_White
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
TimEditState_construct(&ctx.input_username.edit, username_buf.data, username_buf.len, NULL);
|
|
||||||
ctx.input_username.label = "[username]";
|
|
||||||
ctx.input_username.style_default = ctx.style_default;
|
|
||||||
ctx.input_username.style_focused = ctx.style_focused;
|
|
||||||
|
|
||||||
TimEditState_construct(&ctx.input_password.edit, password_buf.data, password_buf.len, NULL);
|
|
||||||
ctx.input_password.edit.masked = true;
|
|
||||||
ctx.input_password.label = "[password]";
|
|
||||||
ctx.input_password.style_default = ctx.style_default;
|
|
||||||
ctx.input_password.style_focused = ctx.style_focused;
|
|
||||||
|
|
||||||
TimScrollItem items[] = {
|
|
||||||
// username input
|
|
||||||
{ .data = &ctx.input_username, .focus_target = &ctx.input_username.edit, .h = 3, .draw = draw_item_text_input },
|
|
||||||
// password input
|
|
||||||
{ .data = &ctx.input_password, .focus_target = &ctx.input_password.edit, .h = 3, .draw = draw_item_text_input },
|
|
||||||
// buttons
|
|
||||||
{ .data = &ctx, .focus_target = NULL, .h = 3, .draw = draw_start_screen_buttons },
|
|
||||||
};
|
|
||||||
|
|
||||||
TimScrollState scroll_list = { .items = items, .len = ARRAY_LEN(items) };
|
|
||||||
|
|
||||||
while(tim_run(FPS)){
|
|
||||||
tim_fill(tim_cell(" ", ctx.style_default.fg, ctx.style_default.bg), 0, 0, A, A);
|
|
||||||
tim_scope(A, A, 40, 13) {
|
|
||||||
tim_scroll(&scroll_list, 1, 1, ~1, ~1, ctx.style_default);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ctx.err_msg){
|
|
||||||
i32 below_scroll = tim->scopes[tim->scope].h/2 + 6;
|
|
||||||
tim_label("ERROR: ", A, below_scroll, A, A, ctx.style_error);
|
|
||||||
tim_label(ctx.err_msg, A, below_scroll + 1, A, A, ctx.style_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ctx.exit
|
|
||||||
|| ctx.input_username.result_key == TimKey_Escape
|
|
||||||
|| ctx.input_password.result_key == TimKey_Escape)
|
|
||||||
{
|
|
||||||
result = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(ctx.start){
|
|
||||||
ctx.start = false;
|
|
||||||
free(ctx.err_msg);
|
|
||||||
ctx.err_msg = NULL;
|
|
||||||
|
|
||||||
// check username
|
// check username
|
||||||
str username = str_from_cstr(username_buf.data);
|
str username = str_from_cstr(ctx->input_username.edit.s);
|
||||||
str_trim(&username, true);
|
str_trim(&username, true);
|
||||||
str name_error_str = validateUsername_str(username);
|
str name_error_str = validateUsername_str(username);
|
||||||
if(name_error_str.data){
|
if(name_error_str.data){
|
||||||
ctx.err_msg = name_error_str.data;
|
ctx->err_msg = name_error_str.data;
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check password
|
// check password
|
||||||
str password = str_from_cstr(password_buf.data);
|
str password = str_from_cstr(ctx->input_password.edit.s);
|
||||||
str_trim(&password, true);
|
str_trim(&password, true);
|
||||||
if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){
|
if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){
|
||||||
ctx.err_msg = sprintf_malloc(
|
ctx->err_msg = sprintf_malloc(
|
||||||
"password length (in bytes) must be >= %i and <= %i",
|
"password length (in bytes) must be >= %i and <= %i",
|
||||||
PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX
|
PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX
|
||||||
);
|
);
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
*out_username = username;
|
|
||||||
*out_password = password;
|
// create client
|
||||||
result = true;
|
try_handle(ctx->client->client, p, Client_create(username, password), handleError);
|
||||||
break;
|
|
||||||
|
// init user DB
|
||||||
|
try_handle_void(openUserDB(ctx), handleError);
|
||||||
|
|
||||||
|
// erase password from memory
|
||||||
|
memset(ctx->input_password.edit.s, 0, ctx->input_password.edit.capacity);
|
||||||
|
|
||||||
|
ctx->client->state = ClientCLIState_MainScreen;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Return RESULT_VALUE(i, result);
|
static Result(void) openUserDB(StartScreenContext* ctx){
|
||||||
|
Deferral(8);
|
||||||
|
|
||||||
|
str username = Client_getUserName(ctx->client->client);
|
||||||
|
// TODO: encrypt user database
|
||||||
|
// Array(u8) user_data_key = Client_getUserDataKey(ctx->client->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));
|
||||||
|
|
||||||
|
try(ctx->client->db, p, ClientDatabase_open(db_path));
|
||||||
|
try(ctx->client->queries, p, ClientQueries_compile(ctx->client->db));
|
||||||
|
|
||||||
|
// load whole servers table to list
|
||||||
|
try_void(SavedServer_getAll(ctx->client->queries, &ctx->client->saved_servers));
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
31
src/cli/ClientCLI/widgets.c
Normal file
31
src/cli/ClientCLI/widgets.c
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "ClientCLI.h"
|
||||||
|
|
||||||
|
void TextInputState_construct(TextInputState* ctx, cstr label,
|
||||||
|
Array(char) buf, NULLABLE(cstr) initial_value, bool masked,
|
||||||
|
TimStyle common, TimStyle focused)
|
||||||
|
{
|
||||||
|
TimEditState_construct(&ctx->edit, buf.data, buf.len, initial_value);
|
||||||
|
ctx->edit.masked = masked;
|
||||||
|
ctx->label = label;
|
||||||
|
ctx->style.common = common;
|
||||||
|
ctx->style.focused = focused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void text_input(TextInputState* ctx, i32 x, i32 y, i32 w, TimStyle style){
|
||||||
|
ctx->result_key = tim_edit(&ctx->edit, x, y, w, style);
|
||||||
|
tim_label(ctx->label, x + 3, y, A, 1, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimScrollItem_fromTextInputState(TimScrollItem* item, TextInputState* input){
|
||||||
|
zeroStruct(item);
|
||||||
|
item->data = input;
|
||||||
|
item->focus_target = &input->edit;
|
||||||
|
item->h = 3;
|
||||||
|
item->draw = draw_item_text_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_item_text_input(bool is_selected, TimRect place, void* data){
|
||||||
|
TextInputState* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->style.focused : ctx->style.common;
|
||||||
|
text_input(ctx, place.x, place.y, place.w, style);
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ Result(void) run_ClientMode(cstr config_path) {
|
|||||||
ClientCLI_construct(&client);
|
ClientCLI_construct(&client);
|
||||||
Defer(ClientCLI_destroy(&client));
|
Defer(ClientCLI_destroy(&client));
|
||||||
// start infinite loop on main thread
|
// start infinite loop on main thread
|
||||||
try_void(ClientCLI_run(&client));
|
ClientCLI_run(&client);
|
||||||
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user