A tool for adding anime to your anidb list.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

781 lignes
24KB

  1. #include <stddef.h>
  2. #include <inttypes.h>
  3. #include <getopt.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <stdbool.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include <unistd.h>
  11. #include <errno.h>
  12. #include <assert.h>
  13. //#include <toml.h>
  14. #include <limits.h>
  15. #include <time.h>
  16. #include <unistd.h>
  17. #include <ctype.h>
  18. #include "config.h"
  19. #include "error.h"
  20. #include "util.h"
  21. #include "uio.h"
  22. static int show_help(struct conf_entry *ce);
  23. static int show_subcomm_help(struct conf_entry *ce);
  24. static int config_parse_file();
  25. static enum error config_required_check();
  26. static int config_set_str(struct conf_entry *ce, char *arg);
  27. static int config_set_port(struct conf_entry *ce, char *arg);
  28. static int config_set_bool(struct conf_entry *ce, char *arg);
  29. //static int config_def_cachedb(struct conf_entry *ce);
  30. //static int config_action_write_config(struct conf_entry *ce);
  31. /* Everything not explicitly defined, is 0 */
  32. /* If an option only has a long name, the short name also has to be
  33. * defined. For example, a number larger than UCHAR_MAX */
  34. #define SUBCOMM_HELP { \
  35. .l_name = "help", .s_name = 'h', .has_arg = no_argument, \
  36. .action_func = show_subcomm_help, .in_args = true, \
  37. .type = OTYPE_ACTION, .handle_order = 0, \
  38. .h_desc = "Display the help for the subcommand and exit", }
  39. static struct conf_entry subcomm_def_help_opt = SUBCOMM_HELP;
  40. static struct conf_entry ed2k_subopts[] = {
  41. SUBCOMM_HELP,
  42. { .l_name = "link", .s_name = 'l', .has_arg = no_argument,
  43. .set_func = config_set_bool, .in_args = true,
  44. .type = OTYPE_B, .handle_order = 0, .value_is_set = true,
  45. .h_desc = "Print an ed2k link for the files", },
  46. };
  47. static struct conf_entry modify_add_subopts[] = {
  48. SUBCOMM_HELP,
  49. { .l_name = "watched", .s_name = 'w', .has_arg = no_argument,
  50. .set_func = config_set_bool, .in_args = true,
  51. .type = OTYPE_B, .handle_order = 0, .value_is_set = true,
  52. .h_desc = "Mark the episode as watched when adding/modifying files", },
  53. { .l_name = "wdate", .s_name = UCHAR_MAX + 4, .has_arg = required_argument,
  54. .set_func = config_set_str, .in_args = true,
  55. .type = OTYPE_S, .handle_order = 0,
  56. .h_desc = "Set the watched date when adding/modifying files. Either in unix time or in YY-MM-DDTHH:MM:SS", },
  57. };
  58. static struct conf_entry options[] = {
  59. { .l_name = "help", .s_name = 'h', .has_arg = no_argument,
  60. .action_func = show_help, .in_args = true,
  61. .type = OTYPE_ACTION, .handle_order = 0,
  62. .h_desc = "Display the help and exit", },
  63. { .l_name = "username", .s_name = 'u', .has_arg = required_argument,
  64. .set_func = config_set_str, .in_args = true, .in_file = true,
  65. .type = OTYPE_S, .handle_order = 1,
  66. .h_desc = "Sets the username for the login", },
  67. { .l_name = "password", .s_name = 'p', .has_arg = required_argument,
  68. .set_func = config_set_str, .in_args = true, .in_file = true,
  69. .type = OTYPE_S, .handle_order = 1,
  70. .h_desc = "Sets the password for the login", },
  71. { .l_name = "port", .s_name = 'P', .has_arg = required_argument,
  72. .set_func = config_set_port, .in_args = true, .in_file = true,
  73. .type = OTYPE_HU, .handle_order = 1, .value.hu = 29937,
  74. .value_is_set = true,
  75. .h_desc = "Sets port to use for API server communication", },
  76. { .l_name = "api-server", .s_name = UCHAR_MAX + 1, .has_arg = required_argument,
  77. .set_func = config_set_str, .in_args = true, .in_file = true,
  78. .type = OTYPE_S, .handle_order = 1, .value.s = "api.anidb.net:9000",
  79. .value_is_set = true,
  80. .h_desc = "Sets the API server address", },
  81. { .l_name = "api-key", .s_name = 'k', .has_arg = required_argument,
  82. .set_func = config_set_str, .in_args = true, .in_file = true,
  83. .type = OTYPE_S, .handle_order = 1,
  84. .h_desc = "Sets the api key used for encryption", },
  85. { .l_name = "save-session", .s_name = 's', .has_arg = no_argument,
  86. .set_func = config_set_bool, .in_args = true, .in_file = true,
  87. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  88. .h_desc = "not implemented", },
  89. { .l_name = "destroy-session", .s_name = 'S', .has_arg = no_argument,
  90. .set_func = config_set_bool, .in_args = true, .in_file = false,
  91. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  92. .h_desc = "not implemented", },
  93. { .l_name = "cachedb", .s_name = 'd', .has_arg = required_argument,
  94. .set_func = config_set_str, .in_args = true, .in_file = true,
  95. .type = OTYPE_S, .handle_order = 1, /*.default_func = config_def_cachedb*/
  96. .h_desc = "Sets the path for the cache database", },
  97. { .l_name = "debug", .s_name = 'D', .has_arg = no_argument,
  98. .set_func = config_set_bool, .in_args = true, .in_file = true,
  99. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  100. .h_desc = "Enable debug output", },
  101. /*### cmds ###*/
  102. { .l_name = "server-version", .s_name = UCHAR_MAX + 2,
  103. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  104. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  105. .h_desc = "Request the server version",
  106. },
  107. { .l_name = "version", .s_name = 'v',
  108. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  109. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  110. .h_desc = "Print the caniadd version", },
  111. { .l_name = "uptime", .s_name = UCHAR_MAX + 3,
  112. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  113. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  114. .h_desc = "Request the uptime of the api servers", },
  115. { .l_name = "ed2k", .s_name = 'e',
  116. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  117. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  118. .h_desc = "Run an ed2k hash on the file arguments",
  119. .subopts = ed2k_subopts,
  120. .subopts_count = sizeof(ed2k_subopts) / sizeof(ed2k_subopts[0]),
  121. },
  122. { .l_name = "add", .s_name = 'a',
  123. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  124. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  125. .h_desc = "Add files to your anidb list",
  126. .subopts = modify_add_subopts,
  127. .subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
  128. },
  129. /* Arguments are either mylist id's, or file sizes and names
  130. * in the format '[watch_date/]<size>/<filename>'. The filename can't contain
  131. * '/' characters. */
  132. { .l_name = "modify", .s_name = 'W',
  133. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  134. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  135. .h_desc = "Modify files in your anidb list",
  136. .h_desc_more =
  137. "The arguments are either mylist id's, a file/folder path, or file sizes and names\n"
  138. "in the format '[watch_date/]<size>/<filename>'. The filename can't contain '/' characters\n",
  139. .subopts = modify_add_subopts,
  140. .subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
  141. },
  142. { .l_name = "stats", .s_name = UCHAR_MAX + 10,
  143. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  144. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  145. .h_desc = "Get the mylist status",
  146. },
  147. /*{ .l_name = "stats", .s_name = UCHAR_MAX + 5,
  148. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  149. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },*/
  150. };
  151. static const size_t options_count = sizeof(options) / sizeof(options[0]);
  152. /* Used in show_subcomm_help to output info about the subcommand */
  153. static struct conf_entry *current_subcommand = NULL;
  154. static char **opt_argv = NULL;
  155. static int opt_argc = 0;
  156. static void config_build_getopt_args(int opts_count, const struct conf_entry opts[opts_count],
  157. char out_sopt[opts_count * 2 + 1], struct option out_lopt[opts_count + 1],
  158. bool stop_at_1st_nonopt)
  159. {
  160. int i_sopt = 0, i_lopt = 0;
  161. if (stop_at_1st_nonopt) {
  162. /* Tell getopts to stop at the 1st non-option argument */
  163. out_sopt[i_sopt++] = '+';
  164. }
  165. for (int i = 0; i < opts_count; i++) {
  166. if (opts[i].type == OTYPE_SUBCOMMAND)
  167. continue;
  168. /* Short options */
  169. if (opts[i].s_name && opts[i].s_name <= UCHAR_MAX) {
  170. out_sopt[i_sopt++] = opts[i].s_name;
  171. if (opts[i].has_arg == required_argument)
  172. out_sopt[i_sopt++] = ':';
  173. assert(opts[i].has_arg == required_argument ||
  174. opts[i].has_arg == no_argument);
  175. }
  176. /* Long opts */
  177. if (opts[i].l_name) {
  178. assert(opts[i].s_name);
  179. out_lopt[i_lopt].name = opts[i].l_name;
  180. out_lopt[i_lopt].has_arg = opts[i].has_arg;
  181. out_lopt[i_lopt].flag = NULL;
  182. out_lopt[i_lopt].val = opts[i].s_name;
  183. i_lopt++;
  184. }
  185. }
  186. out_sopt[i_sopt] = '\0';
  187. memset(&out_lopt[i_lopt], 0, sizeof(struct option));
  188. }
  189. static int config_read_args(int argc, char **argv,
  190. int opts_count, struct conf_entry opts[opts_count],
  191. char sopt[opts_count * 2 + 1], struct option lopt[opts_count + 1], int level)
  192. {
  193. int optc, err = NOERR;
  194. /* Reinitialize getopt with 0 here */
  195. optind = 0;
  196. //uio_debug("Before %d %s", argc, argv[0]);
  197. while ((optc = getopt_long(argc, argv, sopt,
  198. lopt, NULL)) >= 0) {
  199. bool handled = false;
  200. //uio_debug("Optc: %c", optc);
  201. for (int i = 0; i < opts_count; i++) {
  202. if (opts[i].handle_order != level) {
  203. /* Lie a lil :x */
  204. handled = true;
  205. continue;
  206. }
  207. if (optc == opts[i].s_name) {
  208. if (opts[i].type != OTYPE_ACTION)
  209. err = opts[i].set_func(&opts[i], optarg);
  210. else
  211. err = opts[i].action_func(&opts[i]);
  212. if (err != NOERR)
  213. goto end;
  214. opts[i].value_is_set = true;
  215. handled = true;
  216. break;
  217. }
  218. }
  219. if (handled)
  220. continue;
  221. if (optc == '?') {
  222. err = ERR_OPT_FAILED;
  223. goto end;
  224. } else {
  225. fprintf(stderr, "Unhandled option? '%c'\n", optc);
  226. err = ERR_OPT_UNHANDLED;
  227. goto end;
  228. }
  229. }
  230. end:
  231. return err;
  232. }
  233. static enum error config_required_check()
  234. {
  235. enum error err = NOERR;
  236. for (int i = 0; i < options_count; i++) {
  237. if (options[i].required && !options[i].value_is_set) {
  238. printf("Argument %s is required!\n", options[i].l_name);
  239. err = ERR_OPT_REQUIRED;
  240. }
  241. }
  242. return err;
  243. }
  244. static enum error config_parse_subcomm_subopts(struct conf_entry *subcomm)
  245. {
  246. char sopt[64];
  247. struct option lopt[32];
  248. enum error err;
  249. /* Set the global current subcommand pointer */
  250. current_subcommand = subcomm;
  251. config_build_getopt_args(subcomm->subopts_count, subcomm->subopts, sopt, lopt, false);
  252. //uio_debug("Parsing subconn %s", subcomm->l_name);
  253. //uio_debug("sopt: %s", sopt);
  254. /* Update args for next parsing and nonopts parsing for later */
  255. opt_argv = &opt_argv[optind];
  256. opt_argc = config_get_nonopt_count();
  257. /* argv[0] (which is the subcommand string) will be treated as the filename here, and skipped */
  258. err = config_read_args(opt_argc, opt_argv,
  259. subcomm->subopts_count, subcomm->subopts, sopt, lopt, 0);
  260. if (err == NOERR) {
  261. /* Mark subcommand as set */
  262. subcomm->value_is_set = true;
  263. subcomm->value.b = true;
  264. }
  265. current_subcommand = NULL;
  266. return err;
  267. }
  268. static enum error config_parse_subcommands()
  269. {
  270. const char *subcomm_str;
  271. enum error err = ERR_OPT_NOTFOUND;
  272. if (config_get_nonopt_count() <= 0) {
  273. return ERR_OPT_NO_SUBCOMMAND;
  274. }
  275. subcomm_str = config_get_nonopt(0);
  276. for (int i = 0; i < options_count; i++) {
  277. if (options[i].type != OTYPE_SUBCOMMAND)
  278. continue;
  279. if (strcmp(options[i].l_name, subcomm_str) != 0)
  280. continue;
  281. if (options[i].subopts == NULL) {
  282. /* If no suboptions is defined, define the default
  283. * help one here */
  284. options[i].subopts = &subcomm_def_help_opt;
  285. options[i].subopts_count = 1;
  286. }
  287. /* Found, parse subopts */
  288. err = config_parse_subcomm_subopts(&options[i]);
  289. }
  290. return err;
  291. }
  292. enum error config_parse(int argc, char **argv)
  293. {
  294. enum error err = NOERR;
  295. char sopt[options_count * 2 + 1];
  296. struct option lopt[options_count + 1];
  297. opt_argv = argv;
  298. opt_argc = argc;
  299. config_build_getopt_args(options_count, options, sopt, lopt, true);
  300. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 0);
  301. if (err != NOERR)
  302. goto end;
  303. err = config_parse_file();
  304. if (err != NOERR)
  305. goto end;
  306. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 1);
  307. if (err != NOERR)
  308. goto end;
  309. /* Set defaults for those, that didn't got set above */
  310. for (int i = 0; i < options_count; i++) {
  311. if (!options[i].value_is_set && options[i].type != OTYPE_ACTION &&
  312. options[i].default_func) {
  313. err = options[i].default_func(&options[i]);
  314. if (err != NOERR)
  315. goto end;
  316. options[i].value_is_set = true;
  317. }
  318. }
  319. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 2);
  320. if (err != NOERR)
  321. goto end;
  322. err = config_required_check();
  323. if (err != NOERR)
  324. goto end;
  325. /* Now that all of the global arguments are parsed, do the subcommands */
  326. err = config_parse_subcommands();
  327. if (err != NOERR) {
  328. if (err == ERR_OPT_NO_SUBCOMMAND)
  329. printf("No subcommand given!\n\n");
  330. else if (err == ERR_OPT_NOTFOUND)
  331. printf("The given subcommand doesn't exist\n\n");
  332. if (err != ERR_OPT_EXIT) /* If err is this, then help() was already called */
  333. show_help(NULL);
  334. }
  335. end:
  336. if (err != NOERR)
  337. config_free();
  338. return err;
  339. }
  340. #if 0
  341. static int config_def_config_dir(struct conf_entry *ce)
  342. {
  343. char *dir;
  344. int len;
  345. const char *format = "%s/.config/" CONFIG_DIR_NAME;
  346. const char *home_env = getenv("HOME");
  347. if (!home_env) {
  348. /* Fix this at a later date with getuid and getpw */
  349. fprintf(stderr, "HOME environment variable not found!\n");
  350. return ERR_NOTFOUND;
  351. }
  352. len = snprintf(NULL, 0, format, home_env);
  353. if (len == -1) {
  354. int err = errno;
  355. fprintf(stderr, "Failed to call funky snpintf: %s\n", strerror(err));
  356. return err;
  357. }
  358. dir = malloc(len + 1);
  359. sprintf(dir, format, home_env);
  360. ce->value.s = dir;
  361. ce->value_is_dyn = true;
  362. return NOERR;
  363. }
  364. #endif
  365. #if 0
  366. static int config_def_cachedb(struct conf_entry *ce)
  367. {
  368. bool dh_free = false;
  369. const char *data_home = getenv("XDG_DATA_HOME");
  370. if (!data_home) {
  371. const char *home = util_get_home();
  372. if (!home)
  373. return ERR_OPT_FAILED;
  374. sprintf(NULL, "%s/.local/share", home);
  375. }
  376. return NOERR;
  377. }
  378. #endif
  379. static int config_set_str(struct conf_entry *ce, char *arg)
  380. {
  381. // TODO use realpath(3), when necessary
  382. ce->value.s = arg;
  383. return NOERR;
  384. }
  385. static int config_set_port(struct conf_entry *ce, char *arg)
  386. {
  387. long portval = strtol(arg, NULL, 10);
  388. /* A zero return will be invalid no matter if strtol succeeded or not */
  389. if (portval > UINT16_MAX || portval <= 0) {
  390. fprintf(stderr, "Invalid port value '%s'\n", arg);
  391. return ERR_OPT_INVVAL;
  392. }
  393. ce->value.hu = (uint16_t)portval;
  394. return NOERR;
  395. }
  396. static int config_set_bool(struct conf_entry *ce, char *arg)
  397. {
  398. ce->value.b = true;
  399. return NOERR;
  400. }
  401. static int show_subcomm_help(struct conf_entry *ce)
  402. {
  403. assert(current_subcommand);
  404. const struct conf_entry *subopts = current_subcommand->subopts;
  405. printf(
  406. "Usage: caniadd [OPTIONS] %s [SUBCOMMAND_OPTIONS]\n"
  407. "%s\n"
  408. "%s"
  409. "\n"
  410. "SUBCOMMAND_OPTIONS:\n",
  411. current_subcommand->l_name,
  412. current_subcommand->h_desc,
  413. current_subcommand->h_desc_more ? current_subcommand->h_desc_more : ""
  414. );
  415. for (size_t i = 0; i < current_subcommand->subopts_count; i++) {
  416. int printed = 0, pad;
  417. printed += printf(" ");
  418. if (subopts[i].l_name)
  419. printed += printf("--%s", subopts[i].l_name);
  420. if (subopts[i].s_name < UCHAR_MAX)
  421. printed += printf(", -%c", subopts[i].s_name);
  422. if (subopts[i].has_arg != no_argument)
  423. printed += printf(" arg");
  424. pad = 25 - printed;
  425. if (pad <= 0)
  426. pad = 1;
  427. printf("%*s%s", pad, "", subopts[i].h_desc);
  428. if (subopts[i].value_is_set) {
  429. printf(" Val: ");
  430. if (subopts[i].type == OTYPE_S)
  431. printed += printf("%s", subopts[i].value.s);
  432. else if (subopts[i].type == OTYPE_HU)
  433. printed += printf("%hu", subopts[i].value.hu);
  434. else if (subopts[i].type == OTYPE_B)
  435. printed += printf("%s", subopts[i].value.b ? "true" : "false");
  436. }
  437. printf("\n");
  438. }
  439. return ERR_OPT_EXIT;
  440. }
  441. static int show_help(struct conf_entry *ce)
  442. {
  443. printf(
  444. "Usage: caniadd [OPTIONS] SUBCOMMAND [SUBCOMMAND_OPTIONS]\n"
  445. "Caniadd will add files to an AniDB list, and possibly more.\n"
  446. "\n"
  447. "OPTIONS:\n"
  448. );
  449. for (size_t i = 0; i < options_count; i++) {
  450. if (options[i].type == OTYPE_SUBCOMMAND)
  451. continue;
  452. int printed = 0, pad;
  453. printed += printf(" ");
  454. if (options[i].l_name)
  455. printed += printf("--%s", options[i].l_name);
  456. if (options[i].s_name < UCHAR_MAX)
  457. printed += printf(", -%c", options[i].s_name);
  458. if (options[i].has_arg != no_argument)
  459. printed += printf(" arg");
  460. pad = 25 - printed;
  461. if (pad <= 0)
  462. pad = 1;
  463. printf("%*s%s", pad, "", options[i].h_desc);
  464. if (options[i].value_is_set) {
  465. printf(" Val: ");
  466. if (options[i].type == OTYPE_S)
  467. printed += printf("%s", options[i].value.s);
  468. else if (options[i].type == OTYPE_HU)
  469. printed += printf("%hu", options[i].value.hu);
  470. else if (options[i].type == OTYPE_B)
  471. printed += printf("%s", options[i].value.b ? "true" : "false");
  472. }
  473. printf("\n");
  474. }
  475. printf("\nSUBCOMMANDS:\n");
  476. for (size_t i = 0; i < options_count; i++) {
  477. if (options[i].type != OTYPE_SUBCOMMAND)
  478. continue;
  479. int printed = 0, pad;
  480. printed += printf(" %s", options[i].l_name);
  481. pad = 25 - printed;
  482. printf("%*s%s\n", pad, "", options[i].h_desc);
  483. }
  484. return ERR_OPT_EXIT;
  485. }
  486. static int config_parse_file()
  487. {
  488. // TODO implement this
  489. #if 0
  490. assert(conf.config_file_path);
  491. FILE *f = fopen(conf.config_file_path, "rb");
  492. char errbuf[200];
  493. toml_table_t *tml = toml_parse_file(f, errbuf, sizeof(errbuf));
  494. fclose(f);
  495. if (!tml) {
  496. fprintf(stderr, "Failed to parse config toml: %s\n", errbuf);
  497. return ERR_TOML_PARSE_ERROR;
  498. }
  499. toml_datum_t port = toml_int_in(tml, "port");
  500. if (port.ok)
  501. conf.port = (uint16_t)port.u.i;
  502. else
  503. fprintf(stderr, "Failed to parse port from config toml: %s\n", errbuf);
  504. toml_datum_t dldir = toml_string_in(tml, "default_download_dir");
  505. if (dldir.ok) {
  506. conf.default_download_dir = dldir.u.s;
  507. printf("%s\n", dldir.u.s);
  508. conf_dyn.default_download_dir = dldir.u.s;
  509. conf_dyn.default_download_dir = true;
  510. /* TODO is this always malloced?? if yes, remve dyn check */
  511. } else {
  512. fprintf(stderr, "Failed to parse download dir from config toml: %s\n", errbuf);
  513. }
  514. toml_free(tml);
  515. #endif
  516. return NOERR;
  517. }
  518. int config_free()
  519. {
  520. for (int i = 0; i < options_count; i++) {
  521. if (options[i].value_is_dyn) {
  522. free(options[i].value.s);
  523. options[i].value.s = NULL;
  524. options[i].value_is_dyn = false;
  525. options[i].value_is_set = false;
  526. }
  527. }
  528. return NOERR;
  529. }
  530. #if 0
  531. static int config_action_write_config(struct conf_entry *ce)
  532. {
  533. /* This is the success return here */
  534. int err = ERR_OPT_EXIT, plen;
  535. const char *config_dir;
  536. FILE *f = NULL;
  537. config_dir = config_get("config-dir");
  538. plen = snprintf(NULL, 0, "%s/%s", config_dir, CONFIG_FILE_NAME);
  539. char path[plen + 1];
  540. snprintf(path, plen + 1, "%s/%s", config_dir, CONFIG_FILE_NAME);
  541. for (int i = 0; i < 2; i++) {
  542. f = fopen(path, "wb");
  543. if (!f) {
  544. int errn = errno;
  545. if (errn == ENOENT && i == 0) {
  546. /* Try to create parent directory */
  547. if (mkdir(config_dir, 0755) == -1) {
  548. err = errno;
  549. fprintf(stderr, "Config mkdir failed: %s\n", strerror(err));
  550. goto end;
  551. }
  552. } else {
  553. err = errn;
  554. fprintf(stderr, "Config fopen failed: %s\n", strerror(err));
  555. goto end;
  556. }
  557. } else {
  558. break;
  559. }
  560. }
  561. for (int i = 0; i < options_count; i++) {
  562. if (!options[i].in_file)
  563. continue;
  564. fprintf(f, "%s = ", options[i].l_name);
  565. switch (options[i].type) {
  566. case OTYPE_S:
  567. // TODO toml escaping
  568. fprintf(f, "\"%s\"\n", options[i].value.s);
  569. break;
  570. case OTYPE_HU:
  571. fprintf(f, "%hu\n", options[i].value.hu);
  572. break;
  573. case OTYPE_B:
  574. fprintf(f, "%s\n", options[i].value.b ? "true" : "false");
  575. break;
  576. default:
  577. break;
  578. }
  579. }
  580. config_dump();
  581. end:
  582. if (f)
  583. fclose(f);
  584. return err;
  585. }
  586. #endif
  587. enum error config_get_inter(int cenf_count, const struct conf_entry cenf[cenf_count],
  588. const char *key, void **out)
  589. {
  590. enum error err = ERR_OPT_NOTFOUND;
  591. for (int i = 0; i < cenf_count; i++) {
  592. const struct conf_entry *cc = &cenf[i];
  593. if (strcmp(cc->l_name, key) == 0) {
  594. if (cc->value_is_set) {
  595. if (out)
  596. *out = (void**)&cc->value.s;
  597. err = NOERR;
  598. } else {
  599. err = ERR_OPT_UNSET;
  600. }
  601. break;
  602. }
  603. }
  604. return err;
  605. }
  606. enum error config_get(const char *key, void **out)
  607. {
  608. return config_get_inter(options_count, options, key, out);
  609. }
  610. enum error config_get_subopt(const char *subcomm, const char *key, void **out)
  611. {
  612. for (int i = 0; i < options_count; i++) {
  613. struct conf_entry *cc = &options[i];
  614. if (strcmp(cc->l_name, subcomm) == 0) {
  615. return config_get_inter(cc->subopts_count, cc->subopts, key, out);
  616. }
  617. }
  618. return ERR_OPT_NOTFOUND;
  619. }
  620. const char *config_get_nonopt(int index)
  621. {
  622. if (index >= config_get_nonopt_count())
  623. return NULL;
  624. return opt_argv[optind + index];
  625. }
  626. int config_get_nonopt_count()
  627. {
  628. return opt_argc - optind;
  629. }
  630. void config_dump()
  631. {
  632. for (int i = 0; i < options_count; i++) {
  633. if (options[i].type == OTYPE_ACTION)
  634. continue;
  635. printf("%s: ", options[i].l_name);
  636. if (!options[i].value_is_set) {
  637. printf("[UNSET (>.<)]\n");
  638. continue;
  639. }
  640. switch (options[i].type) {
  641. case OTYPE_S:
  642. printf("%s\n", options[i].value.s);
  643. break;
  644. case OTYPE_HU:
  645. printf("%hu\n", options[i].value.hu);
  646. break;
  647. case OTYPE_B:
  648. printf("%s\n", options[i].value.b ? "True" : "False");
  649. break;
  650. default:
  651. printf("Error :(\n");
  652. break;
  653. }
  654. }
  655. }