* v0.0.3
[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   </body>\n\
102 </html>"
103
104 #define FCOPY_BUF_SZ 4096
105 static int file_copy(FILE * src, FILE * dst)
106 {
107         int ret = 0;
108         char *buf = malloc(FCOPY_BUF_SZ);
109         int n;
110
111         if (!buf)
112                 return FILE_COPY_ERROR_ALLOC_BUFFER;
113
114         while (!ret) {
115                 n = fread(buf, 1, FCOPY_BUF_SZ, src);
116                 if (n) {
117                         if (fwrite(buf, 1, n, dst) != n)
118                                 ret = FILE_COPY_ERROR_WRITE;
119                 } else {
120                         if (!feof(src))
121                                 ret = FILE_COPY_ERROR_READ;
122                         else
123                                 break;
124                 }
125         }
126
127         free(buf);
128
129         return ret;
130 }
131
132 int
133 fcopy(const char *src, const char *dst)
134 {
135         FILE *fsrc, *fdst;
136         int ret = 0;
137
138         if (debug)
139                 printf("DEBUG: copy: %s to %s\n", src, dst);
140
141         fsrc = fopen(src, "r");
142
143         if (fsrc) {
144                 fdst = fopen(dst, "w+");
145
146                 if (fdst) {
147                         ret = file_copy(fsrc, fdst);
148                         fclose(fdst);
149                 } else {
150                         ret = FILE_COPY_ERROR_OPEN_DST;
151                 }
152
153                 fclose(fsrc);
154         } else {
155                 ret = FILE_COPY_ERROR_OPEN_SRC;
156         }
157
158         return ret;
159 }
160
161 static char *get_path(const char *dir, const char *file)
162 {
163         char *path = malloc(strlen(dir)+1+
164                             strlen(file)+
165                             strlen(".html")+
166                             1);
167
168         strcpy(path, dir);
169         strcat(path, "/");
170         strcat(path, file);
171         strcat(path, ".html");
172
173         return path;
174 }
175
176 static struct json_object *date_to_json(struct tm *tm)
177 {
178         json_object *json;
179
180         json = json_object_new_array();
181         json_object_array_add(json, json_object_new_int(tm->tm_year+1900));
182         json_object_array_add(json, json_object_new_int(tm->tm_mon+1));
183         json_object_array_add(json, json_object_new_int(tm->tm_mday));
184
185         return json;
186 }
187
188 static void json_add_ddts(json_object *json,
189                           struct daily_download_total **ddts)
190 {
191         json_object *json_ddt, *json_ddts;
192         struct daily_download_total *ddt;
193
194         json_ddts = json_object_new_array();
195         json_object_object_add(json, "ddts", json_ddts);
196
197         if (!ddts)
198                 return ;
199
200         while (*ddts) {
201                 ddt = *ddts;
202
203                 json_ddt = json_object_new_object();
204                 json_object_object_add(json_ddt,
205                                        "value",
206                                        json_object_new_int(ddt->count));
207                 json_object_object_add(json_ddt,
208                                        "time",
209                                        date_to_json(&ddt->date));
210
211                 json_object_array_add(json_ddts, json_ddt);
212
213                 ddts++;
214         }
215 }
216
217 static char *pkg_to_json(struct ppa_stats *ppa, struct package_stats *pkg)
218 {
219         json_object *json, *json_versions;
220         char *ret;
221         struct version_stats **versions;
222
223         json = json_object_new_object();
224
225         json_object_object_add(json,
226                                "ppa_name", json_object_new_string(ppa->name));
227         json_object_object_add(json,
228                                "ppa_owner",
229                                json_object_new_string(ppa->owner));
230
231         json_object_object_add(json,
232                                "name", json_object_new_string(pkg->name));
233
234         json_versions = json_object_new_array();
235         json_object_object_add(json, "versions", json_versions);
236         versions = pkg->versions;
237         while (*versions) {
238                 json_object_array_add
239                         (json_versions,
240                          json_object_new_string((*versions)->version));
241
242                 versions++;
243         }
244
245         json_add_ddts(json, pkg->daily_download_totals);
246
247         ret = strdup(json_object_to_json_string(json));
248
249         json_object_put(json);
250
251         return ret;
252 }
253
254 static char *version_to_json(struct ppa_stats *ppa,
255                              struct package_stats *pkg,
256                              struct version_stats *ver)
257 {
258         json_object *json;
259         char *ret;
260
261         json = json_object_new_object();
262
263         json_object_object_add(json,
264                                "ppa_name", json_object_new_string(ppa->name));
265         json_object_object_add(json,
266                                "ppa_owner",
267                                json_object_new_string(ppa->owner));
268
269         json_object_object_add(json,
270                                "pkg_name", json_object_new_string(pkg->name));
271
272         json_object_object_add(json,
273                                "name", json_object_new_string(ver->version));
274
275         json_add_ddts(json, ver->daily_download_totals);
276
277         ret = strdup(json_object_to_json_string(json));
278
279         json_object_put(json);
280
281         return ret;
282 }
283
284 static void
285 pkg_to_html(struct ppa_stats *ppa, struct package_stats *pkg, const char *dir)
286 {
287         char *path;
288         FILE *f;
289         char *json;
290
291         path = get_path(dir, pkg->name);
292         f = fopen(path, "w");
293
294         if (!f) {
295                 fprintf(stderr, "ERROR: failed to open: %s\n", path);
296                 return ;
297         }
298
299         json = pkg_to_json(ppa, pkg);
300
301         fprintf(f, HTML_PKG_TEMPLATE, json);
302
303         fclose(f);
304
305         free(path);
306         free(json);
307 }
308
309 static void
310 version_to_html(struct ppa_stats *ppa,
311                 struct package_stats *pkg,
312                 struct version_stats *version,
313                 const char *dir)
314 {
315         char *f_name, *path;
316         FILE *f;
317
318         f_name = malloc(strlen(pkg->name)+1+strlen(version->version)+1);
319         sprintf(f_name, "%s_%s", pkg->name, version->version);
320
321         path = get_path(dir, f_name);
322         f = fopen(path, "w");
323
324         if (!f) {
325                 fprintf(stderr, "ERROR: failed to open: %s\n", path);
326                 return ;
327         }
328
329         fprintf(f, HTML_VERSION_TEMPLATE, version_to_json(ppa, pkg, version));
330
331         fclose(f);
332
333         free(path);
334         free(f_name);
335 }
336
337 static void
338 packages_to_html(struct ppa_stats *ppa,
339                  struct package_stats **packages,
340                  const char *dir)
341 {
342         struct package_stats **cur;
343         struct version_stats **versions;
344
345         cur = packages;
346         while (*cur) {
347                 pkg_to_html(ppa, *cur, dir);
348
349                 versions = (*cur)->versions;
350                 while (*versions) {
351                         version_to_html(ppa, *cur, *versions, dir);
352
353                         versions++;
354                 }
355
356                 cur++;
357         }
358 }
359
360 static char *append_path(const char *odir, const char *name)
361 {
362         char *dir;
363
364         dir = malloc(strlen(odir)+1+strlen(name)+1);
365
366         sprintf(dir, "%s/%s", odir, name);
367
368         return dir;
369 }
370
371 void
372 ppa_to_html(const char *owner,
373             const char *ppa,
374             const char *package_status,
375             const char *output_dir)
376 {
377         struct ppa_stats *ppastats;
378         struct daily_download_total **totals;
379         char *path, *f_dst;
380         char *css_dir, *js_dir;
381         int i;
382         static char *www_files[]
383                 = { DEFAULT_WWW_DIR"/jquery.min.js", "js/jquery.min.js",
384                     DEFAULT_WWW_DIR"/ppastats.js", "js/ppastats.js",
385                     DEFAULT_WWW_DIR"/jqplot.dateAxisRenderer.min.js",
386                     "js/jqplot.dateAxisRenderer.min.js",
387                     DEFAULT_WWW_DIR"/jquery.jqplot.min.js",
388                     "js/jquery.jqplot.min.js",
389                     DEFAULT_WWW_DIR"/ppastats.css", "css/ppastats.css",
390                     DEFAULT_WWW_DIR"/jquery.jqplot.min.css",
391                     "css/jquery.jqplot.min.css" };
392
393         css_dir = append_path(output_dir, "css");
394         js_dir = append_path(output_dir, "js");
395
396         mkdir(css_dir, 0777);
397         mkdir(js_dir, 0777);
398
399         for (i = 0; i < 6; i++) {
400                 f_dst = append_path(output_dir, www_files[2*i+1]);
401                 fcopy(www_files[2*i], f_dst);
402
403                 free(f_dst);
404         }
405
406         ppastats = create_ppa_stats(owner, ppa, package_status);
407         totals = ppastats->daily_download_totals;
408
409         path = get_path(output_dir, "ppa");
410
411         packages_to_html(ppastats, ppastats->packages, output_dir);
412
413         ppa_stats_free(ppastats);
414
415         free(path);
416         free(css_dir);
417         free(js_dir);
418 }