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