]> jxnshi.xyz Git - mesange.git/commitdiff
Working on CLI client
authorjxnshi <jxnshi@cock.li>
Tue, 31 Dec 2024 20:58:52 +0000 (21:58 +0100)
committerjxnshi <jxnshi@cock.li>
Tue, 31 Dec 2024 20:58:52 +0000 (21:58 +0100)
client-cli/.gitignore [moved from mesange-cli/.gitignore with 100% similarity]
client-cli/client-cli [new file with mode: 0755]
client-cli/main.odin [new file with mode: 0644]
client-cli/ncurses [new submodule]
client-cli/state.odin [new file with mode: 0644]
mesange-cli/main.odin [deleted file]
server/.gitignore [moved from mesange-server/.gitignore with 100% similarity]
server/main.odin [moved from mesange-server/main.odin with 100% similarity]

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 (executable)
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 (file)
index 0000000..e06d47d
--- /dev/null
@@ -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 (submodule)
index 0000000..8712a2b
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 8712a2b7a368771baf35ce0e860b577c3f87052d
diff --git a/client-cli/state.odin b/client-cli/state.odin
new file mode 100644 (file)
index 0000000..1cf3dde
--- /dev/null
@@ -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 (file)
index 67e160c..0000000
+++ /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()
-}
similarity index 100%
rename from mesange-server/.gitignore
rename to server/.gitignore
similarity index 100%
rename from mesange-server/main.odin
rename to server/main.odin