From: jxnshi Date: Tue, 31 Dec 2024 20:58:52 +0000 (+0100) Subject: Working on CLI client X-Git-Url: https://jxnshi.xyz/repos?a=commitdiff_plain;h=9b7917f73c035ce5159e20c86d2e7a90a27c8805;p=mesange.git Working on CLI client --- diff --git a/mesange-cli/.gitignore b/client-cli/.gitignore similarity index 100% rename from mesange-cli/.gitignore rename to client-cli/.gitignore diff --git a/client-cli/client-cli b/client-cli/client-cli new file mode 100755 index 0000000..3c95ce5 Binary files /dev/null and b/client-cli/client-cli differ diff --git a/client-cli/main.odin b/client-cli/main.odin new file mode 100644 index 0000000..e06d47d --- /dev/null +++ b/client-cli/main.odin @@ -0,0 +1,207 @@ +package main + +import "core:crypto/ed25519" +import "core:encoding/csv" +import "core:encoding/json" +import "core:fmt" +import "core:log" +import "core:net" +import "core:os" +import "core:strings" + +import fpath "core:path/filepath" + +import nc "ncurses" + +import "../common" + +Config :: struct { + selected_profile: Maybe(string), +} + +config_deinit :: proc(config: Config) { + if selected_profiles, ok := config.selected_profile.?; ok { + delete(selected_profiles) + } +} + +config_update :: proc(config: Config) { + home_path := os.get_env("HOME") + + storage_path := fpath.join({ home_path, "mesange-cli" }, context.temp_allocator) + config_path := fpath.join({ storage_path, "config.json" }, context.temp_allocator) + + config_file, err1 := os.open(config_path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0777) + + if err1 != nil { + fmt.eprintln("Failed to open config file with error: .", err1) + os.exit(1) + } + + config_file_stream := os.stream_from_handle(config_file) + + marshal_options := json.Marshal_Options{ + spec = .JSON, + pretty = true, + use_spaces = true, + use_enum_names = true, + } + + err2 := json.marshal_to_writer(config_file_stream, config, &marshal_options) + + if err2 != nil { + fmt.eprintln("Failed to marshal config.") + os.exit(1) + } +} + +Profile :: struct { + private_key: ed25519.Private_Key, +} + +App :: struct { + screen: ^nc.Window, + storage_path: string, + state: State, + + box_message: Maybe(^nc.Window), + info_bar: ^nc.Window, + + config: Config, + profile_password: Maybe(string), + profile: Profile, +} + +app_init :: proc() -> App { + screen := nc.initscr() + + nc.keypad(screen, true) + nc.curs_set(0) + + screen_height, screen_width := nc.getmaxyx(screen) + + info_bar := nc.newwin(0, 0, 3, screen_width) + + nc.box(info_bar, 0, 0) + nc.wrefresh(info_bar) + + home_path := os.get_env("HOME") + storage_path := fpath.join({ home_path, "mesange" }) + + if !os.exists(storage_path) { + err := os.make_directory(storage_path) + + if err != nil { + fmt.eprintln("Failed to create storage directory.") + os.exit(1) + } + } + + return { + screen = screen, + storage_path = storage_path, + } +} + +app_deinit :: proc(app: App) { + config_deinit(app.config) + + nc.delwin(app.info_bar) + + if box_message, ok := app.box_message.?; ok { + nc.delwin(box_message) + } + + nc.endwin() +} + +app_set_state :: proc(app: ^App, state: State) { + app_clear_box_message(app) + + nc.clear() + nc.refresh() + + app.state = state +} + +app_set_info_bar :: proc(app: ^App, content: string) { + +} + +app_set_box_message :: proc(app: ^App, lines: []string) { + app_clear_box_message(app) + + screen_height, screen_width := nc.getmaxyx(app.screen) + + max_line_len: int + + for line in lines { + if len(line) > max_line_len { + max_line_len = len(line) + } + } + + height := i32(len(lines)) + 2 + width := i32(max_line_len) + 2 + + box_message := nc.newwin( + height, width, + (screen_height - height) / 2, + (screen_width - width) / 2, + ) + + for line, i in lines { + c_line := strings.clone_to_cstring(line, context.temp_allocator) + nc.mvwprintw(box_message, i32(i + 1), 1, c_line) + } + + nc.box(box_message, 0, 0) + nc.wrefresh(box_message) + + app.box_message = box_message +} + +app_clear_info_bar :: proc(app: App) { + +} + +app_clear_box_message :: proc(app: ^App) { + if box_message, ok := app.box_message.?; ok { + nc.wclear(box_message) + nc.wrefresh(box_message) + nc.delwin(box_message) + + app.box_message = nil + } +} + +app_get_input :: proc(app: App, allocator := context.allocator) -> string { + context.allocator = allocator + + buffer: [256]u8 + + screen_height, screen_width := nc.getmaxyx(app.screen) + + nc.curs_set(1) + nc.mvgetnstr(screen_height - 1, 0, raw_data(buffer[:]), len(buffer)) + nc.curs_set(0) + + return strings.clone_from_cstring(cstring(raw_data(&buffer))) +} + +main :: proc() { + app := app_init() + defer app_deinit(app) + + for { + switch app.state { + case .Load_Config: state_load_config(&app) + case .Ask_Config: state_ask_config(&app) + + case .Load_Profile: state_load_profile(&app) + case .Ask_Profile: state_ask_profile(&app) + case .Ask_Profile_Password: state_ask_profile_password(&app) + case .Ask_Profile_Seed_Phrase: state_ask_profile_seed_phrase(&app) + } + } +} diff --git a/client-cli/ncurses b/client-cli/ncurses new file mode 160000 index 0000000..8712a2b --- /dev/null +++ b/client-cli/ncurses @@ -0,0 +1 @@ +Subproject commit 8712a2b7a368771baf35ce0e860b577c3f87052d diff --git a/client-cli/state.odin b/client-cli/state.odin new file mode 100644 index 0000000..1cf3dde --- /dev/null +++ b/client-cli/state.odin @@ -0,0 +1,129 @@ +package main + +import "core:encoding/json" +import "core:fmt" +import "core:os" +import "core:strings" + +import fpath "core:path/filepath" + +State :: enum { + Load_Config, + Ask_Config, + + Load_Profile, + Ask_Profile, + Ask_Profile_Password, + Ask_Profile_Seed_Phrase, +} + +state_load_config :: proc(app: ^App) { + config_path := fpath.join({ app.storage_path, "config.json" }, context.temp_allocator) + + raw_config, config_read := os.read_entire_file_from_filename(config_path) + config: Config + + if config_read { + err := json.unmarshal(raw_config, &config) + + if err != nil { + app_set_state(app, .Ask_Config) + return + } + } else { + app_set_info_bar(app, "No config, using default.") + } + + app.config = config + + app_set_state(app, .Load_Profile) +} + +state_ask_config :: proc(app: ^App) { + app_set_box_message( + app, + { + "Invalid config.", + "Type :delete or :retry.", + }, + ) + + input := app_get_input(app^, context.temp_allocator) + + switch input { + case ":retry": + case ":delete": + config_path := fpath.join({ app.storage_path, "config.json" }, context.temp_allocator) + os.remove_directory(config_path) + + case: + return + } + + app_set_state(app, .Load_Config) +} + +state_load_profile :: proc(app: ^App) { + selected_profile, ok := app.config.selected_profile.? + + if !ok { + app_set_state(app, .Ask_Profile) + return + } + + encrypted_profile_path := fpath.join({ app.storage_path, selected_profile }, context.temp_allocator) + + if os.exists(encrypted_profile_path) && app.profile_password == nil { + app_set_state(app, .Ask_Profile_Password) + return + } + + profile_content: string + + load_profile_content: { + encrypted_profile_content, profile_read := os.read_entire_file_from_filename(encrypted_profile_path) + + if profile_read { + + } + } +} + +state_ask_profile :: proc(app: ^App) { + app_set_box_message( + app, + { + "No profile selected.", + "Please enter a profile name.", + }, + ) + + input := app_get_input(app^) + + if len(input) == 0 { + return + } + + app.config.selected_profile = app_get_input(app^) + + config_update(app.config) + app_set_state(app, .Load_Profile) +} + +state_ask_profile_password :: proc(app: ^App) { + app_set_box_message( + app, + { + "Profile is encrypted.", + "Please enter profile password." + }, + ) + + app.profile_password = app_get_input(app^) + + app_set_state(app, .Load_Profile) +} + +state_ask_profile_seed_phrase :: proc(app: ^App) { + +} diff --git a/mesange-cli/main.odin b/mesange-cli/main.odin deleted file mode 100644 index 67e160c..0000000 --- a/mesange-cli/main.odin +++ /dev/null @@ -1,142 +0,0 @@ -package main - -import "core:encoding/csv" -import "core:encoding/json" -import "core:fmt" -import "core:log" -import "core:net" -import "core:os" - -import fpath "core:path/filepath" - -import "../common" - -Config :: struct { - selected_profile: Maybe(string), -} - -App :: struct { - config: Config, - servers: map[string]net.TCP_Socket, -} - -config_update :: proc(app: App, config: Config) { - home_path := os.get_env("HOME") - storage_path := fpath.join({ home_path, "mesange-cli" }, context.temp_allocator) - - config_path := fpath.join({ storage_path, "config.json" }, context.temp_allocator) - - config_file, err1 := os.open(config_path, O_WRONLY) - - if err1 != nil { - fmt.eprintln("Failed to open config file.") - return - } - - config_file_stream := os.stream_from_handle(config_file) - - marshal_options := json.Marshal_Options{ - spec = .JSON, - pretty = true, - use_spaces = true, - use_enum_names = true, - } - - err2 := json.marshal_to_writer(config_file_stream, app.config, &marshal_options) - - if err2 != nil { - fmt.eprintln("Failed to marshal config.") - return - } -} - -init :: proc(app: ^App) { - home_path := os.get_env("HOME") - storage_path := fpath.join({ home_path, "mesange-cli" }, context.temp_allocator) - - err1 := os.make_directory(storage_path) - - if err1 != nil { - fmt.eprintln("Failed to create storage directory.") - return - } - - { // Load config. - fmt.println("Loading config...") - - config_path := fpath.join({ storage_path, "config.json" }, context.temp_allocator) - - if !os.exists(config_path) { - // Init default config. - ok := os.write_entire_file( - config_path, - ` -{ - "current_profile": null -} - `, - ) - - if !ok { - fmt.eprintln("Failed to write to config file.") - return - } - } - - raw_config, ok := os.read_entire_file_from_filename(config_path) - - if !ok { - fmt.eprintln("Failed to read config file.") - return - } - - config: Config - - err2 := json.unmarshal(raw_config, &config) - - if err2 != nil { - fmt.eprintln("Invalid config file.") - return - } - - app.config = config - } -} - -main :: proc() { - context.logger = log.create_console_logger() - defer log.destroy_console_logger(context.logger) - - when ODIN_DEBUG { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - - context.allocator = mem.tracking_allocator(&track) - - defer { - if len(track.allocation_map) > 0 { - fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map)) - - for _, entry in track.allocation_map { - fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location) - } - } - - if len(track.bad_free_array) > 0 { - fmt.eprintf("=== %v incorrect frees: ===\n", len(track.bad_free_array)) - - for entry in track.bad_free_array { - fmt.eprintf("- %p @ %v\n", entry.memory, entry.location) - } - } - - mem.tracking_allocator_destroy(&track) - } - } - - app := App{ - servers = make(map[string]net.TCP_Socket) - } - - init() -} diff --git a/mesange-server/.gitignore b/server/.gitignore similarity index 100% rename from mesange-server/.gitignore rename to server/.gitignore diff --git a/mesange-server/main.odin b/server/main.odin similarity index 100% rename from mesange-server/main.odin rename to server/main.odin