ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
reader.c
Go to the documentation of this file.
1
9
12#include <ascii-chat/log/log.h>
14#include <stdlib.h>
15#include <stdio.h>
16#include <string.h>
17#include <libplatform/compiler.h>
18#include <libplatform/system.h>
19#include <libplatform/terminal.h>
20
21typedef struct terminal_fd_reader_s {
22 FILE *fp; // File pointer created from FD
24 int frame_width; // Detected from first frame (0 = not yet detected)
25 char *line_buffer; // Temporary buffer for one line
28 bool first_frame_processed; // Track if we've processed first frame yet
30
31#define LINE_BUFFER_SIZE 16384 // Max bytes per line
32
34 if (fd < 0 || frame_height <= 0 || !out) {
35 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid reader parameters: fd=%d, height=%d, output=%p", fd, frame_height,
36 (void *)out);
37 }
38
39 FILE *fp = platform_fdopen("terminal_fd_reader", fd, "r");
40 if (!fp) {
41 return SET_ERRNO_SYS(ERROR_INVALID_PARAM, "fdopen failed for FD %d", fd);
42 }
43
44 terminal_fd_reader_t *reader = SAFE_CALLOC(1, sizeof(*reader), terminal_fd_reader_t *);
45 reader->fp = fp;
46 reader->frame_height = frame_height;
47 reader->frame_width = 0; // Will be detected from first frame
48 reader->line_buffer = SAFE_MALLOC(LINE_BUFFER_SIZE, char *);
50 reader->eof_reached = false;
51 reader->first_frame_processed = false;
52
53 log_debug("created with fd=%d, frame height %d (width auto-detect)", fd, frame_height);
54 *out = reader;
55 return ASCIICHAT_OK;
56}
57
59 if (!reader || !out_frame) {
60 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
61 }
62
63 if (reader->eof_reached) {
64 *out_frame = NULL;
65 return ASCIICHAT_OK; // EOF, but no error
66 }
67
68 // Allocate buffer for complete frame (frame_height lines)
69 // Estimate: 16KB per line should be enough for ANSI + ASCII
70 size_t frame_capacity = reader->frame_height * LINE_BUFFER_SIZE;
71 char *frame_data = SAFE_MALLOC(frame_capacity, char *);
72 size_t frame_pos = 0;
73
74 // Read exactly frame_height lines from FD
75 for (int line_num = 0; line_num < reader->frame_height; line_num++) {
76 // Read one line from FD
77 char *line = fgets(reader->line_buffer, (int)reader->line_buffer_size, reader->fp);
78
79 if (!line) {
80 // EOF reached
81 reader->eof_reached = true;
82
83 // If we read some lines but not a complete frame, that's ok - return partial
84 if (frame_pos > 0) {
85 log_debug("EOF reached after %d/%d lines, returning partial frame", line_num, reader->frame_height);
86 // Remove trailing newline from last line if present
87 if (frame_pos > 0 && frame_data[frame_pos - 1] == '\n') {
88 frame_pos--;
89 }
90 frame_data[frame_pos] = '\0';
91 *out_frame = frame_data;
92 return ASCIICHAT_OK;
93 }
94
95 SAFE_FREE(frame_data);
96 *out_frame = NULL;
97 return ASCIICHAT_OK; // EOF
98 }
99
100 // Append line to frame
101 size_t line_len = strlen(line);
102 if (frame_pos + line_len >= frame_capacity) {
103 log_warn("frame buffer overflow, line %d too large", line_num);
104 // Realloc and continue
105 frame_capacity *= 2;
106 char *new_frame = SAFE_MALLOC(frame_capacity, char *);
107 memcpy(new_frame, frame_data, frame_pos);
108 SAFE_FREE(frame_data);
109 frame_data = new_frame;
110 }
111
112 memcpy(frame_data + frame_pos, line, line_len);
113 frame_pos += line_len;
114 }
115
116 // Remove trailing newline from last line (we'll add it back in display)
117 if (frame_pos > 0 && frame_data[frame_pos - 1] == '\n') {
118 frame_pos--;
119 }
120 frame_data[frame_pos] = '\0';
121
122 // Detect frame width from first frame (count visible chars in first line)
123 if (!reader->first_frame_processed && frame_pos > 0) {
124 reader->first_frame_processed = true;
125
126 // Find first line by looking for newline
127 char *first_newline = strchr(frame_data, '\n');
128 if (first_newline) {
129 // Null-terminate first line temporarily for width calculation
130 char saved_char = *first_newline;
131 *first_newline = '\0';
132 reader->frame_width = display_width(frame_data);
133 *first_newline = saved_char; // Restore
134 } else {
135 // Only one line in frame, use it for width
136 reader->frame_width = display_width(frame_data);
137 }
138
139 log_info("detected frame width %d from first frame", reader->frame_width);
140 }
141
142 *out_frame = frame_data;
143 log_debug_every(NS_PER_SEC_INT, "read frame (%zu bytes, %dx%d)", frame_pos, reader->frame_width,
144 reader->frame_height);
145 return ASCIICHAT_OK;
146}
147
149 if (!reader)
150 return;
151 if (reader->fp) {
152 fclose(reader->fp);
153 reader->fp = NULL;
154 }
155 SAFE_FREE(reader->line_buffer);
156 SAFE_FREE(reader);
157}
#define SET_ERRNO(code,...)
#define SET_ERRNO_SYS(code,...)
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define NS_PER_SEC_INT
Definition time.h:169
int display_width(const char *text)
Calculate the visible display width of text with ANSI escape codes.
🖥️ Display Width and Terminal Centering Utilities
📝 Logging API with multiple log levels and terminal output control
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749
static bool size_t gotel_query_t * out
Definition queries.h:8
struct terminal_fd_reader_s terminal_fd_reader_t
void terminal_fd_reader_destroy(terminal_fd_reader_t *reader)
Destroy terminal FD reader.
Definition reader.c:148
asciichat_error_t terminal_fd_reader_next(terminal_fd_reader_t *reader, char **out_frame)
Read next frame from file descriptor.
Definition reader.c:58
asciichat_error_t terminal_fd_reader_create(int fd, int frame_height, terminal_fd_reader_t **out)
Create a terminal FD reader from a file descriptor.
Definition reader.c:33
#define LINE_BUFFER_SIZE
Definition reader.c:31
Read ANSI ASCII frames from file descriptor, chunked by height.
🔤 String Manipulation and Shell Escaping Utilities
size_t line_buffer_size
Definition reader.c:26
bool first_frame_processed
Definition reader.c:28