Added distro/arch information in package version page
[ppastats.git] / src / html.c
1 /*
2     Copyright (C) 2011 jeanfi@gmail.com
3
4     This program 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 2 of the License, or
7     (at your option) any later version.
8
9     This program 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 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 <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/time.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26
27 #include <json/json.h>
28
29 #include "html.h"
30 #include "lp.h"
31 #include "lp_ws.h"
32 #include "ppastats.h"
33
34 enum file_copy_error {
35         FILE_COPY_ERROR_OPEN_SRC = 1,
36         FILE_COPY_ERROR_OPEN_DST,
37         FILE_COPY_ERROR_READ,
38         FILE_COPY_ERROR_WRITE,
39         FILE_COPY_ERROR_ALLOC_BUFFER
40 };
41
42 #define HTML_PKG_TEMPLATE \
43 "<html>\n\
44   <head>\n\
45     <link type=\"text/css\"\n\
46           rel=\"stylesheet\"\n\
47           href=\n\
48 \"http://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin\">\n\
49     <link type=\"text/css\" href=\"css/ppastats.css\" rel=\"stylesheet\" />\n\
50     <link type=\"text/css\" href=\"css/jquery.jqplot.min.css\"\n\
51           rel=\"stylesheet\" />\n\
52     <script type=\"text/javascript\" src=\"js/jquery.min.js\"></script>\n\
53     <script type=\"text/javascript\"\n\
54             src=\"js/jquery.jqplot.min.js\"></script>\n\
55     <script type=\"text/javascript\"\n\
56             src=\"js/jqplot.dateAxisRenderer.min.js\"></script>\n\
57     <script type=\"text/javascript\" src=\"js/ppastats.js\"></script>\n\
58     <script>var data = %s;\n\
59             ppastats_pkg();\n\
60     </script>\n\
61   </head>\n\
62   <body>\n\
63     <h1><span id=\"pkg_name\">N/A</span></h1>\n\
64     <p><em>PPA</em>: \n\
65        <a href=\".\">\n\
66            <span id=\"ppa_owner\">N/A</span>/<span id=\"ppa_name\">N/A</span>\n\
67        </a></p>\n\
68     <div id=\"chart\"></div>\n\
69     <div id=\"versions\"><em>Versions:</em></div>\n\
70   </body>\n\
71 </html>"
72
73 #define HTML_VERSION_TEMPLATE \
74 "<html>\n\
75   <head>\n\
76     <link type=\"text/css\"\n\
77           rel=\"stylesheet\"\n\
78           href=\n\
79 \"http://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin\">\n\
80     <link type=\"text/css\" href=\"css/ppastats.css\" rel=\"stylesheet\" />\n\
81     <link type=\"text/css\" href=\"css/jquery.jqplot.min.css\"\n\
82           rel=\"stylesheet\" />\n\
83     <script type=\"text/javascript\" src=\"js/jquery.min.js\"></script>\n\
84     <script type=\"text/javascript\"\n\
85             src=\"js/jquery.jqplot.min.js\"></script>\n\
86     <script type=\"text/javascript\"\n\
87             src=\"js/jqplot.dateAxisRenderer.min.js\"></script>\n\
88     <script type=\"text/javascript\" src=\"js/ppastats.js\"></script>\n\
89     <script>var data = %s;\n\
90             ppastats_ver();\n\
91     </script>\n\
92   </head>\n\
93   <body>\n\
94     <h1><span id=\"pkg_name\">N/A</span></h1>\n\
95     <div id=\"version\"><em>Version:</em></div>\n\
96     <p><em>PPA</em>: \n\
97        <a href=\".\">\n\
98            <span id=\"ppa_owner\">N/A</span>/<span id=\"ppa_name\">N/A</span>\n\
99        </a></p>\n\
100     <div id=\"chart\"></div>\n\
101     <div class=\"distros\">\n\
102        <em>Distros</em>:\n\
103        <ul id=\"distros\"></ul>\n\
104     </div>\n\
105   </body>\n\
106 </html>"
107
108 #define FCOPY_BUF_SZ 4096
109 static int file_copy(FILE * src, FILE * dst)
110 {
111         int ret = 0;
112         char *buf = malloc(FCOPY_BUF_SZ);
113         int n;
114
115         if (!buf)
116                 return FILE_COPY_ERROR_ALLOC_BUFFER;
117
118         while (!ret) {
119                 n = fread(buf, 1, FCOPY_BUF_SZ, src);
120                 if (n) {
121                         if (fwrite(buf, 1, n, dst) != n)
122                                 ret = FILE_COPY_ERROR_WRITE;
123                 } else {
124                         if (!feof(src))
125                                 ret = FILE_COPY_ERROR_READ;
126                         else
127                                 break;
128                 }
129         }
130
131         free(buf);
132
133         return ret;
134 }
135
136 int
137 fcopy(const char *src, const char *dst)
138 {
139         FILE *fsrc, *fdst;
140         int ret = 0;
141
142         if (debug)
143                 printf("DEBUG: copy: %s to %s\n", src, dst);
144
145         fsrc = fopen(src, "r");
146
147         if (fsrc) {
148                 fdst = fopen(dst, "w+");
149
150                 if (fdst) {
151                         ret = file_copy(fsrc, fdst);
152                         fclose(fdst);
153                 } else {
154                         ret = FILE_COPY_ERROR_OPEN_DST;
155                 }
156
157                 fclose(fsrc);
158         } else {
159                 ret = FILE_COPY_ERROR_OPEN_SRC;
160         }
161
162         return ret;
163 }
164
165 static char *get_path(const char *dir, const char *file)
166 {
167         char *path = malloc(strlen(dir)+1+
168                             strlen(file)+
169                             strlen(".html")+
170                             1);
171
172         strcpy(path, dir);
173         strcat(path, "/");
174         strcat(path, file);
175         strcat(path, ".html");
176
177         return path;
178 }
179
180 static struct json_object *date_to_json(struct tm *tm)
181 {
182         json_object *json;
183
184         json = json_object_new_array();
185         json_object_array_add(json, json_object_new_int(tm->tm_year+1900));
186         json_object_array_add(json, json_object_new_int(tm->tm_mon+1));
187         json_object_array_add(json, json_object_new_int(tm->tm_mday));
188
189         return json;
190 }
191
192 static void json_add_ddts(json_object *json,
193                           struct daily_download_total **ddts)
194 {
195         json_object *json_ddt, *json_ddts;
196         struct daily_download_total *ddt;
197
198         json_ddts = json_object_new_array();
199         json_object_object_add(json, "ddts", json_ddts);
200
201         if (!ddts)
202                 return ;
203
204         while (*ddts) {
205                 ddt = *ddts;
206
207                 json_ddt = json_object_new_object();
208                 json_object_object_add(json_ddt,
209                                        "value",
210                                        json_object_new_int(ddt->count));
211                 json_object_object_add(json_ddt,
212                                        "time",
213                                        date_to_json(&ddt->date));
214
215                 json_object_array_add(json_ddts, json_ddt);
216
217                 ddts++;
218         }
219 }
220
221 static char *pkg_to_json(struct ppa_stats *ppa, struct package_stats *pkg)
222 {
223         json_object *json, *json_versions;
224         char *ret;
225         struct version_stats **versions;
226
227         json = json_object_new_object();
228
229         json_object_object_add(json,
230                                "ppa_name", json_object_new_string(ppa->name));
231         json_object_object_add(json,
232                                "ppa_owner",
233                                json_object_new_string(ppa->owner));
234
235         json_object_object_add(json,
236                                "name", json_object_new_string(pkg->name));
237
238         json_versions = json_object_new_array();
239         json_object_object_add(json, "versions", json_versions);
240         versions = pkg->versions;
241         while (*versions) {
242                 json_object_array_add
243                         (json_versions,
244                          json_object_new_string((*versions)->version));
245
246                 versions++;
247         }
248
249         json_add_ddts(json, pkg->daily_download_totals);
250
251         ret = strdup(json_object_to_json_string(json));
252
253         json_object_put(json);
254
255         return ret;
256 }
257
258 static char *version_to_json(struct ppa_stats *ppa,
259                              struct package_stats *pkg,
260                              struct version_stats *ver)
261 {
262         char *ret;
263         struct distro_stats **distros, *distro;
264         json_object *json, *json_distros, *json_distro, *json_archs, *json_arch;
265         struct arch_stats **archs;
266
267         json = json_object_new_object();
268
269         json_object_object_add(json,
270                                "ppa_name", json_object_new_string(ppa->name));
271         json_object_object_add(json,
272                                "ppa_owner",
273                                json_object_new_string(ppa->owner));
274
275         json_object_object_add(json,
276                                "pkg_name", json_object_new_string(pkg->name));
277
278         json_object_object_add(json,
279                                "name", json_object_new_string(ver->version));
280
281         json_add_ddts(json, ver->daily_download_totals);
282
283         distros = ver->distros;
284         json_distros = json_object_new_array();
285         json_object_object_add(json, "distros", json_distros);
286         while (*distros) {
287                 distro = *distros;
288                 json_distro = json_object_new_object();
289
290                 json_object_array_add(json_distros, json_distro);
291
292                 json_object_object_add(json_distro,
293                                        "name",
294                                        json_object_new_string(distro->name));
295
296                 archs = distro->archs;
297                 json_archs = json_object_new_array();
298                 json_object_object_add(json_distro, "archs", json_archs);
299                 while (*archs) {
300                         json_arch = json_object_new_object();
301
302                         json_object_object_add
303                                 (json_arch,
304                                  "name",
305                                  json_object_new_string((*archs)->name));
306
307                         json_object_object_add
308                                 (json_arch,
309                                  "count",
310                                  json_object_new_int((*archs)->download_count));
311
312                         json_object_array_add(json_archs, json_arch);
313                         archs++;
314                 }
315
316                 distros++;
317         }
318
319         ret = strdup(json_object_to_json_string(json));
320
321         json_object_put(json);
322
323         return ret;
324 }
325
326 static void
327 pkg_to_html(struct ppa_stats *ppa, struct package_stats *pkg, const char *dir)
328 {
329         char *path;
330         FILE *f;
331         char *json;
332
333         path = get_path(dir, pkg->name);
334         f = fopen(path, "w");
335
336         if (!f) {
337                 fprintf(stderr, "ERROR: failed to open: %s\n", path);
338                 return ;
339         }
340
341         json = pkg_to_json(ppa, pkg);
342
343         fprintf(f, HTML_PKG_TEMPLATE, json);
344
345         fclose(f);
346
347         free(path);
348         free(json);
349 }
350
351 static void
352 version_to_html(struct ppa_stats *ppa,
353                 struct package_stats *pkg,
354                 struct version_stats *version,
355                 const char *dir)
356 {
357         char *f_name, *path;
358         FILE *f;
359
360         f_name = malloc(strlen(pkg->name)+1+strlen(version->version)+1);
361         sprintf(f_name, "%s_%s", pkg->name, version->version);
362
363         path = get_path(dir, f_name);
364         f = fopen(path, "w");
365
366         if (!f) {
367                 fprintf(stderr, "ERROR: failed to open: %s\n", path);
368                 return ;
369         }
370
371         fprintf(f, HTML_VERSION_TEMPLATE, version_to_json(ppa, pkg, version));
372
373         fclose(f);
374
375         free(path);
376         free(f_name);
377 }
378
379 static void
380 packages_to_html(struct ppa_stats *ppa,
381                  struct package_stats **packages,
382                  const char *dir)
383 {
384         struct package_stats **cur;
385         struct version_stats **versions;
386
387         cur = packages;
388         while (*cur) {
389                 pkg_to_html(ppa, *cur, dir);
390
391                 versions = (*cur)->versions;
392                 while (*versions) {
393                         version_to_html(ppa, *cur, *versions, dir);
394
395                         versions++;
396                 }
397
398                 cur++;
399         }
400 }
401
402 static char *append_path(const char *odir, const char *name)
403 {
404         char *dir;
405
406         dir = malloc(strlen(odir)+1+strlen(name)+1);
407
408         sprintf(dir, "%s/%s", odir, name);
409
410         return dir;
411 }
412
413 void
414 ppa_to_html(const char *owner,
415             const char *ppa,
416             const char *package_status,
417             const char *output_dir)
418 {
419         struct ppa_stats *ppastats;
420         struct daily_download_total **totals;
421         char *path, *f_dst;
422         char *css_dir, *js_dir;
423         int i;
424         static char *www_files[]
425                 = { DEFAULT_WWW_DIR"/jquery.min.js", "js/jquery.min.js",
426                     DEFAULT_WWW_DIR"/ppastats.js", "js/ppastats.js",
427                     DEFAULT_WWW_DIR"/jqplot.dateAxisRenderer.min.js",
428                     "js/jqplot.dateAxisRenderer.min.js",
429                     DEFAULT_WWW_DIR"/jquery.jqplot.min.js",
430                     "js/jquery.jqplot.min.js",
431                     DEFAULT_WWW_DIR"/ppastats.css", "css/ppastats.css",
432                     DEFAULT_WWW_DIR"/jquery.jqplot.min.css",
433                     "css/jquery.jqplot.min.css" };
434
435         css_dir = append_path(output_dir, "css");
436         js_dir = append_path(output_dir, "js");
437
438         mkdir(css_dir, 0777);
439         mkdir(js_dir, 0777);
440
441         for (i = 0; i < 6; i++) {
442                 f_dst = append_path(output_dir, www_files[2*i+1]);
443                 fcopy(www_files[2*i], f_dst);
444
445                 free(f_dst);
446         }
447
448         ppastats = create_ppa_stats(owner, ppa, package_status);
449         totals = ppastats->daily_download_totals;
450
451         path = get_path(output_dir, "ppa");
452
453         packages_to_html(ppastats, ppastats->packages, output_dir);
454
455         ppa_stats_free(ppastats);
456
457         free(path);
458         free(css_dir);
459         free(js_dir);
460 }