💾 Archived View for gemini.rmf-dev.com › repo › Vaati › tuimarket › files › a10d98bca8d4527aa4f8fbf3… captured on 2023-09-08 at 16:45:24. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 #include <stdio.h>
1 #include <stdlib.h>
2 #include <stdint.h>
3 #include <unistd.h>
4 #include <fcntl.h>
5 #include <string.h>
6 #include <pwd.h>
7 #include <time.h>
8 #include <pthread.h>
9 #include <errno.h>
10 #include <curl/curl.h>
11 #include "termbox.h"
12 #include "strlcpy.h"
13 #include "strnstr.h"
14 #include "config.h"
15
16 #ifndef PATH_MAX
17 #define PATH_MAX 1024
18 #endif
19 #define SIZEOF(X) sizeof(X) / sizeof(*X)
20
21 const char alloc_fail[] = "memory allocation failure\n";
22
23 struct symbol {
24 char symbol[16];
25 char name[256];
26 float price;
27 float previous_price;
28 };
29 struct symbol *symbols = NULL;
30 size_t symbols_length = 0;
31
32 const char query_price[] =
33 "https://query2.finance.yahoo.com/v7/finance/options/%s";
34
35 const char *paths[] = {
36 ".config/tuimarket/symbols",
37 ".tuimarket/symbols",
38 ".tuimarket_symbols",
39 };
40
41 struct mem {
42 char *memory;
43 size_t size;
44 };
45 struct mem chunk;
46
47 static size_t writecb(void *contents, size_t size, size_t nmemb, void *userp) {
48
49 size_t realsize = size * nmemb;
50 struct mem *mem = (struct mem*)userp;
51
52 mem->memory = realloc(mem->memory, mem->size + realsize + 1);
53 if(mem->memory == NULL) {
54 printf(alloc_fail);
55 return 0;
56 }
57
58 memcpy(&(mem->memory[mem->size]), contents, realsize);
59 mem->size += realsize;
60 mem->memory[mem->size] = 0;
61 return realsize;
62 }
63
64 char *handle_url(char *url, size_t *len) {
65
66 CURL *curl_handle;
67 CURLcode res;
68
69 chunk.memory = malloc(1);
70 if (!chunk.memory) {
71 printf(alloc_fail);
72 return NULL;
73 }
74 chunk.size = 0;
75
76 curl_handle = curl_easy_init();
77 curl_easy_setopt(curl_handle, CURLOPT_URL, url);
78 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writecb);
79 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)&chunk);
80 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
81
82 res = curl_easy_perform(curl_handle);
83 curl_easy_cleanup(curl_handle);
84
85 if(res != CURLE_OK) {
86 printf("curl_easy_perform() failed: %s\n",
87 curl_easy_strerror(res));
88 return NULL;
89 }
90
91 *len = chunk.size;
92
93 return chunk.memory;
94 }
95
96 static ssize_t get_home(char *buf, size_t length) {
97
98 struct passwd *pw;
99 char *home;
100 int fd;
101
102 home = getenv("HOME");
103 if (home) {
104 fd = open(home, O_DIRECTORY);
105 if (fd > -1) {
106 close(fd);
107 return strlcpy(buf, home, length);
108 }
109 }
110
111 pw = getpwuid(geteuid());
112 if (!pw) return -1;
113 fd = open(pw->pw_dir, O_DIRECTORY);
114 if (fd < 0) {
115 close(fd);
116 return -1;
117 }
118 return strlcpy(buf, pw->pw_dir, length);
119 }
120
121 static int load_symbols() {
122
123 FILE *f = NULL;
124 size_t i;
125 ssize_t len;
126 char home[PATH_MAX], path[PATH_MAX];
127
128 len = get_home(home, sizeof(home));
129 if (len == -1) return -1;
130
131 for (i = 0; i < SIZEOF(paths); i++) {
132 snprintf(path, sizeof(path), "%s/%s", home, paths[i]);
133 f = fopen(path, "r");
134 if (f) break;
135 }
136 if (!f) return -1;
137
138 for (i = 0; 1; i++) {
139
140 struct symbol s = {0};
141 size_t len;
142
143 if (!fgets(s.symbol, sizeof(s.symbol), f)) break;
144
145 len = strnlen(s.symbol, sizeof(s.symbol));
146 if (!len || len > sizeof(s.symbol)) break;
147
148 if (s.symbol[len - 1] == '\n') s.symbol[len - 1] = '\0';
149
150 symbols = realloc(symbols, (i + 1) * sizeof(struct symbol));
151 if (!symbols) {
152 printf(alloc_fail);
153 return -1;
154 }
155 symbols[i] = s;
156 }
157 symbols_length = i;
158 fclose(f);
159
160 return 0;
161 }
162
163 const char str_price[] = "\"regularMarketPrice\":";
164 const char str_old_price[] = "\"regularMarketPreviousClose\":";
165 const char str_name[] = "\"shortName\":\"";
166
167 static int find_copy(const char *haystack, const char *needle, size_t hay_len,
168 size_t needle_len, char stop, char *buf, size_t len) {
169
170 char *start, *end;
171
172 start = strnstr(haystack, needle, hay_len);
173 if (!start) return -1;
174
175 start += needle_len - 1;
176 end = start;
177 while (*end && *end != stop) end++;
178
179 if (!*end || (size_t)(end - start) > len) return -1;
180
181 memcpy(buf, start, end - start);
182 buf[end - start] = '\0';
183
184 return 0;
185 }
186
187 static int update_symbol(struct symbol *symbol) {
188
189 char url[2048], buf[64], *data;
190 size_t len;
191 int ret = -1;
192
193 snprintf(url, sizeof(url), query_price, symbol->symbol);
194 data = handle_url(url, &len);
195 if (!data) return -1;
196
197 if (find_copy(data, str_price, len, sizeof(str_price), ',', buf,
198 sizeof(buf)))
199 goto clean;
200 symbol->price = atof(buf);
201
202 if (find_copy(data, str_old_price, len, sizeof(str_old_price), ',', buf,
203 sizeof(buf)))
204 goto clean;
205 symbol->previous_price = atof(buf);
206
207 if (find_copy(data, str_name, len, sizeof(str_name), '"',
208 symbol->name, sizeof(symbol->name)))
209 goto clean;
210
211 ret = 0;
212 clean:
213 free(data);
214
215 return ret;
216 }
217
218 void ansi_sleep(long micro) {
219 struct timeval tv;
220 tv.tv_sec = micro / 1000000;
221 tv.tv_usec = micro % 1000000;
222 select(0, NULL, NULL, NULL, &tv);
223 }
224
225 void *update_thread(void *ptr) {
226
227 int *run = ptr, interval, counter;
228
229 interval = 0;
230 while (*run) {
231 size_t i;
232 for (i = 0; i < symbols_length; i++) {
233 update_symbol(&symbols[i]);
234 counter = 0;
235 while (counter++ < interval * 10 && *run)
236 ansi_sleep(100000 / symbols_length);
237 if (!*run) break;
238 }
239 interval = INTERVAL;
240 }
241 return ptr;
242 }
243
244 #define COL_SYMBOL 2
245 #define COL_NAME (COL_SYMBOL + sizeof("Symbol |") + 1)
246 #define COL_VARIATION (-(signed)sizeof("Variation") - 8)
247 #define COL_PRICE (COL_VARIATION -(signed)sizeof("| Price") - 3)
248
249 int display(int *scroll) {
250
251 struct tb_event ev;
252 struct symbol symbol;
253 int i, w, h, bottom;
254
255 w = tb_width();
256 h = tb_height();
257
258 if ((size_t)h > symbols_length) *scroll = 0;
259
260 tb_clear();
261 for (i = 0; i < w; i++) tb_set_cell(i, 0, ' ', TB_BLACK, TB_WHITE);
262
263 tb_print(COL_SYMBOL - 2, 0, TB_BLACK, TB_WHITE, " Symbol");
264 tb_print(COL_NAME - 2, 0, TB_BLACK, TB_WHITE, "| Name");
265 tb_print(w + COL_PRICE - 2, 0, TB_BLACK, TB_WHITE, "| Price");
266 tb_print(w + COL_VARIATION - 2, 0, TB_BLACK, TB_WHITE, "| Variation");
267
268 bottom = 1;
269 for (i = *scroll; i < (int)symbols_length; i++) {
270 int gain, j, y = i + 1;
271 symbol = symbols[i];
272 gain = (symbol.price >= symbol.previous_price);
273 tb_print(COL_SYMBOL, y - *scroll, TB_DEFAULT, TB_DEFAULT,
274 symbol.symbol);
275 tb_print(COL_NAME, y - *scroll, TB_DEFAULT, TB_DEFAULT,
276 symbol.name);
277
278 j = w + COL_PRICE - 2;
279 while (j++ < w)
280 tb_set_cell(j, y, ' ', TB_DEFAULT, TB_DEFAULT);
281 tb_printf(w + COL_PRICE, y - *scroll, TB_DEFAULT, TB_DEFAULT,
282 "%.2f", symbol.price);
283 tb_printf(w + COL_VARIATION + gain, y - *scroll,
284 gain ? TB_GREEN : TB_RED, TB_DEFAULT, "%.2f (%.2f%%)",
285 symbol.price - symbol.previous_price,
286 symbol.price / symbol.previous_price * 100 - 100);
287 if (y - *scroll >= h) {
288 bottom = 0;
289 break;
290 }
291 }
292
293 tb_present();
294
295 if (!tb_peek_event(&ev, REFRESH)) {
296 if (ev.key == TB_KEY_ESC || ev.ch == 'q') return -1;
297 if ((ev.key == TB_KEY_ARROW_DOWN || ev.ch == 'j') && !bottom)
298 (*scroll)++;
299 if ((ev.key == TB_KEY_ARROW_UP || ev.ch == 'k') && *scroll)
300 (*scroll)--;
301 }
302 return 0;
303 }
304
305 int main(int argc, char *argv[]) {
306
307 int scroll = 0, run;
308 pthread_t thread;
309
310 if (!argc) return sizeof(*argv);
311
312 if (load_symbols()) {
313 printf("cannot find symbols file\n");
314 return -1;
315 }
316
317 curl_global_init(CURL_GLOBAL_ALL);
318
319 if (tb_init()) {
320 printf("tb_init: %s\n", strerror(errno));
321 return -1;
322 }
323
324 run = 1;
325 pthread_create(&thread, NULL, update_thread, &run);
326
327 while (!display(&scroll)) ;
328
329 run = 0;
330 tb_shutdown();
331 pthread_join(thread, NULL);
332 curl_global_cleanup();
333 free(symbols);
334
335 return 0;
336 }
337