Parent Directory | Revision Log
Async gcvote
1 | /* |
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 | |
18 | #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 | /* 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 | |
50 | gcvote_t *votes = NULL, **cur = &votes; |
51 | |
52 | for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { |
53 | if (cur_node->type == XML_ELEMENT_NODE) { |
54 | |
55 | if(strcasecmp((char*)cur_node->name, "vote") == 0) { |
56 | *cur = g_new0(gcvote_t, 1); |
57 | (*cur)->quality = GCVOTE_NONE; |
58 | |
59 | /* unhandled attributes: */ |
60 | /* userName, voteAvg, voteUser, waypoint, vote1-5 */ |
61 | |
62 | /* 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 | |
69 | 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 | |
75 | 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 | |
81 | 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 | } |
87 | } |
88 | return votes; |
89 | } |
90 | |
91 | /* parse root element */ |
92 | static gcvote_t *parse_root(xmlDocPtr doc, xmlNode *a_node) { |
93 | xmlNode *cur_node = NULL; |
94 | gcvote_t *votes = NULL; |
95 | |
96 | for (cur_node = a_node; cur_node; cur_node = cur_node->next) { |
97 | if (cur_node->type == XML_ELEMENT_NODE) { |
98 | |
99 | 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 | } else |
105 | printf("found unhandled %s\n", cur_node->name); |
106 | } |
107 | } |
108 | return votes; |
109 | } |
110 | |
111 | static gcvote_t *parse_doc(xmlDocPtr doc) { |
112 | /* Get the root element node */ |
113 | xmlNode *root_element = xmlDocGetRootElement(doc); |
114 | gcvote_t *votes = parse_root(doc, root_element); |
115 | |
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 | |
125 | return votes; |
126 | } |
127 | |
128 | 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 | |
133 | printf("gcvote: using proxy %s:%d\n", proxy->host, proxy->port); |
134 | |
135 | curl_easy_setopt(curl, CURLOPT_PROXY, proxy->host); |
136 | curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy->port); |
137 | |
138 | if(proxy->use_authentication) { |
139 | printf("gcvote: use auth for user %s\n", proxy->authentication_user); |
140 | |
141 | char *cred = g_strdup_printf("%s:%s", |
142 | proxy->authentication_user, |
143 | proxy->authentication_password); |
144 | |
145 | curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, cred); |
146 | g_free(cred); |
147 | } |
148 | } |
149 | } |
150 | |
151 | 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 | curl_formadd(&formpost, &lastptr, |
250 | CURLFORM_COPYNAME, "version", |
251 | CURLFORM_COPYCONTENTS, APP VERSION, |
252 | CURLFORM_END); |
253 | |
254 | curl_formadd(&formpost, &lastptr, |
255 | 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 | |
265 | /* 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 | /* play nice and report some user agent */ |
274 | curl_easy_setopt(curl, CURLOPT_USERAGENT, APP " " VERSION); |
275 | |
276 | /* download to memory */ |
277 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &request->mem); |
278 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_write); |
279 | |
280 | /* what URL that receives this POST */ |
281 | curl_easy_setopt(curl, CURLOPT_URL, |
282 | "http://dosensuche.de/GCVote/getVotes.php"); |
283 | curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); |
284 | |
285 | 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 | /* always cleanup */ |
292 | curl_easy_cleanup(curl); |
293 | |
294 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &request->response); |
295 | |
296 | /* then cleanup the formpost chain */ |
297 | curl_formfree(formpost); |
298 | |
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 | |
310 | 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 | |
329 | 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 | } |
337 | |
338 | return request; |
339 | } |
340 |