new design for HTML pages
[ppastats.git] / src / html.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 <sys/stat.h>
28 #include <sys/types.h>
29
30 #include <json/json.h>
31
32 #include "html.h"
33 #include "io.h"
34 #include "log.h"
35 #include "lp.h"
36 #include "lp_ws.h"
37 #include "ppastats.h"
38 #include "str.h"
39
40 static const char *footer;
41 static const char *ppa_body;
42 static const char *pkg_body;
43 static const char *pkg_version_body;
44 static const char *header;
45
46 static char *path_new(const char *dir, const char *file, const char *suffixe)
47 {
48         char *path;
49
50         /* [dir]/[file][suffixe] */
51         path = malloc(strlen(dir)+1+
52                       strlen(file)+
53                       (suffixe ? strlen(suffixe) : 0) +
54                       1);
55
56         strcpy(path, dir);
57         strcat(path, "/");
58         strcat(path, file);
59         strcat(path, suffixe);
60
61         return path;
62 }
63
64 static char *get_header(const char *title, const char *script)
65 {
66         const char *path;
67         char *res, *tmp;
68
69         if (!header) {
70                 path = DEFAULT_WWW_DIR"/header.tpl";
71                 header = file_get_content(path);
72
73                 if (!header) {
74                         log_err("Failed to read header template: %s", path);
75
76                         return NULL;
77                 }
78         }
79
80         tmp = strdup(header);
81         res = strrep(tmp, "@SCRIPT@", script);
82
83         if (res != tmp)
84                 free(tmp);
85
86         tmp = res;
87         res = strrep(tmp, "@TITLE@", title);
88
89         if (res != tmp)
90                 free(tmp);
91
92         return res;
93 }
94
95 static const char *get_footer()
96 {
97         const char *path;
98
99         if (!footer) {
100                 path = DEFAULT_WWW_DIR"/footer.tpl";
101                 footer = file_get_content(path);
102
103                 if (!footer)
104                         log_err("Failed to read footer template: %s", path);
105         }
106
107         return footer;
108 }
109
110 static const char *get_pkg_version_body()
111 {
112         const char *path;
113
114         if (!pkg_version_body) {
115                 path = DEFAULT_WWW_DIR"/pkg_version.tpl";
116                 pkg_version_body = file_get_content(path);
117
118                 if (!pkg_version_body)
119                         log_err("Failed to read package version template: %s",
120                                 path);
121         }
122
123         return pkg_version_body;
124 }
125 static const char *get_ppa_body()
126 {
127         const char *path;
128
129         if (!ppa_body) {
130                 path = DEFAULT_WWW_DIR"/ppa.tpl";
131                 ppa_body = file_get_content(path);
132
133                 if (!ppa_body)
134                         log_err("Failed to read PPA template: %s", path);
135         }
136
137         return ppa_body;
138 }
139
140 static const char *get_pkg_body()
141 {
142         const char *path;
143
144         if (!pkg_body) {
145                 path = DEFAULT_WWW_DIR"/pkg.tpl";
146                 pkg_body = file_get_content(path);
147
148                 if (!pkg_body)
149                         log_err("Failed to read package template: %s", path);
150         }
151
152         return pkg_body;
153 }
154
155 static struct json_object *date_to_json(struct tm *tm)
156 {
157         json_object *json;
158
159         json = json_object_new_array();
160         json_object_array_add(json, json_object_new_int(tm->tm_year+1900));
161         json_object_array_add(json, json_object_new_int(tm->tm_mon+1));
162         json_object_array_add(json, json_object_new_int(tm->tm_mday));
163
164         return json;
165 }
166
167 static void json_add_ddts(json_object *json,
168                           struct daily_download_total **ddts)
169 {
170         json_object *json_ddt, *json_ddts;
171         struct daily_download_total *ddt;
172
173         json_ddts = json_object_new_array();
174         json_object_object_add(json, "ddts", json_ddts);
175
176         if (!ddts)
177                 return ;
178
179         while (*ddts) {
180                 ddt = *ddts;
181
182                 json_ddt = json_object_new_object();
183                 json_object_object_add(json_ddt,
184                                        "value",
185                                        json_object_new_int(ddt->count));
186                 json_object_object_add(json_ddt,
187                                        "time",
188                                        date_to_json(&ddt->date));
189
190                 json_object_array_add(json_ddts, json_ddt);
191
192                 ddts++;
193         }
194 }
195
196 static json_object *distro_to_json(struct distro_stats *d)
197 {
198         json_object *json;
199
200         json = json_object_new_object();
201
202         json_object_object_add(json,
203                                "name",
204                                json_object_new_string(d->name));
205
206         json_object_object_add(json,
207                                "count",
208                                json_object_new_int(d->download_count));
209
210         json_add_ddts(json, d->ddts);
211
212         return json;
213 }
214
215 static json_object *
216 pkg_to_json(struct ppa_stats *ppa, struct package_stats *pkg)
217 {
218         json_object *json, *json_versions, *json_distros, *json_distro;
219         struct version_stats **versions;
220         struct distro_stats **distros, *d;
221
222         json = json_object_new_object();
223
224         json_object_object_add(json,
225                                "ppa_name", json_object_new_string(ppa->name));
226         json_object_object_add(json,
227                                "ppa_owner",
228                                json_object_new_string(ppa->owner));
229
230         json_object_object_add(json,
231                                "name", json_object_new_string(pkg->name));
232
233         json_versions = json_object_new_array();
234         json_object_object_add(json, "versions", json_versions);
235         versions = pkg->versions;
236         while (*versions) {
237                 json_object_array_add
238                         (json_versions,
239                          json_object_new_string((*versions)->version));
240
241                 versions++;
242         }
243
244         distros = pkg->distros;
245         if (distros) {
246                 json_distros = json_object_new_array();
247                 json_object_object_add(json, "distros", json_distros);
248
249                 while (*distros) {
250                         d = *distros;
251
252                         if (d->download_count) {
253                                 json_distro = distro_to_json(d);
254
255                                 json_object_array_add(json_distros,
256                                                       json_distro);
257                         }
258
259                         distros++;
260                 }
261         }
262
263         json_add_ddts(json, pkg->daily_download_totals);
264
265         return json;
266 }
267
268 static char *version_to_json(struct ppa_stats *ppa,
269                              struct package_stats *pkg,
270                              struct version_stats *ver)
271 {
272         char *ret;
273         struct distro_stats **distros, *distro;
274         json_object *json, *json_distros, *json_distro, *json_archs, *json_arch;
275         struct arch_stats **archs;
276
277         json = json_object_new_object();
278
279         json_object_object_add(json,
280                                "ppa_name", json_object_new_string(ppa->name));
281         json_object_object_add(json,
282                                "ppa_owner",
283                                json_object_new_string(ppa->owner));
284
285         json_object_object_add(json,
286                                "pkg_name", json_object_new_string(pkg->name));
287
288         json_object_object_add(json,
289                                "name", json_object_new_string(ver->version));
290
291         json_add_ddts(json, ver->daily_download_totals);
292
293         distros = ver->distros;
294         json_distros = json_object_new_array();
295         json_object_object_add(json, "distros", json_distros);
296         while (*distros) {
297                 distro = *distros;
298                 json_distro = json_object_new_object();
299
300                 json_object_array_add(json_distros, json_distro);
301
302                 json_object_object_add(json_distro,
303                                        "name",
304                                        json_object_new_string(distro->name));
305
306                 archs = distro->archs;
307                 json_archs = json_object_new_array();
308                 json_object_object_add(json_distro, "archs", json_archs);
309                 while (*archs) {
310                         json_arch = json_object_new_object();
311
312                         json_object_object_add
313                                 (json_arch,
314                                  "name",
315                                  json_object_new_string((*archs)->name));
316
317                         json_object_object_add
318                                 (json_arch,
319                                  "count",
320                                  json_object_new_int((*archs)->download_count));
321
322                         json_object_array_add(json_archs, json_arch);
323                         archs++;
324                 }
325
326                 distros++;
327         }
328
329         ret = strdup(json_object_to_json_string(json));
330
331         json_object_put(json);
332
333         return ret;
334 }
335
336 static json_object *ppa_to_json(struct ppa_stats *ppa)
337 {
338         json_object *json, *json_pkgs, *json_pkg;
339         struct package_stats **pkgs;
340
341         json = json_object_new_object();
342
343         json_object_object_add(json,
344                                "ppa_name", json_object_new_string(ppa->name));
345         json_object_object_add(json,
346                                "ppa_owner",
347                                json_object_new_string(ppa->owner));
348
349         json_add_ddts(json, ppa->daily_download_totals);
350
351         pkgs = ppa->packages;
352         json_pkgs = json_object_new_array();
353         json_object_object_add(json, "packages", json_pkgs);
354         while (*pkgs) {
355                 json_pkg = json_object_new_object();
356                 json_object_array_add(json_pkgs, json_pkg);
357
358                 json_object_object_add(json_pkg, "name",
359                                        json_object_new_string((*pkgs)->name));
360
361                 json_object_object_add
362                         (json_pkg, "count",
363                          json_object_new_int((*pkgs)->download_count));
364
365                 pkgs++;
366         }
367
368         return json;
369 }
370
371 static void
372 create_html(const char *path,
373             const char *title,
374             const char *body_template,
375             const char *script)
376 {
377         FILE *f;
378         const char *footer;
379         char *header;
380
381         f = NULL;
382
383         header = get_header(title, script);
384         if (!header) {
385                 log_err(_("Failed to get the header template"));
386                 goto on_error;
387         }
388
389         f = fopen(path, "w");
390
391         if (!f) {
392                 log_err(_("Failed to open: %s"), path);
393                 goto on_error;
394         }
395
396         fputs(header, f);
397         fputs(body_template, f);
398
399         footer = get_footer();
400         if (footer)
401                 fputs(footer, f);
402
403  on_error:
404         if (header)
405                 free(header);
406
407         if (f)
408                 fclose(f);
409 }
410
411 static char *ppa_display_name(const struct ppa_stats *ppa)
412 {
413         char *ret;
414
415         ret = malloc(4+strlen(ppa->name)+1+strlen(ppa->owner)+1);
416
417         sprintf(ret, "ppa:%s/%s", ppa->owner, ppa->name);
418
419         return ret;
420 }
421
422 static void
423 index_to_html(struct ppa_stats *ppa, const char *dir)
424 {
425         char *path, *json_path, *dname;
426         json_object *json;
427         const char *body;
428
429         body = get_ppa_body();
430         if (!body) {
431                 log_err("Failed to create PPA page");
432                 return ;
433         }
434
435         json = ppa_to_json(ppa);
436         json_path = path_new(dir, "index", ".json");
437
438         log_debug(_("generating %s"), json_path);
439         json_object_to_file(json_path, json);
440         json_object_put(json);
441         free(json_path);
442
443         path = path_new(dir, "index", ".html");
444         dname = ppa_display_name(ppa);
445         create_html(path, dname, body, "ppastats_ppa();");
446         free(path);
447         free(dname);
448 }
449
450 static void
451 version_to_html(struct ppa_stats *ppa,
452                 struct package_stats *pkg,
453                 struct version_stats *version,
454                 const char *dir)
455 {
456         char *f_name, *path;
457         const char *body;
458         const char *script_tpl;
459         char *script, *json;
460
461         body = get_pkg_version_body();
462         if (!body) {
463                 log_err("Failed to create package version page");
464                 return ;
465         }
466
467         json = version_to_json(ppa, pkg, version);
468         if (!json) {
469                 log_err("Failed to create package version page");
470                 return ;
471         }
472
473         f_name = malloc(strlen(pkg->name)+1+strlen(version->version)+1);
474         sprintf(f_name, "%s_%s", pkg->name, version->version);
475
476         path = path_new(dir, f_name, ".html");
477
478         script_tpl = "var data = %s;\n ppastats_ver();";
479         script = malloc(strlen(script_tpl) - 2 + strlen(json) + 1);
480         sprintf(script, script_tpl, json);
481
482         create_html(path, f_name, body, script);
483
484         free(json);
485         free(path);
486         free(f_name);
487 }
488
489 static void
490 pkg_to_html(struct ppa_stats *ppa, struct package_stats *pkg, const char *dir)
491 {
492         char *path, *json_path, *script;
493         json_object *json;
494         const char *body;
495
496         body = get_pkg_body();
497         if (!body) {
498                 log_err("Failed to create package page: %s", pkg->name);
499                 return ;
500         }
501
502         json_path = path_new(dir, pkg->name, ".json");
503         json = pkg_to_json(ppa, pkg);
504         log_debug(_("Generating %s"), json_path);
505
506         json_object_to_file(json_path, json);
507         json_object_put(json);
508         free(json_path);
509
510         path = path_new(dir, pkg->name, ".html");
511         script = malloc(strlen("ppastats_pkg(\"\");")+
512                         strlen(pkg->name)+
513                         strlen(".json")+
514                         1);
515         sprintf(script, "ppastats_pkg(\"%s%s\");", pkg->name, ".json");
516
517         log_debug(_("Generating %s"), path);
518
519         create_html(path, pkg->name, body, script);
520         free(path);
521         free(script);
522 }
523
524 static void
525 pkgs_to_html(struct ppa_stats *ppa,
526              struct package_stats **pkgs,
527              const char *dir)
528 {
529         struct version_stats **versions;
530
531         while (*pkgs) {
532                 pkg_to_html(ppa, *pkgs, dir);
533
534                 versions = (*pkgs)->versions;
535                 while (*versions) {
536                         version_to_html(ppa, *pkgs, *versions, dir);
537
538                         versions++;
539                 }
540
541                 pkgs++;
542         }
543 }
544
545 void
546 ppa_to_html(const char *owner,
547             const char *ppa,
548             const char *package_status,
549             const char *output_dir,
550             const int install_static_files,
551             int ws_size)
552 {
553         struct ppa_stats *ppastats;
554         char *path, *f_dst;
555         char *css_dir, *js_dir;
556         int i;
557         static char *www_files[]
558                 = { DEFAULT_WWW_DIR"/jquery.min.js", "js/jquery.min.js",
559                     DEFAULT_WWW_DIR"/ppastats.js", "js/ppastats.js",
560                     DEFAULT_WWW_DIR"/jqplot.dateAxisRenderer.min.js",
561                     "js/jqplot.dateAxisRenderer.min.js",
562                     DEFAULT_WWW_DIR"/jquery.jqplot.min.js",
563                     "js/jquery.jqplot.min.js",
564                     DEFAULT_WWW_DIR"/excanvas.js", "js/excanvas.js",
565                     DEFAULT_WWW_DIR"/ppastats.css", "css/ppastats.css",
566                     DEFAULT_WWW_DIR"/wpitchoune.css", "css/wpitchoune.css",
567                     DEFAULT_WWW_DIR"/jquery.jqplot.min.css",
568                     "css/jquery.jqplot.min.css" };
569
570         mkdirs(output_dir, 0777);
571
572         if (install_static_files) {
573                 css_dir = path_append(output_dir, "css");
574                 js_dir = path_append(output_dir, "js");
575
576                 mkdir(css_dir, 0777);
577                 mkdir(js_dir, 0777);
578
579                 for (i = 0; i < 8; i++) {
580                         f_dst = path_append(output_dir, www_files[2*i+1]);
581
582                         log_debug(_("Copying %s %s"), www_files[2*i], f_dst);
583                         fcopy(www_files[2*i], f_dst);
584
585                         free(f_dst);
586                 }
587                 free(css_dir);
588                 free(js_dir);
589         }
590
591         ppastats = create_ppa_stats(owner, ppa, package_status, ws_size);
592
593         path = path_new(output_dir, "ppa", ".html");
594
595         pkgs_to_html(ppastats, ppastats->packages, output_dir);
596
597         index_to_html(ppastats, output_dir);
598
599         ppa_stats_free(ppastats);
600
601         free(path);
602 }