merged plib
[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_ISO8601_time(&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 /*
313   Convert ddts older than 4 weeks to the same JSON representation than
314   the LP one.  Newer ddts are not stored in the cache because the data
315   may change during following days. It avoids to miss downloads which
316   are not yet taken in consideration by LP.
317  */
318 static json_object *ddts_to_json_for_cache(struct daily_download_total **ddts)
319 {
320         json_object *j_ddts;
321         struct daily_download_total *ddt;
322         char *date;
323         struct timeval *tv;
324         time_t t;
325         double d;
326
327         j_ddts = json_object_new_object();
328
329         tv = malloc(sizeof(struct timeval));
330         gettimeofday(tv, NULL);
331
332         while (ddts && *ddts) {
333                 ddt = *ddts;
334
335                 t = mktime(&(ddt->date));
336
337                 d = difftime(tv->tv_sec, t);
338
339                 if (d > 4 * 7 * 24 * 60 * 60) { /* older than 4 weeks */
340                         date = tm_to_ISO8601_date(&ddt->date);
341                         json_object_object_add(j_ddts,
342                                                date,
343                                                json_object_new_int(ddt->count));
344                         free(date);
345                 }
346
347                 ddts++;
348         }
349
350         free(tv);
351
352         return j_ddts;
353 }
354
355 char *create_ddts_query(const char *binary_url, time_t st)
356 {
357         char *q;
358         char *sdate;
359
360         if (st) {
361                 sdate = time_to_ISO8601_date(&st);
362
363                 q = malloc(strlen(binary_url)
364                            + strlen(QUERY_GET_DAILY_DOWNLOAD_TOTALS)
365                            + strlen("&start_date=YYYY-MM-DD")
366                            + 1);
367                 strcpy(q, binary_url);
368                 strcat(q, QUERY_GET_DAILY_DOWNLOAD_TOTALS);
369                 strcat(q, "&start_date=");
370                 strcat(q, sdate);
371
372                 free(sdate);
373         } else {
374                 q = malloc(strlen(binary_url)
375                            + strlen(QUERY_GET_DAILY_DOWNLOAD_TOTALS)
376                            + 1);
377                 strcpy(q, binary_url);
378                 strcat(q, QUERY_GET_DAILY_DOWNLOAD_TOTALS);
379         }
380
381         return q;
382 }
383
384 struct daily_download_total **get_daily_download_totals(const char *binary_url)
385 {
386         char *url, *key, *content;
387         json_object *j_ddts, *json;
388         struct daily_download_total **retrieved_ddts = NULL;
389         struct daily_download_total **cached_ddts;
390         struct daily_download_total **ddts;
391         time_t last_t;
392
393         key = get_ddts_list_cache_key(binary_url);
394
395         content = fcache_get(key);
396         if (content)
397                 json = json_tokener_parse(content);
398         else
399                 json = NULL;
400
401         if (json) {
402                 cached_ddts = json_object_to_daily_download_totals(json);
403                 last_t = ddts_get_last_date(cached_ddts);
404         } else {
405                 last_t = 0;
406                 cached_ddts = NULL;
407         }
408
409         url = create_ddts_query(binary_url, last_t);
410
411         json = get_json_object(url);
412
413         free(url);
414
415         if (json) {
416                 retrieved_ddts = json_object_to_daily_download_totals(json);
417
418                 ddts = ddts_merge(cached_ddts, retrieved_ddts);
419
420                 json_object_put(json);
421                 j_ddts = ddts_to_json_for_cache(ddts);
422                 fcache_put(key, json_object_get_string(j_ddts));
423                 json_object_put(j_ddts);
424         } else {
425                 ddts = NULL;
426         }
427
428         free(key);
429         free(cached_ddts);
430         free(retrieved_ddts);
431
432         return ddts;
433 }
434