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