diff postdog/main.c @ 114:e2a73e64e8e6

[Postdog] Got history working.
author June Park <parkjune1995@gmail.com>
date Tue, 06 Jan 2026 08:15:37 -0800
parents d6d578b49a19
children 96db6c3f38d6
line wrap: on
line diff
--- a/postdog/main.c	Sun Jan 04 14:42:54 2026 -0800
+++ b/postdog/main.c	Tue Jan 06 08:15:37 2026 -0800
@@ -3,7 +3,6 @@
 #include <string.h>
 #include <time.h>
 #include <sys/stat.h>
-#include <dirent.h>
 #include "dowa/dowa.h"
 
 #include <curl/curl.h>
@@ -11,6 +10,10 @@
 #define RAYGUI_IMPLEMENTATION
 #include "third_party/raylib/include/raygui.h"
 
+#ifndef POSTDOG_PATHS
+  #define POSTDOG_PATHS "/Users/mrjunejune/zenbu/postdog/history"
+#endif
+
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 780
 #define TEXT_SIZE 10
@@ -27,6 +30,19 @@
 #define RESULT_BUFFER_LENGTH 1024 * 1025 * 5
 #define AREANA_BUFFER_LENGTH 1024 * 1025 * 15
 
+
+#ifdef _WIN32
+    #include <direct.h>
+    #include <io.h>
+    #define mkdir(path, mode) _mkdir(path)
+    #define access _access
+    #define F_OK 0
+#else
+    #include <sys/stat.h>
+    #include <dirent.h>
+    #include <unistd.h>
+#endif
+
 typedef Dowa_KV(char*, char*) INPUT_HASHMAP;
 
 typedef struct {
@@ -35,12 +51,114 @@
 } ResponseBuffer;
 
 typedef struct {
-  char filename[256];
-  char displayName[128];
-  char method[16];
-  time_t timestamp;
+  char *filename;
+  Rectangle rect;
+  long time_modified;
 } HistoryItem;
 
+typedef struct {
+  Rectangle rectangle;
+  char *label;
+  bool active;
+} TabItem;
+
+typedef enum {
+  TAB_HEADER = 0,
+  TAB_BODY,
+  TAB_GET_PARAMS,
+  TAB_BAR,
+  TAB_LENGTH
+} PostDog_Tab_Enum;
+
+static uint32 counter = 0;
+HistoryItem *history_items = NULL;
+HistoryItem *new_history_items = NULL;
+
+int CompareHistoryItemsByDate(const void *a, const void *b) {
+  HistoryItem *itemA = (HistoryItem *)a;
+  HistoryItem *itemB = (HistoryItem *)b;
+  return (itemB->time_modified - itemA->time_modified); 
+}
+
+// TODO: Make this into generic fucntion so I can use it across different thing.
+void PostDog_List_Directory(const char *path, HistoryItem **p_file_arr)
+{
+  HistoryItem *file_arr = *p_file_arr;
+#ifdef _WIN32
+  struct _finddata_t fileinfo;
+  intptr_t handle;
+  char search_path[256];
+  sprintf(search_path, "%s\\*", path);
+
+  if ((handle = _findfirst(search_path, &fileinfo)) == -1L) {
+    printf("Directory is empty or cannot be read.\n");
+  } else {
+    do {
+      HistoryItem item = {0};
+      item.filename = strdup(fileinfo.name);
+      item.rect = (Rectangle){0};
+      item.time_modified = fileinfo.time_write; 
+      Dowa_Array_Push(file_arr, item);
+    } while (_findnext(handle, &fileinfo) == 0);
+    _findclose(handle);
+  }
+#else
+  struct dirent *entry;
+  struct stat file_stat;
+  DIR *dp = opendir(path);
+  if (dp == NULL) return;
+
+  char full_path[256];
+  while ((entry = readdir(dp)))
+  {
+    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+      continue;
+    snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
+    if (stat(full_path, &file_stat) == 0)
+    {
+      HistoryItem item = {0};
+      item.filename = strdup(entry->d_name);
+      item.time_modified = file_stat.st_mtime;
+      Dowa_Array_Push(file_arr, item);
+    }
+  }
+  closedir(dp);
+#endif
+  int count = Dowa_Array_Length(file_arr);
+  if (count > 1) {
+    qsort(file_arr, count, sizeof(HistoryItem), CompareHistoryItemsByDate);
+  }
+}
+
+int PostDog_History_Load(HistoryItem **p_history_files)
+{
+  if (access(POSTDOG_PATHS, F_OK) == -1)
+  {
+    printf("Directory '%s' not found. Creating it...\n", POSTDOG_PATHS);
+    if (mkdir(POSTDOG_PATHS, 0777) != 0)
+      return -1;
+    return 0;
+  }
+  printf("Directory '%s' already exists.\n", POSTDOG_PATHS);
+  PostDog_List_Directory(POSTDOG_PATHS, p_history_files);
+  return 0;
+}
+
+bool InArea(Vector2 mouse_position, Rectangle area)
+{
+  return (
+    mouse_position.x >= area.x &&
+    mouse_position.x < area.x + area.width &&
+    mouse_position.y >= area.y &&
+    mouse_position.y < area.y + area.height
+  );
+}
+
+bool Clicked(Vector2 mouse_position, Rectangle area)
+{
+  return (InArea(mouse_position, area) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
+}
+
 char *PostDog_Enum_To_String(int active_enum)
 {
   switch(active_enum)
@@ -50,6 +168,74 @@
     case 2: return "PUT";
     case 3: return "DELETE";
   }
+  return 0;
+}
+
+void PostDog_History_CreateFile(char *filename, char* values)
+{
+  char full_file_path[512] = {0};
+  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
+  FILE *file = fopen(full_file_path, "w");
+  if (!file)
+  {
+    printf("Failed to create a file: %s\n", full_file_path);
+    return;
+  }
+  fwrite(values, 1, strlen(values), file);
+  fclose(file);
+}
+
+void PostDog_Request_SaveFile(
+    const char *url,
+    const char *method,
+    const char *headers,
+    const char *body, 
+    const char *response)
+{
+  size_t new_file_size = 1024 * 1024;
+  Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 2);
+  char *new_file = Dowa_Arena_Allocate(arena, 1024 * 1024);
+  snprintf(
+      new_file,
+      new_file_size,
+      "%s\n"
+      "---\n"
+      "%s\n"
+      "---\n"
+      "%s\n"
+      "---\n"
+      "%s\n"
+      "---\n"
+      "%s\n",
+      url,
+      method,
+      headers,
+      body,
+      response
+  );
+  char *filename = Dowa_Arena_Allocate(arena, 1024);
+  if (!filename)
+  {
+    perror("Error opening file");
+    exit(EXIT_FAILURE); 
+  }
+  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, 37);
+  if (!uuid4)
+  {
+    perror("Error uuid");
+    exit(EXIT_FAILURE); 
+  }
+  
+  int32 seed = (uint32)time(NULL) ^ counter++;
+  Dowa_String_UUID(seed, uuid4);
+  snprintf(filename, 1024, "%s.txt", uuid4);
+  PostDog_History_CreateFile(filename, new_file);
+
+  HistoryItem item = (HistoryItem){ .filename = malloc(sizeof(char) * strlen(filename) + 1), .rect = (Rectangle){0} };
+  memcpy(item.filename, filename, strlen(filename) + 1);
+  Dowa_Array_Push(new_history_items, item);
+  
+  Dowa_Arena_Free(arena);
 }
 
 static size_t Postdog_Curl_Callback(void *contents, size_t size, size_t nmemb, void *userp)
@@ -72,7 +258,7 @@
   return real_size;
 }
 
-int PostDog_Make_HttpRequest(
+int PostDog_Http_Request(
     const char *url,
     const char *method,
     const char *headers,
@@ -106,16 +292,14 @@
     if (strcmp(method, "POST") == 0)
     {
       curl_easy_setopt(curl, CURLOPT_POST, 1L);
-      if (body && strlen(body) > 0) {
+      if (body && strlen(body) > 0)
         curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
-      }
     } 
     else if (strcmp(method, "PUT") == 0)
     {
       curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
-      if (body && strlen(body) > 0) {
+      if (body && strlen(body) > 0)
         curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
-      }
     }
     else if (strcmp(method, "DELETE") == 0)
     {
@@ -131,9 +315,8 @@
       while (line != NULL) {
         // Trim whitespace
         while (*line == ' ' || *line == '\t') line++;
-        if (strlen(line) > 0) {
+        if (strlen(line) > 0)
           headerList = curl_slist_append(headerList, line);
-        }
         line = strtok(NULL, "\n");
       }
       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList);
@@ -146,10 +329,8 @@
 
     // Follow redirects
     curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
-
     // Set timeout
     curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
-
     // Perform request
     res = curl_easy_perform(curl);
 
@@ -174,27 +355,20 @@
   free(buffer.data);
   curl_global_cleanup();
 
+  PostDog_Request_SaveFile(
+    url,
+    method,
+    headers,
+    body,
+    response);
   return 0;
 }
 
-typedef struct {
-  Rectangle rectangle;
-  char *label;
-  bool active;
-} TabItem;
-
-typedef enum {
-  TAB_HEADER = 0,
-  TAB_BODY,
-  TAB_GET_PARAMS,
-  TAB_BAR,
-} PostDog_Tab_Enum;
-
 void PostDog_Update_URL(char **p_url_input_text, char* get_params)
 {
   char *url_input_text = *p_url_input_text;
 
-  // Reset
+  // Reset 
   char *question_mark = strchr(url_input_text, '?');
   if (question_mark)
     *question_mark = '\0';
@@ -207,43 +381,23 @@
 
   Dowa_Arena *arena = Dowa_Arena_Create(1024*1024);
   char **lines = Dowa_String_Split(get_params, "\n", get_params_length, 1, arena);
-
-  size_t url_len = strlen(url_input_text);
-  size_t url_capacity = URL_TEXT_BUFFER;
-
   for (int i = 0; i < Dowa_Array_Length(lines); i++)
   {
     char *line = lines[i];
     char **key_value = Dowa_String_Split(line, " ", (int)strlen(line), 1, arena);
 
     if (Dowa_Array_Length(key_value) < 2)
-      continue;  // Skip this line, not break entire loop
-
-    // Check buffer capacity before each append
-    size_t needed = url_len + strlen(separator) + strlen(key_value[0]) + 1 + 10; // +10 for "=" and safety
-    if (needed >= url_capacity) break;
+      break;
 
     strcat(url_input_text, separator);
     strcat(url_input_text, key_value[0]);
     strcat(url_input_text, "=");
-    url_len = strlen(url_input_text);
-
     for (int i = 1; i < Dowa_Array_Length(key_value); i++)
     {
       if (!key_value[i] || key_value[i][0] == '\0')
         break;
-
-      size_t value_len = strlen(key_value[i]);
-      needed = url_len + value_len + 4; // +4 for "%20" if needed
-      if (needed >= url_capacity) break;
-
-      printf("\n\n------\n\n");
-      printf("key_value[%i]: %s, p = %p\n", i, key_value[i], key_value[i]);
-      printf("\n\n------\n\n");
-
       if (i > 1) strcat(url_input_text, "%20");
       strcat(url_input_text, key_value[i]);
-      url_len = strlen(url_input_text);
     }
     separator = "&";
   }
@@ -251,6 +405,100 @@
   Dowa_Arena_Free(arena);
 }
 
+int PostDog_String_To_MethodEnum(char *value)
+{
+  if (strstr(value, "GET"))
+    return 0;
+  if (strstr(value, "POST"))
+    return 1;
+  if (strstr(value, "PUT"))
+    return 2;
+  if (strstr(value, "DELETE"))
+    return 3;
+  return 0;
+}
+
+void PostDog_Params_Reset(
+    char **p_url_input_text,
+    int  *p_active_method_dropdown,
+    char **url_body_map,
+    char **p_url_result_text
+)
+{
+  char *url_input_text = *p_url_input_text;
+  char *url_result_text = *p_url_result_text;
+  int active_method_dropdown = *p_active_method_dropdown;
+
+  url_input_text = "";
+  url_result_text = "";
+  active_method_dropdown = 0;
+  for (int i = 0; i < Dowa_Array_Length(url_body_map); i++)
+    url_body_map[i] = "";
+}
+
+void PostDog_Load_File(
+    const char *filename,
+    char **p_url_input_text,
+    int  *p_active_method_dropdown,
+    char **url_body_map,
+    char **p_url_result_text
+) { 
+  char *url_input_text = *p_url_input_text;
+  char *url_result_text = *p_url_result_text;
+  int active_method_dropdown = *p_active_method_dropdown;
+
+  char full_file_path[512] = {0};
+  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
+  FILE *file = fopen(full_file_path, "r");
+  if (!file)
+    return;
+
+  fseek(file, 0, SEEK_END);
+  size_t file_size = ftell(file);
+  fseek(file, 0, SEEK_SET);
+
+  Dowa_Arena *init_arena = Dowa_Arena_Create(file_size + 2);
+  Dowa_Arena *split_arena = Dowa_Arena_Create(file_size * 2);
+  char *file_buffer = Dowa_Arena_Allocate(init_arena, file_size+1);
+  fread(file_buffer, 1, file_size, file);
+  char **values = Dowa_String_Split(file_buffer, "---\n", file_size, 4, split_arena);
+  Dowa_Arena_Free(init_arena);
+  for (int i = 0; i < Dowa_Array_Length(values); i++)
+  {
+    if (i == 0)
+    {
+      snprintf(url_input_text, strlen(values[i]) + 1, "%s", values[i]);
+      url_input_text[strcspn(url_input_text, "\n")] = '\0';
+    }
+    else if (i == 1)
+      active_method_dropdown = PostDog_String_To_MethodEnum(values[i]);
+    else if (i <= 3)
+    {
+      snprintf(url_body_map[i-2], strlen(values[i]) + 1, "%s", values[i]);
+      for (int j = strlen(values[i]); j > 0; j--)
+      {
+        if (url_body_map[i-2][j] == '\n')
+        {
+          url_body_map[i-2][j] = '\0';
+          break;
+        }
+      }
+    }
+    else
+      snprintf(url_result_text, strlen(values[i]) + 1, "%s", values[i]);
+  }
+}
+
+Rectangle AddPadding(Rectangle rect, float padding)
+{
+  return (Rectangle){ 
+    rect.x + padding,
+    rect.y + padding,
+    rect.width - (2 * padding),
+    rect.height - (2 * padding)
+  };
+}
+
 int main()
 {
   // -- initizlied --//
@@ -260,31 +508,39 @@
 
   Dowa_Arena *arena = Dowa_Arena_Create(AREANA_BUFFER_LENGTH);
 
+  Font customFont = LoadFontEx("postdog/Roboto-Regular.ttf", 20, 0, 0);
+  GuiSetFont(customFont);
+  GuiSetStyle(DEFAULT, TEXT_SIZE, 20);
+
   // -- Starting pos ---//
+  // Everyhting is relative to sidebar at this point lol
   float side_bar_x = 10;
   float side_bar_y = 10;
-
   Rectangle history_sidebar = { .x = side_bar_x, .y = side_bar_y, .width = 0, .height = 0 };
+  Dowa_Array_Reserve(history_items, 10);
+  Dowa_Array_Reserve(new_history_items, 10);
+  PostDog_History_Load(&history_items);
 
   Rectangle url_area = { 0 };
   Rectangle textBounds = { 0 };
   Rectangle url_input_bounds = { 0 };
-  bool url_input_bool = false;
+  bool url_input_edit = false;
   Rectangle url_text_bounds = { 0 };
   Rectangle url_enter_button = { 0 };
   Rectangle method_dropdown = { 0 };
 
+
   char *url_input_text = (char *)Dowa_Arena_Allocate(arena, URL_TEXT_BUFFER);
   snprintf(url_input_text, URL_TEXT_BUFFER, "https://httpbin.org/get");
 
-  INPUT_HASHMAP *url_body_map = NULL;
-  Dowa_HashMap_Push_Arena(url_body_map, "Header", (char *)Dowa_Arena_Allocate(arena, HEADER_BUFFER_LENGTH), arena);
-  Dowa_HashMap_Push_Arena(url_body_map, "Body", (char *)Dowa_Arena_Allocate(arena, BODY_BUFFER_LENGTH), arena);
-  Dowa_HashMap_Push_Arena(url_body_map, "Get Param", (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
-  Dowa_HashMap_Push_Arena(url_body_map, "Bar",  (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
+  char **url_body_map = NULL;
+  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, HEADER_BUFFER_LENGTH), arena);
+  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, BODY_BUFFER_LENGTH), arena);
+  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
+  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
 
-  snprintf(url_body_map[0].value, HEADER_BUFFER_LENGTH, "Content-Type: application/json");
-  snprintf(url_body_map[1].value, HEADER_BUFFER_LENGTH, "");
+  snprintf(url_body_map[TAB_HEADER], HEADER_BUFFER_LENGTH, "Content-Type: application/json");
+  snprintf(url_body_map[TAB_BODY], HEADER_BUFFER_LENGTH, "");
 
   char *url_result_text = (char *)Dowa_Arena_Allocate(arena, RESULT_BUFFER_LENGTH);
 
@@ -305,7 +561,7 @@
   Rectangle result_body = { 0 };
 
   // General styling.
-  int padding = 10; // TODO make it % based?
+  float padding = 10; // TODO make it % based?
   int active_input_tab = 0;
 
   while (!WindowShouldClose())
@@ -314,7 +570,19 @@
     int screen_height = GetScreenHeight();
 
     history_sidebar.width = screen_width * 0.15;
-    history_sidebar.height = screen_width - 10;
+    history_sidebar.height = screen_width - 10; 
+
+    int32 new_history_items_length = Dowa_Array_Length(new_history_items);
+    int32 history_item_length = Dowa_Array_Length(history_items);
+    int32 total =  new_history_items_length + history_item_length;
+    for (int i = 0; i < total; i++)
+    {
+      HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length];
+      curr_history_items->rect.x = history_sidebar.x + padding;
+      curr_history_items->rect.y = history_sidebar.y + (padding * 2 * (i+1)) + (i * history_sidebar.height * 0.05);
+      curr_history_items->rect.width = history_sidebar.width - (padding * 2);
+      curr_history_items->rect.height = history_sidebar.height * 0.05;
+    }
 
     // -- URL Area --//
     url_area.x = (side_bar_x + history_sidebar.width);
@@ -371,24 +639,32 @@
     result_body.width = url_area.width * 0.49 - padding;
     result_body.height = result_area.height - input_tab.height - padding;
 
+    Vector2 mouse_position = GetMousePosition();
+
     BeginDrawing();
       ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));
 
-      // Sidebar Rect
       DrawRectangleRec(history_sidebar, Fade(GRAY, 0.1f));
+      for (int i = 0; i < total; i++)
+      {
+        HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length];
+        DrawRectangleRec(curr_history_items->rect, Fade(RED, 0.1f)); 
+        GuiDrawText(curr_history_items->filename, AddPadding(curr_history_items->rect, padding), TEXT_ALIGN_CENTER, RED);
+      }
 
       // URL area Rect
       GuiDrawText("URL: ", url_text_bounds, TEXT_ALIGN_CENTER, RED); 
       DrawRectangleRec(url_area, Fade(RED, 0.1f));
-      if (GuiTextBox(url_input_bounds, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_bool))
-        url_input_bool = !url_input_bool;
+      if (GuiTextBox(url_input_bounds, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_edit))
+        url_input_edit = !url_input_edit;
+
       sendRequest = GuiButton(url_enter_button, "ENTER");
       if (sendRequest)
-        PostDog_Make_HttpRequest(
+        PostDog_Http_Request(
             url_input_text,
             PostDog_Enum_To_String(active_method_dropdown),
-            url_body_map[0].value,
-            url_body_map[1].value,
+            url_body_map[TAB_HEADER],
+            url_body_map[TAB_BODY],
             url_result_text,
             RESULT_BUFFER_LENGTH
         );
@@ -399,15 +675,43 @@
       DrawRectangleRec(input_area, Fade(BLUE, 0.1f));
       DrawRectangleRec(input_tab,  Fade(DARKBLUE, 0.1f));
       GuiSetStyle(TOGGLE, GROUP_PADDING, 0);
-      if (JUNE_GuiTextBox(input_body, url_body_map[active_input_tab].value, DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
+      if (JUNE_GuiTextBox(input_body, url_body_map[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
         input_body_bool = !input_body_bool;
       GuiToggleGroup(input_tab_item, "Header;Body;Get Param;Bar", &active_input_tab);
-      PostDog_Update_URL(&url_input_text, url_body_map[TAB_GET_PARAMS].value);
+
+      PostDog_Update_URL(&url_input_text, url_body_map[TAB_GET_PARAMS]);
 
       // Result Rect
       DrawRectangleRec(result_area, Fade(GREEN, 0.1f));
       DrawRectangleRec(result_body, Fade(DARKGREEN, 0.1f));
       GuiTextBoxMulti(result_body, url_result_text, RESULT_BUFFER_LENGTH, false);
+
+      if (url_input_edit && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
+      {
+        SetClipboardText(url_input_text);
+      }
+      else if (input_body_bool && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
+      {
+        SetClipboardText(url_body_map[active_input_tab]);
+      }
+      else if (InArea(mouse_position, result_body))
+      {
+        DrawRectangleRec(result_body, Fade(GREEN, 0.3f));
+        if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
+          SetClipboardText(url_result_text);
+      }
+
+      for (int i = 0; i < Dowa_Array_Length(history_items); i++)
+      {
+        if (Clicked(mouse_position, history_items[i].rect))
+          PostDog_Load_File(
+              history_items[i].filename,
+              &url_input_text,
+              &active_method_dropdown,
+              url_body_map,
+              &url_result_text
+          );
+      }
     EndDrawing();
   }
   CloseWindow();