Set release details for 0.2.13
[quvi.git] / lib / lua_wrap.c
blob5d39ed305cdf4659bc152de5f9238b94e3f43599
1 /* quvi
2 * Copyright (C) 2010 Toni Gundogdu <legatvs@gmail.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library 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 GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA
20 #include "config.h"
22 #include <string.h>
23 #ifdef HAVE_SYS_TYPES_H
24 #include <sys/types.h>
25 #endif
26 #include <dirent.h>
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30 #include <assert.h>
32 #include <lualib.h>
33 #include <lauxlib.h>
35 #include "quvi/quvi.h"
36 #include "internal.h"
37 #include "util.h"
38 #include "curl_wrap.h"
39 #include "lua_wrap.h"
41 #ifdef _0
42 static void dump_lua_stack(lua_State * L)
44 /* http://www.lua.org/pil/24.2.3.html */
45 int i, top;
47 top = lua_gettop(L);
49 for (i = 1; i <= top; i++) /* repeat for each level */
51 int t = lua_type(L, i);
52 switch (t)
54 case LUA_TSTRING: /* strings */
55 printf("`%s'", lua_tostring(L, i));
56 break;
58 case LUA_TBOOLEAN: /* booleans */
59 printf(lua_toboolean(L, i) ? "true" : "false");
60 break;
62 case LUA_TNUMBER: /* numbers */
63 printf("%g", lua_tonumber(L, i));
64 break;
66 default: /* other values */
67 printf("%s", lua_typename(L, t));
68 break;
70 printf(" "); /* put a separator */
72 printf("\n"); /* end the listing */
74 #endif
76 static _quvi_video_t qv = NULL;
78 /* c functions for lua. */
80 static int l_quvi_fetch(lua_State * l)
82 QUVIstatusType st;
83 luaL_Buffer b;
84 _quvi_t quvi;
85 QUVIcode rc;
86 char *data;
88 quvi = qv->quvi;
89 st = QUVISTATUSTYPE_PAGE;
91 if (!lua_isstring(l, 1))
92 luaL_error(l, "`quvi.fetch' expects `url' argument");
94 rc = fetch_to_mem(qv, lua_tostring(l, 1), l, &data);
96 if (rc == QUVI_OK)
99 if (data != NULL)
102 luaL_buffinit(l, &b);
103 luaL_addstring(&b, data);
104 luaL_pushresult(&b);
105 _free(data);
109 else
112 /* Rare. Server returns an empty page (no content). Possibly related
113 * to a proxy software in use. cURL returns CURLE_OK so the only way
114 * we can tell this is by checking the data pointer (==NULL). */
116 luaL_error(l, "server returned no data (quvicode=%d, curlcode=0)",
117 rc);
122 else
124 _free(data);
125 luaL_error(l, qv->quvi->errmsg);
128 return (1);
131 static int l_quvi_unescape(lua_State * l)
133 luaL_Buffer b;
134 char *tmp;
136 if (!lua_isstring(l, -1))
137 luaL_error(l, "expected a string");
139 tmp = unescape(qv->quvi, strdup((char *)lua_tostring(l, 1)));
140 lua_pop(l, 1);
142 luaL_buffinit(l, &b);
143 luaL_addstring(&b, tmp);
144 _free(tmp);
145 luaL_pushresult(&b);
147 return (1);
150 /* wrapper. */
152 static QUVIcode new_lua_script(_quvi_lua_script_t * dst)
154 struct _quvi_lua_script_s *qls;
156 qls = calloc(1, sizeof(*qls));
157 if (!qls)
158 return (QUVI_MEM);
160 *dst = qls;
162 return (QUVI_OK);
165 typedef int (*filter_func) (const struct dirent *);
167 static QUVIcode
168 scan_dir(llst_node_t * dst, const char *path, filter_func filter)
170 char *show_scandir, *show_script;
171 struct dirent *de;
172 DIR *dir;
174 dir = opendir(path);
175 if (!dir)
176 return (QUVI_OK);
178 show_scandir = getenv("QUVI_SHOW_SCANDIR");
179 show_script = getenv("QUVI_SHOW_SCRIPT");
181 if (show_scandir)
182 fprintf(stderr, "%s: %s\n", __PRETTY_FUNCTION__, path);
184 while ((de = readdir(dir)))
187 if (filter(de))
190 _quvi_lua_script_t qls;
192 QUVIcode rc = new_lua_script(&qls);
194 if (rc != QUVI_OK)
195 return (rc);
197 asprintf((char **)&qls->basename, "%s", de->d_name);
198 asprintf((char **)&qls->path, "%s/%s", path, de->d_name);
200 if (show_script)
201 fprintf(stderr, "%s: %s\n", __PRETTY_FUNCTION__, qls->path);
203 llst_add(dst, qls);
207 closedir(dir);
209 return (QUVI_OK);
212 static QUVIcode
213 scan_known_dirs(llst_node_t * dst, const char *spath,
214 filter_func filter)
216 char *homedir, *path, *basedir;
217 char buf[PATH_MAX];
219 #define _scan \
220 do { \
221 QUVIcode rc = scan_dir (dst, path, filter); \
222 _free (path); \
223 if (rc != QUVI_OK) \
224 return (rc); \
225 } while (0)
227 /* QUVI_BASEDIR. */
228 basedir = getenv("QUVI_BASEDIR");
229 if (basedir)
231 asprintf(&path, "%s/%s", basedir, spath);
232 _scan;
233 return (QUVI_OK);
236 /* Current working directory. */
237 asprintf(&path, "%s/%s", getcwd(buf, sizeof(buf)), spath);
238 _scan;
240 /* Home directory. */
241 homedir = getenv("HOME");
242 if (homedir)
244 asprintf(&path, "%s/.quvi/%s", homedir, spath);
245 _scan;
246 asprintf(&path, "%s/.config/quvi/%s", homedir, spath);
247 _scan;
248 asprintf(&path, "%s/.local/share/quvi/%s", homedir, spath);
249 _scan;
252 /* --datadir. */
253 asprintf(&path, "%s/%s", DATADIR, spath);
254 _scan;
256 /* pkgdatadir. */
257 asprintf(&path, "%s/%s", PKGDATADIR, spath);
258 _scan;
260 #undef _scan
262 return (QUVI_OK);
265 static const luaL_Reg reg_meth[] =
267 {"fetch", l_quvi_fetch},
268 {"unescape", l_quvi_unescape},
269 {NULL, NULL}
272 static int lua_files_only(const struct dirent *de)
274 const char *ext = strrchr(de->d_name, '.');
275 return (de->d_name[0] != '.' && ext != 0 && strcmp(ext, ".lua") == 0);
278 /* init. */
280 int init_lua(_quvi_t quvi)
282 QUVIcode rc;
284 quvi->lua = (lua_State *) lua_open();
285 if (!quvi->lua)
286 return (QUVI_LUAINIT);
288 luaL_openlibs(quvi->lua);
289 luaL_openlib(quvi->lua, "quvi", reg_meth, 1);
291 rc = scan_known_dirs(&quvi->util_scripts, "lua/util", lua_files_only);
293 if (rc != QUVI_OK)
294 return (rc);
296 if (llst_size(quvi->util_scripts) == 0)
297 return (QUVI_NOLUAUTIL);
299 rc = scan_known_dirs(&quvi->website_scripts, "lua/website",
300 lua_files_only);
302 if (rc != QUVI_OK)
303 return (rc);
305 return (llst_size(quvi->website_scripts)
306 ? QUVI_OK : QUVI_NOLUAWEBSITE);
309 void free_lua(_quvi_t * quvi)
312 #define _rel(w) \
313 do { \
314 llst_node_t curr = w; \
315 while (curr) { \
316 _quvi_lua_script_t s = (_quvi_lua_script_t) curr->data; \
317 _free (s->basename); \
318 _free (s->path); \
319 curr = curr->next; \
321 } while (0)
323 _rel((*quvi)->util_scripts);
324 _rel((*quvi)->website_scripts);
326 #undef _rel
328 llst_free(&(*quvi)->util_scripts);
329 assert((*quvi)->util_scripts == NULL);
331 llst_free(&(*quvi)->website_scripts);
332 assert((*quvi)->website_scripts == NULL);
334 lua_close((*quvi)->lua);
335 (*quvi)->lua = NULL;
338 static void set_key(lua_State * l, const char *key)
340 lua_pushstring(l, key);
341 lua_gettable(l, -2);
344 #define _istype(t) \
345 do { \
346 if (!lua_is##t(l, -1)) { \
347 luaL_error (l, \
348 "%s: undefined value for key `%s' in the returned table, " \
349 "expected `%s' type value", \
350 qls->path, key, #t); \
352 } while (0)
354 static char *get_field_req_s(lua_State * l, _quvi_lua_script_t qls,
355 const char *key)
357 char *s;
358 set_key(l, key);
359 _istype(string);
360 s = (char *)lua_tostring(l, -1);
361 lua_pop(l, 1);
362 return (s);
365 char *lua_get_field_s(lua_State * l, const char *key)
367 char *s;
368 set_key(l, key);
369 s = (char *)lua_tostring(l, -1);
370 lua_pop(l, 1);
371 return (s);
374 static int get_field_b(lua_State * l, _quvi_lua_script_t qls,
375 const char *key)
377 int b;
378 set_key(l, key);
379 _istype(boolean);
380 b = lua_toboolean(l, -1);
381 lua_pop(l, 1);
382 return (b);
385 static long get_field_n(lua_State * l, _quvi_lua_script_t qls,
386 const char *key)
388 long n;
389 set_key(l, key);
390 _istype(number);
391 n = lua_tonumber(l, -1);
392 lua_pop(l, 1);
393 return (n);
396 static QUVIcode
397 iter_video_url(lua_State * l,
398 _quvi_lua_script_t qls, const char *key,
399 _quvi_video_t qv)
401 QUVIcode rc = QUVI_OK;
403 set_key(l, key);
404 _istype(table);
405 lua_pushnil(l);
407 while (lua_next(l, -2) && rc == QUVI_OK)
409 if (!lua_isstring(l, -1))
410 luaL_error(l, "%s: expected an array of URL strings", qls->path);
412 rc = add_video_link(&qv->link, "%s", lua_tostring(l, -1));
414 lua_pop(l, 1);
417 lua_pop(l, 1);
419 return (rc);
422 #undef _istype
424 static void set_field(lua_State * l, const char *key, const char *value)
426 lua_pushstring(l, key);
427 lua_pushstring(l, value);
428 lua_settable(l, -3);
431 /* Util scripts. */
433 /* Finds the specified util script from the list. */
435 static _quvi_lua_script_t find_util_script(_quvi_t quvi,
436 const char *basename)
438 llst_node_t curr = quvi->util_scripts;
439 while (curr)
441 _quvi_lua_script_t s = (_quvi_lua_script_t) curr->data;
442 if (strcmp(s->basename, basename) == 0)
443 return (s);
444 curr = curr->next;
446 return (NULL);
449 static QUVIcode
450 prep_util_script(_quvi_t quvi,
451 const char *script_fname,
452 const char *func_name, lua_State ** pl,
453 _quvi_lua_script_t *qls)
455 lua_State *l;
457 assert(quvi != NULL);
458 assert(func_name != NULL);
459 assert(script_fname != NULL);
461 *pl = NULL;
462 *qls = NULL;
464 *qls = find_util_script(quvi, script_fname);
465 if (*qls == NULL)
466 return (QUVI_NOLUAUTIL);
468 l = quvi->lua;
469 assert(l != NULL);
471 lua_pushnil(l);
472 lua_getglobal(l, func_name);
474 if (luaL_dofile(l, (*qls)->path))
475 luaL_error(l, "%s: %s", (*qls)->path, lua_tostring(l, -1));
477 lua_getglobal(l, func_name);
479 if (!lua_isfunction(l, -1))
481 luaL_error(l,
482 "%s: function `%s' not found", (*qls)->path, func_name);
485 *pl = l;
487 return (QUVI_OK);
490 /* Executes the `suffix_from_contenttype' lua function. */
492 QUVIcode run_lua_suffix_func(_quvi_t quvi, _quvi_video_link_t qvl)
494 const static char script_fname[] = "content_type.lua";
495 const static char func_name[] = "suffix_from_contenttype";
496 _quvi_lua_script_t qls;
497 lua_State *l;
498 QUVIcode rc;
500 assert(quvi != NULL);
501 assert(qvl != NULL);
503 rc = prep_util_script(quvi, script_fname, func_name, &l, &qls);
504 if (rc != QUVI_OK)
505 return (rc);
507 assert(l != NULL);
508 assert(qls != NULL);
510 lua_pushstring(l, qvl->content_type);
512 if (lua_pcall(l, 1, 1, 0))
513 luaL_error(l, "%s: %s", qls->path, lua_tostring(l, -1));
515 if (lua_isstring(l, -1))
516 freprintf(&qvl->suffix, "%s", lua_tostring(l, -1));
517 else
519 luaL_error(l,
520 "%s: expected `%s' function to return a string",
521 qls->path, func_name);
524 lua_pop(l, 1);
526 return (QUVI_OK);
529 /* Executes the `trim_fields' lua function. */
531 static QUVIcode run_lua_trim_fields_func(_quvi_video_t video, int ref)
533 const static char script_fname[] = "trim.lua";
534 const static char func_name[] = "trim_fields";
535 _quvi_lua_script_t qls;
536 _quvi_t quvi;
537 lua_State *l;
538 QUVIcode rc;
540 assert(video != NULL);
542 quvi = video->quvi;
543 assert(quvi != NULL);
545 rc = prep_util_script(quvi, script_fname, func_name, &l, &qls);
546 if (rc != QUVI_OK)
547 return (rc);
549 assert(l != NULL);
550 assert(qls != NULL);
552 lua_rawgeti(l, LUA_REGISTRYINDEX, ref);
554 if (lua_pcall(l, 1, 1, 0))
555 luaL_error(l, "%s: %s", qls->path, lua_tostring(l, -1));
557 if (!lua_istable(l, -1))
559 luaL_error(l,
560 "%s: expected `%s' function to return a table",
561 qls->path, func_name);
564 return (QUVI_OK);
567 /* Executes the `charset_from_data' lua function. */
569 QUVIcode run_lua_charset_func(_quvi_video_t video, const char *data)
571 const static char script_fname[] = "charset.lua";
572 const static char func_name[] = "charset_from_data";
573 _quvi_lua_script_t qls;
574 _quvi_t quvi;
575 lua_State *l;
576 QUVIcode rc;
578 assert(video != NULL);
579 quvi = video->quvi;
580 assert(quvi != NULL);
582 rc = prep_util_script(quvi, script_fname, func_name, &l, &qls);
583 if (rc != QUVI_OK)
584 return (rc);
586 assert(l != NULL);
587 assert(qls != NULL);
589 lua_pushstring(l, data);
591 if (lua_pcall(l, 1, 1, 0))
592 luaL_error(l, "%s: %s", qls->path, lua_tostring(l, -1));
594 if (lua_isstring(l, -1))
595 freprintf(&video->charset, "%s", lua_tostring(l, -1));
596 else if (lua_isnil(l, -1)) ; /* Charset was not found. We can live with that. */
597 else
599 luaL_error(l,
600 "%s: expected `%s' function to return a string",
601 qls->path, func_name);
604 lua_pop(l, 1);
606 return (QUVI_OK);
609 /* Website scripts. */
611 /* Executes the host script's "ident" function. */
613 QUVIcode run_ident_func(lua_ident_t ident, llst_node_t node)
615 _quvi_lua_script_t qls;
616 char *script_dir;
617 _quvi_t quvi;
618 lua_State *l;
619 QUVIcode rc;
621 assert(ident != NULL);
622 assert(node != NULL);
624 quvi = ident->quvi;
625 assert(quvi != NULL); /* seterr macro uses this. */
627 l = quvi->lua;
628 assert(l != NULL);
630 rc = QUVI_NOSUPPORT;
631 qls = (_quvi_lua_script_t) node->data;
633 lua_pushnil(l);
634 lua_pushnil(l);
636 lua_setglobal(l, "ident");
637 lua_setglobal(l, "parse");
639 if (luaL_dofile(l, qls->path))
641 freprintf(&quvi->errmsg, "%s", lua_tostring(l, -1));
642 return (QUVI_LUA);
645 lua_getglobal(l, "ident");
647 if (!lua_isfunction(l, -1))
649 freprintf(&quvi->errmsg, "%s: `ident' function not found",
650 qls->path);
651 return (QUVI_LUA);
654 script_dir = dirname_from(qls->path);
656 lua_newtable(l);
657 set_field(l, "page_url", ident->url);
658 set_field(l, "script_dir", script_dir);
660 _free(script_dir);
662 if (lua_pcall(l, 1, 1, 0))
664 freprintf(&quvi->errmsg, "%s", lua_tostring(l, -1));
665 return (QUVI_LUA);
668 if (lua_istable(l, -1))
671 ident->domain = strdup(get_field_req_s(l, qls, "domain"));
673 ident->formats = strdup(get_field_req_s(l, qls, "formats"));
675 ident->categories = get_field_n(l, qls, "categories");
677 rc = get_field_b(l, qls, "handles")
678 ? QUVI_OK : QUVI_NOSUPPORT;
680 if (rc == QUVI_OK)
681 rc = ident->categories & quvi->
682 category ? QUVI_OK : QUVI_NOSUPPORT;
685 else
687 luaL_error(l,
688 "%s: expected `ident' function to return a table", qls->path);
691 lua_pop(l, 1);
693 return (rc);
696 /* Executes the host script's "parse" function. */
698 static QUVIcode
699 run_parse_func(lua_State * l, llst_node_t node, _quvi_video_t video)
701 static const char func_name[] = "parse";
702 _quvi_lua_script_t qls;
703 char *script_dir;
704 _quvi_t quvi;
705 QUVIcode rc;
707 assert(node != NULL);
708 assert(video != NULL);
710 quvi = video->quvi; /* seterr macro needs this. */
711 qls = (_quvi_lua_script_t) node->data;
713 lua_getglobal(l, func_name);
715 if (!lua_isfunction(l, -1))
717 freprintf(&quvi->errmsg,
718 "%s: `%s' function not found", qls->path, func_name);
719 return (QUVI_LUA);
722 script_dir = dirname_from(qls->path);
724 lua_newtable(l);
725 set_field(l, "page_url", video->page_link);
726 set_field(l, "requested_format", video->quvi->format);
727 set_field(l, "redirect", "");
728 set_field(l, "starttime", "");
729 set_field(l, "script_dir", script_dir);
731 _free(script_dir);
733 if (lua_pcall(l, 1, 1, 0))
735 freprintf(&quvi->errmsg, "%s", lua_tostring(l, -1));
736 return (QUVI_LUA);
739 if (!lua_istable(l, -1))
741 freprintf(&quvi->errmsg,
742 "expected `%s' function return a table", func_name);
743 return (QUVI_LUA);
746 freprintf(&video->redirect, "%s",
747 get_field_req_s(l, qls, "redirect"));
749 if (strlen(video->redirect) == 0)
752 const int r = luaL_ref(l, LUA_REGISTRYINDEX);
754 rc = run_lua_trim_fields_func(video, r);
756 luaL_unref(l, LUA_REGISTRYINDEX, r);
758 if (rc == QUVI_OK)
761 freprintf(&video->host_id, "%s",
762 get_field_req_s(l, qls, "host_id"));
763 freprintf(&video->title, "%s", get_field_req_s(l, qls, "title"));
764 freprintf(&video->id, "%s", get_field_req_s(l, qls, "id"));
765 freprintf(&video->starttime, "%s",
766 get_field_req_s(l, qls, "starttime"));
768 rc = iter_video_url(l, qls, "url", video);
773 lua_pop(l, 1);
775 return (rc);
778 /* Match host script to the url. */
780 static llst_node_t find_host_script_node(_quvi_video_t video,
781 QUVIcode * rc)
783 llst_node_t curr;
784 _quvi_t quvi;
785 lua_State *l;
787 qv = video;
788 quvi = video->quvi; /* seterr macro uses this. */
789 curr = quvi->website_scripts;
790 l = quvi->lua;
792 while (curr)
794 struct lua_ident_s ident;
796 ident.quvi = video->quvi;
797 ident.url = video->page_link;
798 ident.domain = NULL;
799 ident.formats = NULL;
801 /* Check if script ident this url. */
802 *rc = run_ident_func(&ident, curr);
804 _free(ident.domain);
805 _free(ident.formats);
807 if (*rc == QUVI_OK)
809 /* Found a script. */
810 return (curr);
812 else if (*rc != QUVI_NOSUPPORT)
814 /* A script error. Terminate with it. */
815 return (NULL);
818 curr = curr->next;
821 /* Trust that run_ident_func sets the rc. */
822 freprintf(&quvi->errmsg, "no support: %s", video->page_link);
824 return (NULL);
827 /* Match host script to the url */
828 QUVIcode find_host_script(_quvi_video_t video)
830 QUVIcode rc;
831 find_host_script_node(video, &rc);
832 return (rc);
835 /* Match host script to the url and run parse func */
836 QUVIcode find_host_script_and_parse(_quvi_video_t video)
838 llst_node_t script;
839 _quvi_t quvi;
840 lua_State *l;
841 QUVIcode rc;
843 qv = video;
844 quvi = video->quvi; /* seterr macro uses this. */
845 l = quvi->lua;
847 script = find_host_script_node(video, &rc);
848 if (script == NULL)
849 return (rc);
851 /* found a script. */
852 return (run_parse_func(l, script, video));
855 /* vim: set ts=2 sw=2 tw=72 expandtab: */