0dce1402287d8fd5d847565801292c8ea3b13dd1
[ppastats.git] / src / lp_ws.c
1 /*
2  * Copyright (C) 2011-2014 jeanfi@gmail.com
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301 USA
18  */
19
20 #include <libintl.h>
21 #define _(String) gettext(String)
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/time.h>
27 #include <time.h>
28
29 #include <json.h>
30
31 #include "cache.h"
32 #include "fcache.h"
33 #include "http.h"
34 #include "list.h"
35 #include "lp_ws.h"
36 #include "lp_json.h"
37 #include <plog.h>
38 #include "ppastats.h"
39 #include <ptime.h>
40
41 /** Default ws.size value for the getPublishedBinaries request. */
42 static const int DEFAULT_WS_SIZE = 150;
43
44 static const char *QUERY_GET_DOWNLOAD_COUNT = "?ws.op=getDownloadCount";
45 static const char *
46 QUERY_GET_DAILY_DOWNLOAD_TOTALS = "?ws.op=getDailyDownloadTotals";
47
48 static json_object *get_json_object(const char *url)
49 {
50         json_object *obj = NULL;
51         char *body;
52
53         body = get_url_content(url, 0);
54
55         if (body) {
56                 obj = json_tokener_parse(body);
57
58                 free(body);
59
60                 return obj;
61         }
62
63         return NULL;
64 }
65
66 static char *get_bpph_list_cache_key(const char *archive_url)
67 {
68         char *key;
69
70         key = malloc(strlen(archive_url + 7) + strlen("/bpph") + 1);
71         sprintf(key, "%s/bpph", archive_url + 7);
72
73         return key;
74 }
75
76 static char *get_ddts_list_cache_key(const char *url)
77 {
78         char *key;
79
80         key = malloc(strlen(url + 7) + strlen("/ddts") + 1);
81         sprintf(key, "%s/ddts", url + 7);
82
83         return key;
84 }
85
86 static struct bpph **get_bpph_list_from_cache(const char *key)
87 {
88         char *content;
89         struct bpph **list;
90         json_object *json;
91
92         content = fcache_get(key);
93         if (!content)
94                 return NULL;
95
96         json = json_tokener_parse(content);
97         if (!json)
98                 return NULL;
99
100         list = json_object_to_bpph_list(json);
101
102         json_object_put(json);
103         free(content);
104
105         return list;
106 }
107
108 static char *get_last_creation_date(struct bpph **list)
109 {
110         time_t last, t;
111         struct bpph **cur;
112
113         last = 0;
114
115         if (list)
116                 for (cur = list; *cur; cur++) {
117                         t = (*cur)->date_created;
118                         if (t > last)
119                                 last = t;
120                 }
121
122         if (last)
123                 return time_to_str(&last);
124         else
125                 return NULL;
126 }
127
128 /*
129  * 'archive_url': LP URL of the archive.
130  * 'size': size of the reply array. Between 1-300, else default value is used.
131  */
132 static char *create_query_get_bpph(const char *archive_url,
133                                    const char *status,
134                                    int size)
135 {
136         static const char *default_opt = "?ws.op=getPublishedBinaries&ws.size=";
137         static const char *status_opt = "&status=";
138         char *url;
139         size_t n;
140
141         if (size < 1 || size > 300)
142                 size = DEFAULT_WS_SIZE;
143
144         n = strlen(archive_url) + strlen(default_opt) + 3 + 1;
145
146         if (status)
147                 n += strlen(status_opt) + strlen(status);
148
149         url = malloc(n);
150         sprintf(url, "%s%s%d", archive_url, default_opt, size);
151
152         if (status) {
153                 strcat(url, status_opt);
154                 strcat(url, status);
155         }
156
157         return url;
158 }
159
160 struct bpph **get_bpph_list(const char *archive_url,
161                             const char *pkg_status,
162                             int ws_size)
163 {
164         char *url, *key, *tmp;
165         struct bpph **result;
166         struct json_object *o, *bpph_json, *o_next;
167         char *date;
168         int ok;
169
170         url = create_query_get_bpph(archive_url, pkg_status, ws_size);
171
172         key = get_bpph_list_cache_key(archive_url);
173
174         result = get_bpph_list_from_cache(key);
175
176         if (result) {
177                 date = get_last_creation_date(result);
178
179                 if (date) {
180                         tmp = malloc(strlen(url)
181                                      + strlen("&created_since_date=")
182                                      + strlen(date)+1);
183                         strcpy(tmp, url);
184                         strcat(tmp, "&created_since_date=");
185                         strcat(tmp, date);
186
187                         free(url);
188                         url = tmp;
189
190                         free(date);
191                 }
192         }
193
194         ok = 1;
195         while (url) {
196                 o = get_json_object(url);
197                 free(url);
198                 url = NULL;
199
200                 if (!o) {
201                         ok = 0;
202                         break;
203                 }
204
205                 result = bpph_list_append_list(result,
206                                                json_object_to_bpph_list(o));
207
208                 o_next = json_object_object_get(o, "next_collection_link");
209
210                 if (o_next)
211                         url = strdup(json_object_get_string(o_next));
212
213                 json_object_put(o);
214         }
215
216         if (ok) {
217                 bpph_json = bpph_list_to_json(result);
218                 fcache_put(key, json_object_to_json_string(bpph_json));
219                 json_object_put(bpph_json);
220         }
221
222         free(key);
223
224         return result;
225 }
226
227 int get_download_count(const char *archive_url)
228 {
229         int n = strlen(archive_url) + strlen(QUERY_GET_DOWNLOAD_COUNT) + 1;
230         char *url = malloc(n);
231         int result;
232         json_object *obj;
233
234         strcpy(url, archive_url);
235         strcat(url, QUERY_GET_DOWNLOAD_COUNT);
236
237         obj = get_json_object(url);
238         free(url);
239
240         if (!obj)
241                 return -1;
242
243         result = json_object_get_int(obj);
244
245         json_object_put(obj);
246
247         return result;
248 }
249
250 const struct distro_arch_series *get_distro_arch_series(const char *url)
251 {
252         json_object *obj;
253         const struct distro_arch_series *distro;
254         char *content;
255
256         distro = cache_get(url);
257         if (distro)
258                 return (struct distro_arch_series *)distro;
259
260         content = get_url_content(url, 1);
261
262         if (!content)
263                 return NULL;
264
265         obj = json_tokener_parse(content);
266
267         free(content);
268
269         if (!obj)
270                 return NULL;
271
272         distro = json_object_to_distro_arch_series(obj);
273
274         json_object_put(obj);
275
276         cache_put(url, distro, (void (*)(void *))&distro_arch_series_free);
277
278         return distro;
279 }
280
281 const struct distro_series *get_distro_series(const char *url)
282 {
283         json_object *obj;
284         const struct distro_series *distro;
285         char *content;
286
287         distro = cache_get(url);
288         if (distro)
289                 return (struct distro_series *)distro;
290
291         content = get_url_content(url, 1);
292
293         if (!content)
294                 return NULL;
295
296         obj = json_tokener_parse(content);
297
298         free(content);
299
300         if (!obj)
301                 return NULL;
302
303         distro = json_object_to_distro_series(obj);
304
305         json_object_put(obj);
306
307         cache_put(url, distro, (void (*)(void *))&distro_series_free);
308
309         return distro;
310 }
311
312 char *date_to_str(struct tm tm)
313 {
314         char *str;
315
316         str = malloc(4 + 1 + 2 + 1 + 2 + 1);
317
318         strftime(str, 11, "%Y-%m-%d", &tm);
319
320         return str;
321 }
322
323 /*
324   Convert ddts older than 4 weeks to the same JSON representation than
325   the LP one.  Newer ddts are not stored in the cache because the data
326   may change during following days. It avoids to miss downloads which
327   are not yet taken in consideration by LP.
328  */
329 static json_object *ddts_to_json_for_cache(struct daily_download_total **ddts)
330 {
331         json_object *j_ddts;
332         struct daily_download_total *ddt;
333         char *date;
334         struct timeval *tv;
335         time_t t;
336         double d;
337
338         j_ddts = json_object_new_object();
339
340         tv = malloc(sizeof(struct timeval));
341         gettimeofday(tv, NULL);
342
343         while (ddts && *ddts) {
344                 ddt = *ddts;
345
346                 t = mktime(&(ddt->date));
347
348                 d = difftime(tv->tv_sec, t);
349
350                 if (d > 4 * 7 * 24 * 60 * 60) { /* older than 4 weeks */
351                         date = date_to_str(ddt->date);
352                         json_object_object_add(j_ddts,
353                                                date,
354                                                json_object_new_int(ddt->count));
355                         free(date);
356                 }
357
358                 ddts++;
359         }
360
361         free(tv);
362
363         return j_ddts;
364 }
365
366 static char *time_t_to_str(time_t t)
367 {
368         struct tm *tm;
369         char *str;
370
371         tm = gmtime(&t);
372
373         str = date_to_str(*tm);
374
375         return str;
376 }
377
378 char *create_ddts_query(const char *binary_url, time_t st)
379 {
380         char *q;
381         char *sdate;
382
383         if (st) {
384                 sdate = time_t_to_str(st);
385
386                 q = malloc(strlen(binary_url)
387                            + strlen(QUERY_GET_DAILY_DOWNLOAD_TOTALS)
388                            + strlen("&start_date=YYYY-MM-DD")
389                            + 1);
390                 strcpy(q, binary_url);
391                 strcat(q, QUERY_GET_DAILY_DOWNLOAD_TOTALS);
392                 strcat(q, "&start_date=");
393                 strcat(q, sdate);
394
395                 free(sdate);
396         } else {
397                 q = malloc(strlen(binary_url)
398                            + strlen(QUERY_GET_DAILY_DOWNLOAD_TOTALS)
399                            + 1);
400                 strcpy(q, binary_url);
401                 strcat(q, QUERY_GET_DAILY_DOWNLOAD_TOTALS);
402         }
403
404         return q;
405 }
406
407 struct daily_download_total **get_daily_download_totals(const char *binary_url)
408 {
409         char *url, *key, *content;
410         json_object *j_ddts, *json;
411         struct daily_download_total **retrieved_ddts = NULL;
412         struct daily_download_total **cached_ddts;
413         struct daily_download_total **ddts;
414         time_t last_t;
415
416         key = get_ddts_list_cache_key(binary_url);
417
418         content = fcache_get(key);
419         if (content)
420                 json = json_tokener_parse(content);
421         else
422                 json = NULL;
423
424         if (json) {
425                 cached_ddts = json_object_to_daily_download_totals(json);
426                 last_t = ddts_get_last_date(cached_ddts);
427         } else {
428                 last_t = 0;
429                 cached_ddts = NULL;
430         }
431
432         url = create_ddts_query(binary_url, last_t);
433
434         json = get_json_object(url);
435
436         free(url);
437
438         if (json) {
439                 retrieved_ddts = json_object_to_daily_download_totals(json);
440
441                 ddts = ddts_merge(cached_ddts, retrieved_ddts);
442
443                 json_object_put(json);
444                 j_ddts = ddts_to_json_for_cache(ddts);
445                 fcache_put(key, json_object_get_string(j_ddts));
446                 json_object_put(j_ddts);
447         } else {
448                 ddts = NULL;
449         }
450
451         free(key);
452         free(cached_ddts);
453         free(retrieved_ddts);
454
455         return ddts;
456 }
457