anki.c (7087B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 #include <cii/assert.h> 6 #include <cii/except.h> 7 #include <cii/mem.h> 8 #include <curl/curl.h> 9 #include <json-c/json.h> 10 11 #include "anki.h" 12 #include "display.h" 13 14 #define Move_T card_T 15 16 struct card_T { 17 char *id; 18 char *name; 19 char *game; 20 char *fen; 21 char *pgn; 22 char *player; 23 char *start; 24 }; 25 26 Move_T card; 27 28 #define ANKI_URL "127.0.0.1:8765" 29 #define ANKI_CURRENT_CARD "{\"version\": 6, \"action\": \"guiCurrentCard\"}" 30 #define ANKI_EXIT "{\"version\": 6, \"action\": \"guiExitAnki\"}" 31 #define ANKI_INIT "{\"version\": 6, \"action\": \"requestPermission\"}" 32 #define ANKI_SYNC "{\"version\": 6, \"action\": \"sync\"}" 33 34 #define ANKI_PROFILES "{\"version\": 6, \"action\": \"getProfiles\"}" 35 #define ANKI_DECKS "{\"version\": 6, \"action\": \"deckNames\"}" 36 37 #define ANKI_GRADE_FAIL \ 38 "{\"version\": 6, \"action\": \"multi\", \"params\": { \"actions\": [ " \ 39 "{\"action\": \"guiShowAnswer\"}, { \"action\": \"guiAnswerCard\", " \ 40 "\"params\": {\"ease\": 1}}]}}" 41 #define ANKI_GRADE_PASS \ 42 "{\"version\": 6, \"action\": \"multi\", \"params\": { \"actions\": [ " \ 43 "{\"action\": \"guiShowAnswer\"}, { \"action\": \"guiAnswerCard\", " \ 44 "\"params\": {\"ease\": 3}}]}}" 45 46 #define JSON_PRINT_FLAGS JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY 47 #define JSON_PRINT(x) \ 48 fprintf(stderr, "%s\n---\n", \ 49 json_object_to_json_string_ext((x), JSON_PRINT_FLAGS)); 50 #define JSON_STRING_ASSERT(x, value) \ 51 strstr(json_object_get_string((x)), (value)) != NULL 52 53 CURL *curl; 54 CURLcode res; 55 56 struct json_object *result; 57 size_t got_data(void *buffer, size_t itemsize, size_t nitems, void *ignore); 58 59 const Except_T ANKIE_CONNECT = {"Anki: Couldn't connect to the server"}, 60 ANKIE_CURL = {"Anki: Couldn't initialize libcurl"}, 61 ANKIE_DECK = {"Anki: Invalid deck"}, 62 ANKIE_DECK_SELECT = {"Anki: Invalid deck selection"}, 63 ANKIE_GRADE = {"Anki: Unable to grade current card"}, 64 ANKIE_OK = {"everything is ok"}, 65 ANKIE_PROFILE = {"Anki: Invalid profile"}, 66 ANKIE_PROFILE_SELECT = {"Anki: Invalid profile selection"}, 67 ANKIE_SUSPEND = {"Anki: Unable to suspend current card"}; 68 69 void curl_do(char *action) { 70 curl_easy_setopt(curl, CURLOPT_URL, ANKI_URL); 71 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, action); 72 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, got_data); 73 74 res = curl_easy_perform(curl); 75 76 switch (res) { 77 case CURLE_OK: 78 break; 79 case CURLE_COULDNT_CONNECT: 80 RAISE(ANKIE_CONNECT); 81 default: 82 RAISE(ANKIE_CONNECT); 83 } 84 85 /* JSON_PRINT(result); */ 86 } 87 88 size_t got_data(void *buffer, size_t itemsize, size_t nitems, void *ignore) { 89 (void)ignore; 90 91 size_t bytes = itemsize * nitems; 92 free(result); 93 struct json_object *parsed_json = json_tokener_parse(buffer); 94 json_object_object_get_ex(parsed_json, "result", &result); 95 return bytes; 96 } 97 98 void anki_connect(void) { 99 struct json_object *permission; 100 curl_do(ANKI_INIT); 101 json_object_object_get_ex(result, "permission", &permission); 102 103 if (!(JSON_STRING_ASSERT(permission, "granted"))) 104 RAISE(ANKIE_CONNECT); 105 } 106 107 void card_dtor(Move_T self) { 108 FREE(self->fen); 109 FREE(self->game); 110 FREE(self->id); 111 FREE(self->name); 112 FREE(self->pgn); 113 FREE(self->player); 114 FREE(self->start); 115 } 116 117 void card_set_field(Move_T self, char *key, const char *name) { 118 char **field; 119 120 if (strcmp(key, "id") == 0) 121 field = &self->id; 122 else if (strcmp(key, "name") == 0) 123 field = &self->name; 124 else if (strcmp(key, "FEN") == 0) 125 field = &self->fen; 126 else if (strcmp(key, "PGN") == 0) 127 field = &self->pgn; 128 else if (strcmp(key, "player") == 0) 129 field = &self->player; 130 else if (strcmp(key, "game") == 0) 131 field = &self->game; 132 else if (strcmp(key, "start") == 0) 133 field = &self->start; 134 else 135 return; 136 137 *field = ALLOC(strlen(name) * sizeof(char) + 1); 138 strcpy(*field, name); 139 } 140 141 Move_T anki_current_card(void) { 142 struct json_object *fields, *card_id; 143 /* FREE(card); */ 144 NEW(card); 145 146 curl_do(ANKI_CURRENT_CARD); 147 if (result == NULL) 148 return NULL; 149 150 json_object_object_get_ex(result, "fields", &fields); 151 json_object_object_get_ex(result, "cardId", &card_id); 152 153 json_object_object_foreach(fields, key, val_t) { 154 struct json_object *value; 155 json_object_object_get_ex(val_t, "value", &value); 156 card_set_field(card, key, json_object_get_string(value)); 157 } 158 159 card_set_field(card, "id", json_object_get_string(card_id)); 160 161 if (!(card->name && card->pgn)) { 162 printf("Card info incomplete. Suspending it...\n"); 163 anki_suspend(card); 164 return 0; 165 } 166 167 return card; 168 } 169 170 void anki_start(void) { 171 if (!(curl = curl_easy_init())) 172 RAISE(ANKIE_CURL); 173 anki_connect(); 174 } 175 176 const char **anki_list(size_t *size) { 177 size_t len = json_object_array_length(result), i; 178 const char **list; 179 180 list = ALLOC(len * sizeof(const char *)); 181 for (i = 0; i < len; i++) 182 list[i] = json_object_get_string(json_object_array_get_idx(result, i)); 183 184 *size = len; 185 return list; 186 } 187 188 const char **anki_get_profiles(size_t *size) { 189 curl_do(ANKI_PROFILES); 190 return anki_list(size); 191 } 192 193 void anki_load_profile(char *name) { 194 static char request[100]; 195 sprintf(request, 196 "{\"version\": 6, \"action\": \"loadProfile\", \"params\": { " 197 "\"name\": \"%s\" }}", 198 name); 199 curl_do(request); 200 if (!(JSON_STRING_ASSERT(result, "true"))) 201 RAISE(ANKIE_PROFILE); 202 } 203 204 const char **anki_get_decks(size_t *size) { 205 curl_do(ANKI_DECKS); 206 return anki_list(size); 207 } 208 209 void anki_load_deck(char *name) { 210 static char request[1000]; 211 212 if (!name) 213 RAISE(ANKIE_DECK_SELECT); 214 sprintf(request, 215 "{\"version\": 6, \"action\": \"guiDeckReview\", \"params\": { " 216 "\"name\": \"%s\" }}", 217 name); 218 curl_do(request); 219 220 if (!(JSON_STRING_ASSERT(result, "true"))) 221 RAISE(ANKIE_DECK); 222 } 223 224 void anki_grade(int pass) { 225 curl_do((pass) ? ANKI_GRADE_PASS : ANKI_GRADE_FAIL); 226 if (!(JSON_STRING_ASSERT(result, "true"))) 227 RAISE(ANKIE_GRADE); 228 } 229 230 void anki_suspend(Move_T self) { 231 static char request[100]; 232 sprintf(request, 233 "{\"action\": \"suspend\", \"version\": 6, \"params\": { \"cards\": " 234 "[%s] }}", 235 self->id); 236 curl_do(request); 237 if (!(JSON_STRING_ASSERT(result, "true"))) 238 RAISE(ANKIE_SUSPEND); 239 } 240 241 void anki_stop(void) { curl_easy_cleanup(curl); } 242 char *card_name(Move_T self) { return self->name; } 243 char *card_pgn(Move_T self) { return self->pgn; } 244 char *card_start(Move_T self) { return self->start; } 245 char *card_game(Move_T self) { return self->game; } 246 char *card_player(Move_T self) { return self->player; } 247 char *card_fen(Move_T self) { return self->fen; } 248 char *card_id(Move_T self) { return self->id; }