ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
server_status.c
Go to the documentation of this file.
1
6#include <ascii-chat/ui/server_status.h>
7#include <ascii-chat/ui/terminal_screen.h>
8#include <ascii-chat/log/interactive_grep.h>
9#include <ascii-chat/session/session_log_buffer.h>
10#include <ascii-chat/log/grep.h>
11#include <ascii-chat/platform/abstraction.h>
12#include <ascii-chat/platform/system.h>
13#include <ascii-chat/platform/terminal.h>
14#include <ascii-chat/platform/keyboard.h>
15#include <ascii-chat/options/options.h>
16#include <ascii-chat/util/display.h>
17#include <ascii-chat/util/ip.h>
18#include <ascii-chat/util/time.h>
19#include <ascii-chat/common.h>
20#include <stdio.h>
21#include <string.h>
22#include <time.h>
23#include <stdatomic.h>
24
26 // Delegate to shared session log buffer
28}
29
31 // Delegate to shared session log buffer
33}
34
36 // Delegate to shared session log buffer
38}
39
40void server_status_log_append(const char *message) {
41 // Delegate to shared session log buffer
43}
44
45// ============================================================================
46// Status Display
47// ============================================================================
48
49asciichat_error_t server_status_gather(tcp_server_t *server, const char *session_string, const char *ipv4_address,
50 const char *ipv6_address, uint16_t port, time_t start_time,
51 const char *mode_name, bool session_is_mdns_only, server_status_t *out_status) {
52 if (!server || !out_status || !mode_name) {
53 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for server_status_gather");
54 }
55
56 memset(out_status, 0, sizeof(*out_status));
57
58 // Copy session string
59 if (session_string) {
60 SAFE_STRNCPY(out_status->session_string, session_string, sizeof(out_status->session_string));
61 }
62
63 // Format IPv4 address
64 out_status->ipv4_bound = (ipv4_address && ipv4_address[0] != '\0');
65 if (out_status->ipv4_bound) {
66 snprintf(out_status->ipv4_address, sizeof(out_status->ipv4_address), "%s:%u", ipv4_address, port);
67 }
68
69 // Format IPv6 address
70 out_status->ipv6_bound = (ipv6_address && ipv6_address[0] != '\0');
71 if (out_status->ipv6_bound) {
72 snprintf(out_status->ipv6_address, sizeof(out_status->ipv6_address), "[%s]:%u", ipv6_address, port);
73 }
74
75 out_status->port = port;
76 out_status->start_time = start_time;
77 out_status->mode_name = mode_name;
78 out_status->session_is_mdns_only = session_is_mdns_only;
79 out_status->connected_count = tcp_server_get_client_count(server);
80
81 return ASCIICHAT_OK;
82}
83
90static void truncate_to_fit(const char *src, int max_display_width, char *dst, size_t dst_size) {
91 if (!src || !dst || max_display_width < 0) {
92 if (dst && dst_size > 0) {
93 dst[0] = '\0';
94 }
95 return;
96 }
97
98 // Check if it fits without truncation
99 int width = display_width(src);
100 if (width <= max_display_width) {
101 SAFE_STRNCPY(dst, src, dst_size);
102 return;
103 }
104
105 // Reserve 3 characters for ellipsis
106 int target_width = max_display_width - 3;
107 if (target_width <= 0) {
108 dst[0] = '\0';
109 return;
110 }
111
112 // Try progressively shorter versions until one fits
113 size_t src_len = strlen(src);
114 for (size_t truncate_at = src_len; truncate_at > 0; truncate_at--) {
115 // Create test string with this length
116 char test_buf[512];
117 if (truncate_at >= sizeof(test_buf) - 4) {
118 continue;
119 }
120
121 memcpy(test_buf, src, truncate_at);
122 test_buf[truncate_at] = '\0';
123
124 // Check if this fits
125 if (display_width(test_buf) <= target_width) {
126 // Found a length that fits - copy it and add ellipsis
127 safe_snprintf(dst, dst_size, "%.*s...", (int)truncate_at, src);
128 return;
129 }
130 }
131
132 // Fallback: just ellipsis if nothing else fits
133 SAFE_STRNCPY(dst, "...", dst_size);
134}
135
147static void render_server_status_header(terminal_size_t term_size, void *user_data) {
148 const server_status_t *status = (const server_status_t *)user_data;
149 if (!status) {
150 return;
151 }
152
153 // Calculate uptime
154 time_t now = time(NULL);
155 time_t uptime_secs = now - status->start_time;
156 int uptime_hours = uptime_secs / SEC_PER_HOUR;
157 int uptime_mins = (uptime_secs % SEC_PER_HOUR) / SEC_PER_MIN;
158 int uptime_secs_rem = uptime_secs % 60;
159
160 // Line 1: Top border
161 printf("\033[1;36m━");
162 for (int i = 1; i < term_size.cols - 1; i++) {
163 printf("━");
164 }
165 printf("\033[0m\n");
166
167 // Line 2: Title + Stats (centered)
168 char status_line[512];
169 char status_line_truncated[512];
170 char status_line_colored[600];
171 char uptime_str[12];
172 format_uptime_hms(uptime_hours, uptime_mins, uptime_secs_rem, uptime_str, sizeof(uptime_str));
173 snprintf(status_line, sizeof(status_line), "ascii-chat %s | 👥 %zu | ⏱️ %s", status->mode_name,
174 status->connected_count, uptime_str);
175
176 // Truncate if needed
177 truncate_to_fit(status_line, term_size.cols - 2, status_line_truncated, sizeof(status_line_truncated));
178 snprintf(status_line_colored, sizeof(status_line_colored), "\033[1;36m%s\033[0m", status_line_truncated);
179
180 int padding = display_center_horizontal(status_line_truncated, term_size.cols);
181 for (int i = 0; i < padding; i++) {
182 printf(" ");
183 }
184 printf("%s\n", status_line_colored);
185
186 // Line 3: Session + Addresses (centered)
187 char addr_line[512];
188 char addr_line_truncated[512];
189 int pos = 0;
190 if (status->session_string[0] != '\0') {
191 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, "🔗 %s", status->session_string);
192 }
193 if (status->ipv4_bound && pos < (int)sizeof(addr_line) - 30) {
194 if (pos > 0) {
195 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, " | ");
196 }
197 // Extract IP and get type
198 char ipv4_only[64];
199 if (extract_ip_from_address(status->ipv4_address, ipv4_only, sizeof(ipv4_only)) == 0) {
200 const char *type = get_ip_type_string(ipv4_only);
201 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s (%s)", status->ipv4_address, type);
202 } else {
203 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s", status->ipv4_address);
204 }
205 }
206 if (status->ipv6_bound && pos < (int)sizeof(addr_line) - 30) {
207 if (pos > 0) {
208 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, " | ");
209 }
210 // Extract IP and get type
211 char ipv6_only[64];
212 if (extract_ip_from_address(status->ipv6_address, ipv6_only, sizeof(ipv6_only)) == 0) {
213 const char *type = get_ip_type_string(ipv6_only);
214 snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s (%s)", status->ipv6_address, type);
215 } else {
216 snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s", status->ipv6_address);
217 }
218 }
219
220 // Truncate if needed
221 truncate_to_fit(addr_line, term_size.cols - 2, addr_line_truncated, sizeof(addr_line_truncated));
222
223 int addr_padding = display_center_horizontal(addr_line_truncated, term_size.cols);
224 for (int i = 0; i < addr_padding; i++) {
225 printf(" ");
226 }
227 printf("%s\n", addr_line_truncated);
228
229 // Line 4: Bottom border
230 printf("\033[1;36m━");
231 for (int i = 1; i < term_size.cols - 1; i++) {
232 printf("━");
233 }
234 printf("\033[0m\n");
235}
236
237void server_status_display(const server_status_t *status) {
238 if (!status) {
239 return;
240 }
241
242 // Only render the status screen in interactive mode
243 // In non-interactive mode, logs flow to stdout/stderr normally
245 return;
246 }
247
248 // If --grep pattern was provided, enter interactive grep mode with it pre-populated
249 // Only do this once (check if not already entering)
250 static bool grep_mode_entered = false;
251 if (!grep_mode_entered && grep_get_last_pattern() && grep_get_last_pattern()[0] != '\0') {
253 grep_mode_entered = true;
254 }
255
256 // Use terminal_screen abstraction for rendering
257 terminal_screen_config_t config = {
258 .fixed_header_lines = 4,
259 .render_header = render_server_status_header,
260 .user_data = (void *)status,
261 .show_logs = true,
262 };
263
264 terminal_screen_render(&config);
265}
266
271bool server_status_display_interactive(const server_status_t *status) {
272 if (!status) {
273 return true;
274 }
275
276 // Only render the status screen in interactive mode
278 return true;
279 }
280
281 // If --grep pattern was provided, enter interactive grep mode with it pre-populated
282 static bool grep_mode_entered = false;
283 if (!grep_mode_entered && grep_get_last_pattern() && grep_get_last_pattern()[0] != '\0') {
285 grep_mode_entered = true;
286 }
287
288 // Initialize keyboard for interactive grep
289 bool keyboard_enabled = false;
290 if (keyboard_init() == ASCIICHAT_OK) {
291 keyboard_enabled = true;
292 }
293
294 // Use terminal_screen abstraction for rendering
295 terminal_screen_config_t config = {
296 .fixed_header_lines = 4,
297 .render_header = render_server_status_header,
298 .user_data = (void *)status,
299 .show_logs = true,
300 };
301
302 terminal_screen_render(&config);
303
304 // Poll keyboard for Escape to exit or for interactive grep
305 bool should_exit_status = false;
306 if (keyboard_enabled) {
307 keyboard_key_t key = keyboard_read_nonblocking();
308 if (key == KEY_ESCAPE) {
309 // Escape key: cancel grep if active, otherwise exit status screen
311 interactive_grep_exit_mode(false); // Cancel grep without applying
312 } else {
313 should_exit_status = true; // Exit status screen
314 }
315 } else if (key != KEY_NONE && interactive_grep_should_handle(key)) {
317 }
318 }
319
320 // Cleanup keyboard
321 if (keyboard_enabled) {
322 keyboard_destroy();
323 }
324
325 return !should_exit_status; // Return false if user wants to exit
326}
327
328void server_status_update(tcp_server_t *server, const char *session_string, const char *ipv4_address,
329 const char *ipv6_address, uint16_t port, time_t start_time, const char *mode_name,
330 bool session_is_mdns_only, uint64_t *last_update_ns) {
331 if (!server || !last_update_ns) {
332 return;
333 }
334
335 // Update at FPS rate (60 Hz = 16.67ms by default)
336 uint32_t fps = GET_OPTION(fps);
337 if (fps == 0) {
338 fps = 60; // Default
339 }
340
341 // Calculate frame interval in microseconds
342 uint64_t frame_interval_us = US_PER_SEC_INT / fps;
343
344 // Get current time in microseconds using platform abstraction
345 uint64_t now_us = platform_get_monotonic_time_us();
346
347 // Check if enough time has passed
348 if ((now_us - *last_update_ns) < frame_interval_us) {
349 return; // Too soon, skip frame
350 }
351
352 server_status_t status;
353 if (server_status_gather(server, session_string, ipv4_address, ipv6_address, port, start_time, mode_name,
354 session_is_mdns_only, &status) == ASCIICHAT_OK) {
355 server_status_display(&status);
356 *last_update_ns = now_us;
357 }
358}
const char * grep_get_last_pattern(void)
Definition grep.c:1389
void interactive_grep_exit_mode(bool accept)
void interactive_grep_enter_mode(void)
bool interactive_grep_is_active(void)
asciichat_error_t interactive_grep_handle_key(keyboard_key_t key)
bool interactive_grep_should_handle(int key)
int extract_ip_from_address(const char *addr_with_port, char *ip_out, size_t ip_out_size)
Definition ip.c:1053
const char * get_ip_type_string(const char *ip)
Definition ip.c:1091
size_t tcp_server_get_client_count(tcp_server_t *server)
int display_width(const char *text)
int display_center_horizontal(const char *text, int terminal_width)
bool terminal_is_interactive(void)
uint64_t platform_get_monotonic_time_us(void)
bool server_status_display_interactive(const server_status_t *status)
Display status screen with keyboard input support Returns true if status screen should continue,...
void server_status_log_destroy(void)
asciichat_error_t server_status_gather(tcp_server_t *server, const char *session_string, const char *ipv4_address, const char *ipv6_address, uint16_t port, time_t start_time, const char *mode_name, bool session_is_mdns_only, server_status_t *out_status)
void server_status_log_append(const char *message)
void server_status_log_clear(void)
void server_status_display(const server_status_t *status)
void server_status_update(tcp_server_t *server, const char *session_string, const char *ipv4_address, const char *ipv6_address, uint16_t port, time_t start_time, const char *mode_name, bool session_is_mdns_only, uint64_t *last_update_ns)
void server_status_log_init(void)
bool session_log_buffer_init(void)
void session_log_buffer_append(const char *message)
void session_log_buffer_clear(void)
void session_log_buffer_destroy(void)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
void terminal_screen_render(const terminal_screen_config_t *config)
int format_uptime_hms(int hours, int minutes, int seconds, char *buffer, size_t buffer_size)
Definition util/time.c:369