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