Parent Directory | Revision Log
Async gcvote
1 | harbaum | 157 | /* |
2 | * This file is part of GPXView. | ||
3 | * | ||
4 | * GPXView is free software: you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation, either version 3 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * GPXView is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with GPXView. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | harbaum | 152 | |
18 | harbaum | 157 | #include <curl/curl.h> |
19 | #include <curl/types.h> | ||
20 | #include <curl/easy.h> | ||
21 | |||
22 | #include <libxml/parser.h> | ||
23 | #include <libxml/tree.h> | ||
24 | |||
25 | #ifndef LIBXML_TREE_ENABLED | ||
26 | #error "Tree not enabled in libxml" | ||
27 | #endif | ||
28 | |||
29 | #include "gpxview.h" | ||
30 | |||
31 | #define ID_PATTERN "guid=" | ||
32 | |||
33 | static size_t mem_write(void *ptr, size_t size, size_t nmemb, | ||
34 | void *stream) { | ||
35 | curl_mem_t *p = (curl_mem_t*)stream; | ||
36 | |||
37 | p->ptr = g_realloc(p->ptr, p->len + size*nmemb + 1); | ||
38 | if(p->ptr) { | ||
39 | memcpy(p->ptr+p->len, ptr, size*nmemb); | ||
40 | p->len += size*nmemb; | ||
41 | p->ptr[p->len] = 0; | ||
42 | } | ||
43 | return nmemb; | ||
44 | } | ||
45 | |||
46 | harbaum | 158 | /* return list of all votes in this file */ |
47 | static gcvote_t *votes_parse(xmlDocPtr doc, xmlNode *a_node) { | ||
48 | xmlNode *cur_node = NULL; | ||
49 | harbaum | 157 | |
50 | harbaum | 158 | gcvote_t *votes = NULL, **cur = &votes; |
51 | harbaum | 157 | |
52 | harbaum | 158 | for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { |
53 | if (cur_node->type == XML_ELEMENT_NODE) { | ||
54 | harbaum | 157 | |
55 | harbaum | 158 | if(strcasecmp((char*)cur_node->name, "vote") == 0) { |
56 | *cur = g_new0(gcvote_t, 1); | ||
57 | (*cur)->quality = GCVOTE_NONE; | ||
58 | harbaum | 157 | |
59 | harbaum | 158 | /* unhandled attributes: */ |
60 | /* userName, voteAvg, voteUser, waypoint, vote1-5 */ | ||
61 | harbaum | 157 | |
62 | harbaum | 158 | /* parse votes attributes */ |
63 | char *prop_str = (char*)xmlGetProp(cur_node, BAD_CAST "voteMedian"); | ||
64 | if(prop_str) { | ||
65 | (*cur)->quality = atoi(prop_str); | ||
66 | xmlFree(prop_str); | ||
67 | } | ||
68 | harbaum | 157 | |
69 | harbaum | 158 | prop_str = (char*)xmlGetProp(cur_node, BAD_CAST "voteCnt"); |
70 | if(prop_str) { | ||
71 | (*cur)->votes = atoi(prop_str); | ||
72 | xmlFree(prop_str); | ||
73 | } | ||
74 | harbaum | 157 | |
75 | harbaum | 158 | prop_str = (char*)xmlGetProp(cur_node, BAD_CAST "cacheId"); |
76 | if(prop_str) { | ||
77 | (*cur)->id = g_strdup(prop_str); | ||
78 | xmlFree(prop_str); | ||
79 | } | ||
80 | harbaum | 157 | |
81 | harbaum | 158 | cur = &(*cur)->next; |
82 | } else if(strcasecmp((char*)cur_node->name, "errorstring") == 0) { | ||
83 | /* ignore for now ... */ | ||
84 | } else | ||
85 | printf("found unhandled votes/%s\n", cur_node->name); | ||
86 | harbaum | 157 | } |
87 | harbaum | 158 | } |
88 | return votes; | ||
89 | harbaum | 157 | } |
90 | |||
91 | /* parse root element */ | ||
92 | harbaum | 158 | static gcvote_t *parse_root(xmlDocPtr doc, xmlNode *a_node) { |
93 | harbaum | 157 | xmlNode *cur_node = NULL; |
94 | harbaum | 158 | gcvote_t *votes = NULL; |
95 | harbaum | 157 | |
96 | for (cur_node = a_node; cur_node; cur_node = cur_node->next) { | ||
97 | if (cur_node->type == XML_ELEMENT_NODE) { | ||
98 | |||
99 | harbaum | 158 | if(strcasecmp((char*)cur_node->name, "votes") == 0) { |
100 | if(!votes) | ||
101 | votes = votes_parse(doc, cur_node); | ||
102 | else | ||
103 | printf("gcvote: ignoring multiple votes lists\n"); | ||
104 | harbaum | 157 | } else |
105 | printf("found unhandled %s\n", cur_node->name); | ||
106 | } | ||
107 | } | ||
108 | harbaum | 158 | return votes; |
109 | harbaum | 157 | } |
110 | |||
111 | harbaum | 158 | static gcvote_t *parse_doc(xmlDocPtr doc) { |
112 | harbaum | 157 | /* Get the root element node */ |
113 | xmlNode *root_element = xmlDocGetRootElement(doc); | ||
114 | harbaum | 158 | gcvote_t *votes = parse_root(doc, root_element); |
115 | harbaum | 157 | |
116 | /*free the document */ | ||
117 | xmlFreeDoc(doc); | ||
118 | |||
119 | /* | ||
120 | * Free the global variables that may | ||
121 | * have been allocated by the parser. | ||
122 | */ | ||
123 | xmlCleanupParser(); | ||
124 | harbaum | 158 | |
125 | return votes; | ||
126 | harbaum | 157 | } |
127 | |||
128 | harbaum | 158 | static void curl_set_proxy(CURL *curl, proxy_t *proxy) { |
129 | if(proxy) { | ||
130 | if(proxy->ignore_hosts) | ||
131 | printf("WARNING: Proxy \"ignore_hosts\" unsupported!\n"); | ||
132 | harbaum | 157 | |
133 | harbaum | 158 | printf("gcvote: using proxy %s:%d\n", proxy->host, proxy->port); |
134 | harbaum | 157 | |
135 | harbaum | 158 | curl_easy_setopt(curl, CURLOPT_PROXY, proxy->host); |
136 | curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy->port); | ||
137 | harbaum | 157 | |
138 | harbaum | 158 | if(proxy->use_authentication) { |
139 | printf("gcvote: use auth for user %s\n", proxy->authentication_user); | ||
140 | harbaum | 157 | |
141 | harbaum | 158 | char *cred = g_strdup_printf("%s:%s", |
142 | proxy->authentication_user, | ||
143 | proxy->authentication_password); | ||
144 | harbaum | 157 | |
145 | harbaum | 158 | curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, cred); |
146 | g_free(cred); | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | harbaum | 157 | |
151 | harbaum | 158 | void gcvote_request_free(gcvote_request_t *request) { |
152 | /* decrease refcount and only free structure if no references are left */ | ||
153 | request->refcount--; | ||
154 | if(request->refcount) { | ||
155 | printf("gcvote: still %d references, keeping request\n", | ||
156 | request->refcount); | ||
157 | return; | ||
158 | } | ||
159 | |||
160 | printf("gcvote: no references left, freeing request\n"); | ||
161 | if(request->url) g_free(request->url); | ||
162 | |||
163 | g_free(request); | ||
164 | } | ||
165 | |||
166 | /* gcvote net result handler */ | ||
167 | static gboolean gcvote_result_handler(gpointer data) { | ||
168 | gcvote_request_t *request = (gcvote_request_t*)data; | ||
169 | |||
170 | printf("gcvote: result handler\n"); | ||
171 | |||
172 | /* worker thread has already reduced its refcounter */ | ||
173 | if(request->refcount < 1) { | ||
174 | printf("gcvote: main app isn't listening anymore\n"); | ||
175 | g_free(request->mem.ptr); | ||
176 | return FALSE; | ||
177 | } | ||
178 | |||
179 | if(request->res) { | ||
180 | printf("gcvote: curl failed\n"); | ||
181 | g_free(request->mem.ptr); | ||
182 | request->cb(NULL, request->userdata); | ||
183 | return FALSE; | ||
184 | } | ||
185 | |||
186 | /* parse the reply */ | ||
187 | /* nothing could be parsed, just give up */ | ||
188 | if(!request->mem.ptr || !request->mem.len) { | ||
189 | printf("gcvote: ignoring zero length reply\n"); | ||
190 | g_free(request->mem.ptr); | ||
191 | request->cb(NULL, request->userdata); | ||
192 | return FALSE; | ||
193 | } | ||
194 | |||
195 | /* start XML parser */ | ||
196 | LIBXML_TEST_VERSION; | ||
197 | |||
198 | /* parse the file and get the DOM */ | ||
199 | xmlDoc *doc = xmlReadMemory(request->mem.ptr, request->mem.len, | ||
200 | NULL, NULL, 0); | ||
201 | g_free(request->mem.ptr); | ||
202 | |||
203 | /* nothing could be parsed, just give up */ | ||
204 | if(!doc) { | ||
205 | request->cb(NULL, request->userdata); | ||
206 | return FALSE; | ||
207 | } | ||
208 | |||
209 | vote_t *vote = NULL; | ||
210 | gcvote_t *votes = parse_doc(doc); | ||
211 | |||
212 | /* search for requested cache in reply and free chain */ | ||
213 | while(votes) { | ||
214 | gcvote_t *next = votes->next; | ||
215 | |||
216 | if(!vote && !strcmp(votes->id, request->id) && (votes->quality > 0)) { | ||
217 | |||
218 | vote = g_new0(vote_t, 1); | ||
219 | vote->quality = votes->quality; | ||
220 | vote->votes = votes->votes; | ||
221 | } | ||
222 | |||
223 | if(votes->id) g_free(votes->id); | ||
224 | g_free(votes); | ||
225 | votes = next; | ||
226 | } | ||
227 | |||
228 | if(vote) { | ||
229 | printf("gcvote: worker found vote %d/%d\n", vote->quality, vote->votes); | ||
230 | request->cb(vote, request->userdata); | ||
231 | } else | ||
232 | printf("gcvote: no vote found\n"); | ||
233 | |||
234 | return FALSE; | ||
235 | } | ||
236 | |||
237 | static void *worker_thread(void *ptr) { | ||
238 | gcvote_request_t *request = (gcvote_request_t*)ptr; | ||
239 | struct curl_httppost *formpost=NULL; | ||
240 | struct curl_httppost *lastptr=NULL; | ||
241 | |||
242 | printf("gcvote: worker thread running\n"); | ||
243 | |||
244 | request->mem.ptr = NULL; | ||
245 | request->mem.len = 0; | ||
246 | |||
247 | printf("gcvote: request to get votes for id %s\n", request->id); | ||
248 | |||
249 | harbaum | 157 | curl_formadd(&formpost, &lastptr, |
250 | harbaum | 158 | CURLFORM_COPYNAME, "version", |
251 | CURLFORM_COPYCONTENTS, APP VERSION, | ||
252 | CURLFORM_END); | ||
253 | harbaum | 157 | |
254 | curl_formadd(&formpost, &lastptr, | ||
255 | harbaum | 158 | CURLFORM_COPYNAME, "cacheIds", |
256 | CURLFORM_COPYCONTENTS, request->id, | ||
257 | CURLFORM_END); | ||
258 | |||
259 | /* try to init curl */ | ||
260 | CURL *curl = curl_easy_init(); | ||
261 | if(!curl) { | ||
262 | curl_formfree(formpost); | ||
263 | gcvote_request_free(request); | ||
264 | harbaum | 157 | |
265 | harbaum | 158 | /* callback anyway, so main loop can also clean up */ |
266 | g_idle_add(gcvote_result_handler, request); | ||
267 | |||
268 | return NULL; | ||
269 | } | ||
270 | |||
271 | curl_set_proxy(curl, request->proxy); | ||
272 | |||
273 | harbaum | 157 | /* play nice and report some user agent */ |
274 | curl_easy_setopt(curl, CURLOPT_USERAGENT, APP " " VERSION); | ||
275 | harbaum | 158 | |
276 | harbaum | 157 | /* download to memory */ |
277 | harbaum | 158 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &request->mem); |
278 | harbaum | 157 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_write); |
279 | |||
280 | /* what URL that receives this POST */ | ||
281 | harbaum | 158 | curl_easy_setopt(curl, CURLOPT_URL, |
282 | "http://dosensuche.de/GCVote/getVotes.php"); | ||
283 | harbaum | 157 | curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); |
284 | |||
285 | harbaum | 158 | request->res = curl_easy_perform(curl); |
286 | printf("gcvote: curl perform returned with %d\n", request->res); | ||
287 | |||
288 | // printf("received %d bytes\n", mem.len); | ||
289 | // printf("data: %s\n", mem.ptr); | ||
290 | |||
291 | harbaum | 157 | /* always cleanup */ |
292 | curl_easy_cleanup(curl); | ||
293 | |||
294 | harbaum | 158 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &request->response); |
295 | |||
296 | harbaum | 157 | /* then cleanup the formpost chain */ |
297 | curl_formfree(formpost); | ||
298 | harbaum | 158 | |
299 | printf("gcvote: worker thread done\n"); | ||
300 | gcvote_request_free(request); | ||
301 | |||
302 | /* cause gtk main loop to handle result */ | ||
303 | g_idle_add(gcvote_result_handler, request); | ||
304 | |||
305 | printf("gcvote: thread terminating\n"); | ||
306 | |||
307 | return NULL; | ||
308 | } | ||
309 | harbaum | 157 | |
310 | harbaum | 158 | gcvote_request_t *gcvote_request(appdata_t *appdata, gcvote_cb cb, |
311 | char *url, gpointer data) { | ||
312 | if(!url) return NULL; | ||
313 | |||
314 | /* ------ prepare request for worker thread --------- */ | ||
315 | gcvote_request_t *request = g_new0(gcvote_request_t, 1); | ||
316 | request->url = g_strdup(url); | ||
317 | request->id = strstr(request->url, ID_PATTERN); | ||
318 | if(!request->id) { | ||
319 | printf("gcvote: unable to find id\n"); | ||
320 | gcvote_request_free(request); | ||
321 | return NULL; | ||
322 | } | ||
323 | |||
324 | request->id += strlen(ID_PATTERN); | ||
325 | request->refcount = 2; // master and worker hold a reference | ||
326 | request->cb = cb; | ||
327 | request->userdata = data; | ||
328 | harbaum | 157 | |
329 | harbaum | 158 | if(!g_thread_create(&worker_thread, request, FALSE, NULL) != 0) { |
330 | g_warning("gcvote: failed to create the worker thread"); | ||
331 | |||
332 | /* free request and return error */ | ||
333 | request->refcount--; /* decrease by one for dead worker thread */ | ||
334 | gcvote_request_free(request); | ||
335 | return NULL; | ||
336 | harbaum | 157 | } |
337 | |||
338 | harbaum | 158 | return request; |
339 | } | ||
340 | harbaum | 157 |