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.

603 lignes
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. }