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
23 #ifdef HAVE_SYS_TYPES_H
24 #include <sys/types.h>
35 #include "quvi/quvi.h"
38 #include "curl_wrap.h"
42 static void dump_lua_stack(lua_State
* L
)
44 /* http://www.lua.org/pil/24.2.3.html */
49 for (i
= 1; i
<= top
; i
++) /* repeat for each level */
51 int t
= lua_type(L
, i
);
54 case LUA_TSTRING
: /* strings */
55 printf("`%s'", lua_tostring(L
, i
));
58 case LUA_TBOOLEAN
: /* booleans */
59 printf(lua_toboolean(L
, i
) ? "true" : "false");
62 case LUA_TNUMBER
: /* numbers */
63 printf("%g", lua_tonumber(L
, i
));
66 default: /* other values */
67 printf("%s", lua_typename(L
, t
));
70 printf(" "); /* put a separator */
72 printf("\n"); /* end the listing */
76 static _quvi_video_t qv
= NULL
;
78 /* c functions for lua. */
80 static int l_quvi_fetch(lua_State
* l
)
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
);
102 luaL_buffinit(l
, &b
);
103 luaL_addstring(&b
, data
);
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)",
125 luaL_error(l
, qv
->quvi
->errmsg
);
131 static int l_quvi_unescape(lua_State
* l
)
136 if (!lua_isstring(l
, -1))
137 luaL_error(l
, "expected a string");
139 tmp
= unescape(qv
->quvi
, strdup((char *)lua_tostring(l
, 1)));
142 luaL_buffinit(l
, &b
);
143 luaL_addstring(&b
, tmp
);
152 static QUVIcode
new_lua_script(_quvi_lua_script_t
* dst
)
154 struct _quvi_lua_script_s
*qls
;
156 qls
= calloc(1, sizeof(*qls
));
165 typedef int (*filter_func
) (const struct dirent
*);
168 scan_dir(llst_node_t
* dst
, const char *path
, filter_func filter
)
170 char *show_scandir
, *show_script
;
178 show_scandir
= getenv("QUVI_SHOW_SCANDIR");
179 show_script
= getenv("QUVI_SHOW_SCRIPT");
182 fprintf(stderr
, "%s: %s\n", __PRETTY_FUNCTION__
, path
);
184 while ((de
= readdir(dir
)))
190 _quvi_lua_script_t qls
;
192 QUVIcode rc
= new_lua_script(&qls
);
197 asprintf((char **)&qls
->basename
, "%s", de
->d_name
);
198 asprintf((char **)&qls
->path
, "%s/%s", path
, de
->d_name
);
201 fprintf(stderr
, "%s: %s\n", __PRETTY_FUNCTION__
, qls
->path
);
213 scan_known_dirs(llst_node_t
* dst
, const char *spath
,
216 char *homedir
, *path
, *basedir
;
221 QUVIcode rc = scan_dir (dst, path, filter); \
228 basedir
= getenv("QUVI_BASEDIR");
231 asprintf(&path
, "%s/%s", basedir
, spath
);
236 /* Current working directory. */
237 asprintf(&path
, "%s/%s", getcwd(buf
, sizeof(buf
)), spath
);
240 /* Home directory. */
241 homedir
= getenv("HOME");
244 asprintf(&path
, "%s/.quvi/%s", homedir
, spath
);
246 asprintf(&path
, "%s/.config/quvi/%s", homedir
, spath
);
248 asprintf(&path
, "%s/.local/share/quvi/%s", homedir
, spath
);
253 asprintf(&path
, "%s/%s", DATADIR
, spath
);
257 asprintf(&path
, "%s/%s", PKGDATADIR
, spath
);
265 static const luaL_Reg reg_meth
[] =
267 {"fetch", l_quvi_fetch
},
268 {"unescape", l_quvi_unescape
},
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);
280 int init_lua(_quvi_t quvi
)
284 quvi
->lua
= (lua_State
*) lua_open();
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
);
296 if (llst_size(quvi
->util_scripts
) == 0)
297 return (QUVI_NOLUAUTIL
);
299 rc
= scan_known_dirs(&quvi
->website_scripts
, "lua/website",
305 return (llst_size(quvi
->website_scripts
)
306 ? QUVI_OK
: QUVI_NOLUAWEBSITE
);
309 void free_lua(_quvi_t
* quvi
)
314 llst_node_t curr = w; \
316 _quvi_lua_script_t s = (_quvi_lua_script_t) curr->data; \
317 _free (s->basename); \
323 _rel((*quvi
)->util_scripts
);
324 _rel((*quvi
)->website_scripts
);
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
);
338 static void set_key(lua_State
* l
, const char *key
)
340 lua_pushstring(l
, key
);
346 if (!lua_is##t(l, -1)) { \
348 "%s: undefined value for key `%s' in the returned table, " \
349 "expected `%s' type value", \
350 qls->path, key, #t); \
354 static char *get_field_req_s(lua_State
* l
, _quvi_lua_script_t qls
,
360 s
= (char *)lua_tostring(l
, -1);
365 char *lua_get_field_s(lua_State
* l
, const char *key
)
369 s
= (char *)lua_tostring(l
, -1);
374 static int get_field_b(lua_State
* l
, _quvi_lua_script_t qls
,
380 b
= lua_toboolean(l
, -1);
385 static long get_field_n(lua_State
* l
, _quvi_lua_script_t qls
,
391 n
= lua_tonumber(l
, -1);
397 iter_video_url(lua_State
* l
,
398 _quvi_lua_script_t qls
, const char *key
,
401 QUVIcode rc
= QUVI_OK
;
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));
424 static void set_field(lua_State
* l
, const char *key
, const char *value
)
426 lua_pushstring(l
, key
);
427 lua_pushstring(l
, value
);
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
;
441 _quvi_lua_script_t s
= (_quvi_lua_script_t
) curr
->data
;
442 if (strcmp(s
->basename
, basename
) == 0)
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
)
457 assert(quvi
!= NULL
);
458 assert(func_name
!= NULL
);
459 assert(script_fname
!= NULL
);
464 *qls
= find_util_script(quvi
, script_fname
);
466 return (QUVI_NOLUAUTIL
);
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))
482 "%s: function `%s' not found", (*qls
)->path
, func_name
);
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
;
500 assert(quvi
!= NULL
);
503 rc
= prep_util_script(quvi
, script_fname
, func_name
, &l
, &qls
);
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));
520 "%s: expected `%s' function to return a string",
521 qls
->path
, func_name
);
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
;
540 assert(video
!= NULL
);
543 assert(quvi
!= NULL
);
545 rc
= prep_util_script(quvi
, script_fname
, func_name
, &l
, &qls
);
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))
560 "%s: expected `%s' function to return a table",
561 qls
->path
, func_name
);
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
;
578 assert(video
!= NULL
);
580 assert(quvi
!= NULL
);
582 rc
= prep_util_script(quvi
, script_fname
, func_name
, &l
, &qls
);
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. */
600 "%s: expected `%s' function to return a string",
601 qls
->path
, func_name
);
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
;
621 assert(ident
!= NULL
);
622 assert(node
!= NULL
);
625 assert(quvi
!= NULL
); /* seterr macro uses this. */
631 qls
= (_quvi_lua_script_t
) node
->data
;
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));
645 lua_getglobal(l
, "ident");
647 if (!lua_isfunction(l
, -1))
649 freprintf(&quvi
->errmsg
, "%s: `ident' function not found",
654 script_dir
= dirname_from(qls
->path
);
657 set_field(l
, "page_url", ident
->url
);
658 set_field(l
, "script_dir", script_dir
);
662 if (lua_pcall(l
, 1, 1, 0))
664 freprintf(&quvi
->errmsg
, "%s", lua_tostring(l
, -1));
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
;
681 rc
= ident
->categories
& quvi
->
682 category
? QUVI_OK
: QUVI_NOSUPPORT
;
688 "%s: expected `ident' function to return a table", qls
->path
);
696 /* Executes the host script's "parse" function. */
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
;
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
);
722 script_dir
= dirname_from(qls
->path
);
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
);
733 if (lua_pcall(l
, 1, 1, 0))
735 freprintf(&quvi
->errmsg
, "%s", lua_tostring(l
, -1));
739 if (!lua_istable(l
, -1))
741 freprintf(&quvi
->errmsg
,
742 "expected `%s' function return a table", func_name
);
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
);
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
);
778 /* Match host script to the url. */
780 static llst_node_t
find_host_script_node(_quvi_video_t video
,
788 quvi
= video
->quvi
; /* seterr macro uses this. */
789 curr
= quvi
->website_scripts
;
794 struct lua_ident_s ident
;
796 ident
.quvi
= video
->quvi
;
797 ident
.url
= video
->page_link
;
799 ident
.formats
= NULL
;
801 /* Check if script ident this url. */
802 *rc
= run_ident_func(&ident
, curr
);
805 _free(ident
.formats
);
809 /* Found a script. */
812 else if (*rc
!= QUVI_NOSUPPORT
)
814 /* A script error. Terminate with it. */
821 /* Trust that run_ident_func sets the rc. */
822 freprintf(&quvi
->errmsg
, "no support: %s", video
->page_link
);
827 /* Match host script to the url */
828 QUVIcode
find_host_script(_quvi_video_t video
)
831 find_host_script_node(video
, &rc
);
835 /* Match host script to the url and run parse func */
836 QUVIcode
find_host_script_and_parse(_quvi_video_t video
)
844 quvi
= video
->quvi
; /* seterr macro uses this. */
847 script
= find_host_script_node(video
, &rc
);
851 /* found a script. */
852 return (run_parse_func(l
, script
, video
));
855 /* vim: set ts=2 sw=2 tw=72 expandtab: */