A tool for adding anime to your anidb list.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

603 lines
18KB

  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. static int show_help(struct conf_entry *ce);
  22. static int config_parse_file();
  23. static enum error config_required_check();
  24. static int config_set_str(struct conf_entry *ce, char *arg);
  25. static int config_set_port(struct conf_entry *ce, char *arg);
  26. static int config_set_bool(struct conf_entry *ce, char *arg);
  27. //static int config_def_cachedb(struct conf_entry *ce);
  28. //static int config_action_write_config(struct conf_entry *ce);
  29. /* Everything not explicitly defined, is 0 */
  30. /* If an option only has a long name, the short name also has to be
  31. * defined. For example, a number larger than UCHAR_MAX */
  32. static struct conf_entry options[] = {
  33. { .l_name = "help", .s_name = 'h', .has_arg = no_argument,
  34. .action_func = show_help, .in_args = true,
  35. .type = OTYPE_ACTION, .handle_order = 0,
  36. .h_desc = "Display the help and exit", },
  37. /*
  38. { .l_name = "config-dir", .s_name = 'b', .has_arg = required_argument,
  39. .default_func = config_def_config_dir, .set_func = config_set_str,
  40. .in_args = true, .type = OTYPE_S, .handle_order = 0 },
  41. { .l_name = "default-download-dir", .s_name = 'd', .has_arg = required_argument,
  42. .default_func = config_def_default_download_dir, .set_func = config_set_str,
  43. .in_file = true, .in_args = true, .type = OTYPE_S, .handle_order = 1 },
  44. { .l_name = "port", .s_name = 'p', .has_arg = required_argument,
  45. .set_func = config_set_port, .value.hu = 21729, .value_is_set = true,
  46. .in_file = true, .in_args = true, .type = OTYPE_HU, .handle_order = 1 },
  47. { .l_name = "foreground", .s_name = 'f', .has_arg = no_argument,
  48. .set_func = config_set_bool, .value.b = false, .value_is_set = true,
  49. .in_args = true, .type = OTYPE_B, .handle_order = 1 },
  50. { .l_name = "write-config", .s_name = UCHAR_MAX + 1, .has_arg = no_argument,
  51. .action_func = config_action_write_config, .value_is_set = true,
  52. .in_args = true, .type = OTYPE_ACTION, .handle_order = 2 },
  53. { .l_name = "peer-id", .s_name = UCHAR_MAX + 2, .has_arg = required_argument,
  54. .default_func = config_def_peer_id, .type = OTYPE_S, .handle_order = 1 },
  55. */
  56. { .l_name = "username", .s_name = 'u', .has_arg = required_argument,
  57. .set_func = config_set_str, .in_args = true, .in_file = true,
  58. .type = OTYPE_S, .handle_order = 1,
  59. .h_desc = "Sets the username for the login", },
  60. { .l_name = "password", .s_name = 'p', .has_arg = required_argument,
  61. .set_func = config_set_str, .in_args = true, .in_file = true,
  62. .type = OTYPE_S, .handle_order = 1,
  63. .h_desc = "Sets the password for the login", },
  64. { .l_name = "port", .s_name = 'P', .has_arg = required_argument,
  65. .set_func = config_set_port, .in_args = true, .in_file = true,
  66. .type = OTYPE_HU, .handle_order = 1, .value.hu = 29937,
  67. .value_is_set = true,
  68. .h_desc = "Sets port to use for API server communication", },
  69. { .l_name = "api-server", .s_name = UCHAR_MAX + 1, .has_arg = required_argument,
  70. .set_func = config_set_str, .in_args = true, .in_file = true,
  71. .type = OTYPE_S, .handle_order = 1, .value.s = "api.anidb.net:9000",
  72. .value_is_set = true,
  73. .h_desc = "Sets the API server address", },
  74. { .l_name = "api-key", .s_name = 'k', .has_arg = required_argument,
  75. .set_func = config_set_str, .in_args = true, .in_file = true,
  76. .type = OTYPE_S, .handle_order = 1,
  77. .h_desc = "Sets the api key used for encryption", },
  78. { .l_name = "save-session", .s_name = 's', .has_arg = no_argument,
  79. .set_func = config_set_bool, .in_args = true, .in_file = true,
  80. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  81. .h_desc = "not implemented", },
  82. { .l_name = "destroy-session", .s_name = 'S', .has_arg = no_argument,
  83. .set_func = config_set_bool, .in_args = true, .in_file = false,
  84. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  85. .h_desc = "not implemented", },
  86. { .l_name = "watched", .s_name = 'w', .has_arg = no_argument,
  87. .set_func = config_set_bool, .in_args = true,
  88. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  89. .h_desc = "Mark the episode as watched when adding files", },
  90. { .l_name = "link", .s_name = 'l', .has_arg = no_argument,
  91. .set_func = config_set_bool, .in_args = true,
  92. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  93. .h_desc = "Print an ed2k link when running the ed2k command", },
  94. { .l_name = "cachedb", .s_name = 'd', .has_arg = required_argument,
  95. .set_func = config_set_str, .in_args = true, .in_file = true,
  96. .type = OTYPE_S, .handle_order = 1, /*.default_func = config_def_cachedb*/
  97. .h_desc = "Sets the path for the cache database", },
  98. { .l_name = "debug", .s_name = 'D', .has_arg = no_argument,
  99. .set_func = config_set_bool, .in_args = true, .in_file = true,
  100. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  101. .h_desc = "Enable debug output", },
  102. { .l_name = "wdate", .s_name = UCHAR_MAX + 4, .has_arg = required_argument,
  103. .set_func = config_set_str, .in_args = true,
  104. .type = OTYPE_S, .handle_order = 1,
  105. .h_desc = "Set the watched date when adding files", },
  106. /*### cmd ###*/
  107. { .l_name = "server-version", .s_name = UCHAR_MAX + 2,
  108. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  109. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  110. .h_desc = "CMD: Request the server version", },
  111. { .l_name = "version", .s_name = 'v',
  112. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  113. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  114. .h_desc = "CMD: Print the caniadd version", },
  115. { .l_name = "uptime", .s_name = UCHAR_MAX + 3,
  116. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  117. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  118. .h_desc = "CMD: Request the uptime of the api servers", },
  119. { .l_name = "ed2k", .s_name = 'e',
  120. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  121. .type = OTYPE_B, .handle_order = 1,
  122. .h_desc = "CMD: Run an ed2k hash on the file arguments", },
  123. { .l_name = "add", .s_name = 'a',
  124. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  125. .type = OTYPE_B, .handle_order = 1,
  126. .h_desc = "CMD: Add files to your anidb list", },
  127. /* Arguments are either mylist id's, or file sizes and names
  128. * in the format '[watch_date/]<size>/<filename>'. The filename can't contain
  129. * '/' characters. */
  130. { .l_name = "modify", .s_name = 'W',
  131. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  132. .type = OTYPE_B, .handle_order = 1,
  133. .h_desc = "CMD: Modify files in your anidb list", },
  134. /*{ .l_name = "stats", .s_name = UCHAR_MAX + 5,
  135. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  136. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },*/
  137. };
  138. static const size_t options_count = sizeof(options) / sizeof(options[0]);
  139. static const char **opt_argv = NULL;
  140. static int opt_argc = 0;
  141. static void config_build_getopt_args(char out_sopt[options_count * 2 + 1],
  142. struct option out_lopt[options_count + 1])
  143. {
  144. int i_sopt = 0, i_lopt = 0;
  145. for (int i = 0; i < options_count; i++) {
  146. /* Short options */
  147. if (options[i].s_name && options[i].s_name <= UCHAR_MAX) {
  148. out_sopt[i_sopt++] = options[i].s_name;
  149. if (options[i].has_arg == required_argument)
  150. out_sopt[i_sopt++] = ':';
  151. assert(options[i].has_arg == required_argument ||
  152. options[i].has_arg == no_argument);
  153. }
  154. /* Long options */
  155. if (options[i].l_name) {
  156. assert(options[i].s_name);
  157. out_lopt[i_lopt].name = options[i].l_name;
  158. out_lopt[i_lopt].has_arg = options[i].has_arg;
  159. out_lopt[i_lopt].flag = NULL;
  160. out_lopt[i_lopt].val = options[i].s_name;
  161. i_lopt++;
  162. }
  163. }
  164. out_sopt[i_sopt] = '\0';
  165. memset(&out_lopt[i_lopt], 0, sizeof(struct option));
  166. }
  167. static int config_read_args(int argc, char **argv, char sopt[options_count * 2 + 1],
  168. struct option lopt[options_count + 1], int level)
  169. {
  170. int optc, err = NOERR;
  171. optind = 1;
  172. while ((optc = getopt_long(argc, argv, sopt,
  173. lopt, NULL)) >= 0) {
  174. bool handled = false;
  175. for (int i = 0; i < options_count; i++) {
  176. if (options[i].handle_order != level) {
  177. /* Lie a lil :x */
  178. handled = true;
  179. continue;
  180. }
  181. if (optc == options[i].s_name) {
  182. if (options[i].type != OTYPE_ACTION)
  183. err = options[i].set_func(&options[i], optarg);
  184. else
  185. err = options[i].action_func(&options[i]);
  186. if (err != NOERR)
  187. goto end;
  188. options[i].value_is_set = true;
  189. handled = true;
  190. break;
  191. }
  192. }
  193. if (handled)
  194. continue;
  195. if (optc == '?') {
  196. err = ERR_OPT_FAILED;
  197. goto end;
  198. } else {
  199. fprintf(stderr, "Unhandled option? '%c'\n", optc);
  200. err = ERR_OPT_UNHANDLED;
  201. goto end;
  202. }
  203. }
  204. end:
  205. return err;
  206. }
  207. static enum error config_required_check()
  208. {
  209. enum error err = NOERR;
  210. for (int i = 0; i < options_count; i++) {
  211. if (options[i].required && !options[i].value_is_set) {
  212. printf("Argument %s is required!\n", options[i].l_name);
  213. err = ERR_OPT_REQUIRED;
  214. }
  215. }
  216. return err;
  217. }
  218. enum error config_parse(int argc, char **argv)
  219. {
  220. enum error err = NOERR;
  221. char sopt[options_count * 2 + 1];
  222. struct option lopt[options_count + 1];
  223. opt_argv = (const char**)argv;
  224. opt_argc = argc;
  225. config_build_getopt_args(sopt, lopt);
  226. err = config_read_args(argc, argv, sopt, lopt, 0);
  227. if (err != NOERR)
  228. goto end;
  229. err = config_parse_file();
  230. if (err != NOERR)
  231. goto end;
  232. err = config_read_args(argc, argv, sopt, lopt, 1);
  233. if (err != NOERR)
  234. goto end;
  235. /* Set defaults for those, that didn't got set above */
  236. for (int i = 0; i < options_count; i++) {
  237. if (!options[i].value_is_set && options[i].type != OTYPE_ACTION &&
  238. options[i].default_func) {
  239. err = options[i].default_func(&options[i]);
  240. if (err != NOERR)
  241. goto end;
  242. options[i].value_is_set = true;
  243. }
  244. }
  245. err = config_read_args(argc, argv, sopt, lopt, 2);
  246. if (err != NOERR)
  247. goto end;
  248. err = config_required_check();
  249. end:
  250. if (err != NOERR)
  251. config_free();
  252. return err;
  253. }
  254. #if 0
  255. static int config_def_config_dir(struct conf_entry *ce)
  256. {
  257. char *dir;
  258. int len;
  259. const char *format = "%s/.config/" CONFIG_DIR_NAME;
  260. const char *home_env = getenv("HOME");
  261. if (!home_env) {
  262. /* Fix this at a later date with getuid and getpw */
  263. fprintf(stderr, "HOME environment variable not found!\n");
  264. return ERR_NOTFOUND;
  265. }
  266. len = snprintf(NULL, 0, format, home_env);
  267. if (len == -1) {
  268. int err = errno;
  269. fprintf(stderr, "Failed to call funky snpintf: %s\n", strerror(err));
  270. return err;
  271. }
  272. dir = malloc(len + 1);
  273. sprintf(dir, format, home_env);
  274. ce->value.s = dir;
  275. ce->value_is_dyn = true;
  276. return NOERR;
  277. }
  278. #endif
  279. #if 0
  280. static int config_def_cachedb(struct conf_entry *ce)
  281. {
  282. bool dh_free = false;
  283. const char *data_home = getenv("XDG_DATA_HOME");
  284. if (!data_home) {
  285. const char *home = util_get_home();
  286. if (!home)
  287. return ERR_OPT_FAILED;
  288. sprintf(NULL, "%s/.local/share", home);
  289. }
  290. return NOERR;
  291. }
  292. #endif
  293. static int config_set_str(struct conf_entry *ce, char *arg)
  294. {
  295. // TODO use realpath(3), when necessary
  296. ce->value.s = arg;
  297. return NOERR;
  298. }
  299. static int config_set_port(struct conf_entry *ce, char *arg)
  300. {
  301. long portval = strtol(arg, NULL, 10);
  302. /* A zero return will be invalid no matter if strtol succeeded or not */
  303. if (portval > UINT16_MAX || portval <= 0) {
  304. fprintf(stderr, "Invalid port value '%s'\n", arg);
  305. return ERR_OPT_INVVAL;
  306. }
  307. ce->value.hu = (uint16_t)portval;
  308. return NOERR;
  309. }
  310. static int config_set_bool(struct conf_entry *ce, char *arg)
  311. {
  312. ce->value.b = true;
  313. return NOERR;
  314. }
  315. static int show_help(struct conf_entry *ce)
  316. {
  317. printf(
  318. "Usage: caniadd [OPTION]...\n"
  319. "Caniadd will add files to an AniDB list, and possibly more.\n"
  320. "\n"
  321. "OPTIONS:\n"
  322. );
  323. for (size_t i = 0; i < options_count; i++) {
  324. int printed = 0, pad;
  325. printed += printf(" ");
  326. if (options[i].l_name)
  327. printed += printf("--%s", options[i].l_name);
  328. if (options[i].s_name < UCHAR_MAX)
  329. printed += printf(", -%c", options[i].s_name);
  330. if (options[i].has_arg != no_argument)
  331. printed += printf(" arg");
  332. pad = 25 - printed;
  333. if (pad <= 0)
  334. pad = 1;
  335. printf("%*s%s", pad, "", options[i].h_desc);
  336. if (options[i].value_is_set) {
  337. printf(" Def: ");
  338. if (options[i].type == OTYPE_S)
  339. printed += printf("%s", options[i].value.s);
  340. else if (options[i].type == OTYPE_HU)
  341. printed += printf("%hu", options[i].value.hu);
  342. else if (options[i].type == OTYPE_B)
  343. printed += printf("%s", options[i].value.b ? "true" : "false");
  344. }
  345. printf("\n");
  346. }
  347. return ERR_OPT_EXIT;
  348. }
  349. static int config_parse_file()
  350. {
  351. // TODO implement this
  352. #if 0
  353. assert(conf.config_file_path);
  354. FILE *f = fopen(conf.config_file_path, "rb");
  355. char errbuf[200];
  356. toml_table_t *tml = toml_parse_file(f, errbuf, sizeof(errbuf));
  357. fclose(f);
  358. if (!tml) {
  359. fprintf(stderr, "Failed to parse config toml: %s\n", errbuf);
  360. return ERR_TOML_PARSE_ERROR;
  361. }
  362. toml_datum_t port = toml_int_in(tml, "port");
  363. if (port.ok)
  364. conf.port = (uint16_t)port.u.i;
  365. else
  366. fprintf(stderr, "Failed to parse port from config toml: %s\n", errbuf);
  367. toml_datum_t dldir = toml_string_in(tml, "default_download_dir");
  368. if (dldir.ok) {
  369. conf.default_download_dir = dldir.u.s;
  370. printf("%s\n", dldir.u.s);
  371. conf_dyn.default_download_dir = dldir.u.s;
  372. conf_dyn.default_download_dir = true;
  373. /* TODO is this always malloced?? if yes, remve dyn check */
  374. } else {
  375. fprintf(stderr, "Failed to parse download dir from config toml: %s\n", errbuf);
  376. }
  377. toml_free(tml);
  378. #endif
  379. return NOERR;
  380. }
  381. int config_free()
  382. {
  383. for (int i = 0; i < options_count; i++) {
  384. if (options[i].value_is_dyn) {
  385. free(options[i].value.s);
  386. options[i].value.s = NULL;
  387. options[i].value_is_dyn = false;
  388. options[i].value_is_set = false;
  389. }
  390. }
  391. return NOERR;
  392. }
  393. #if 0
  394. static int config_action_write_config(struct conf_entry *ce)
  395. {
  396. /* This is the success return here */
  397. int err = ERR_OPT_EXIT, plen;
  398. const char *config_dir;
  399. FILE *f = NULL;
  400. config_dir = config_get("config-dir");
  401. plen = snprintf(NULL, 0, "%s/%s", config_dir, CONFIG_FILE_NAME);
  402. char path[plen + 1];
  403. snprintf(path, plen + 1, "%s/%s", config_dir, CONFIG_FILE_NAME);
  404. for (int i = 0; i < 2; i++) {
  405. f = fopen(path, "wb");
  406. if (!f) {
  407. int errn = errno;
  408. if (errn == ENOENT && i == 0) {
  409. /* Try to create parent directory */
  410. if (mkdir(config_dir, 0755) == -1) {
  411. err = errno;
  412. fprintf(stderr, "Config mkdir failed: %s\n", strerror(err));
  413. goto end;
  414. }
  415. } else {
  416. err = errn;
  417. fprintf(stderr, "Config fopen failed: %s\n", strerror(err));
  418. goto end;
  419. }
  420. } else {
  421. break;
  422. }
  423. }
  424. for (int i = 0; i < options_count; i++) {
  425. if (!options[i].in_file)
  426. continue;
  427. fprintf(f, "%s = ", options[i].l_name);
  428. switch (options[i].type) {
  429. case OTYPE_S:
  430. // TODO toml escaping
  431. fprintf(f, "\"%s\"\n", options[i].value.s);
  432. break;
  433. case OTYPE_HU:
  434. fprintf(f, "%hu\n", options[i].value.hu);
  435. break;
  436. case OTYPE_B:
  437. fprintf(f, "%s\n", options[i].value.b ? "true" : "false");
  438. break;
  439. default:
  440. break;
  441. }
  442. }
  443. config_dump();
  444. end:
  445. if (f)
  446. fclose(f);
  447. return err;
  448. }
  449. #endif
  450. enum error config_get(const char *key, void **out)
  451. {
  452. enum error err = ERR_OPT_NOTFOUND;
  453. for (int i = 0; i < options_count; i++) {
  454. struct conf_entry *cc = &options[i];
  455. if (strcmp(cc->l_name, key) == 0) {
  456. if (cc->value_is_set) {
  457. if (out)
  458. *out = &cc->value.s;
  459. err = NOERR;
  460. } else {
  461. err = ERR_OPT_UNSET;
  462. }
  463. break;
  464. }
  465. }
  466. return err;
  467. }
  468. const char *config_get_nonopt(int index)
  469. {
  470. if (index >= config_get_nonopt_count())
  471. return NULL;
  472. return opt_argv[optind + index];
  473. }
  474. int config_get_nonopt_count()
  475. {
  476. return opt_argc - optind;
  477. }
  478. void config_dump()
  479. {
  480. for (int i = 0; i < options_count; i++) {
  481. if (options[i].type == OTYPE_ACTION)
  482. continue;
  483. printf("%s: ", options[i].l_name);
  484. if (!options[i].value_is_set) {
  485. printf("[UNSET (>.<)]\n");
  486. continue;
  487. }
  488. switch (options[i].type) {
  489. case OTYPE_S:
  490. printf("%s\n", options[i].value.s);
  491. break;
  492. case OTYPE_HU:
  493. printf("%hu\n", options[i].value.hu);
  494. break;
  495. case OTYPE_B:
  496. printf("%s\n", options[i].value.b ? "True" : "False");
  497. break;
  498. default:
  499. printf("Error :(\n");
  500. break;
  501. }
  502. }
  503. }