SQL
SQL描述位于 movian/res/metadb
,C语言使用 metadb
进行交互,位于 src/metadata/metadb.c
,例如
-- Create artist picture table
CREATE TABLE artistpic (
artist_id INTEGER REFERENCES artist(id) ON DELETE CASCADE,
url TEXT,
width INTEGER,
height INTEGER,
UNIQUE (artist_id, url)
);
CREATE INDEX artistpic_artid_id_idx ON artistpic(artist_id);
CREATE INDEX artistpic_url_idx ON artistpic(url);
void
metadb_insert_artistpic(void *db, int64_t artist_id, const char *url,
int width, int height)
{
sqlite3_stmt *ins;
int rc;
rc = db_prepare(db, &ins,
"INSERT INTO artistpic "
"(artist_id, url, width, height) "
"VALUES "
"(?1, ?2, ?3, ?4)");
if(rc != SQLITE_OK)
return;
sqlite3_bind_int64(ins, 1, artist_id);
sqlite3_bind_text(ins, 2, url, -1, SQLITE_STATIC);
if(width) sqlite3_bind_int64(ins, 3, width);
if(height) sqlite3_bind_int64(ins, 4, height);
db_step(ins);
sqlite3_finalize(ins);
}
metadb
函数调用关系:
_plugin_store_init
-> main_init
-> metadb_init
-> db_upgrade_schema
需要关心的几个函数:
metadata_init
app_dataroot
app_plugin_installed_path
需要关心的路径信息 gconf.persistent_path
,由函数 posix_init
进行初始化
//const char *homedir = "/media/sda1/movian";//getenv("HOME");
const char *homedir = "/tmp/plugin";//getenv("HOME");
if(homedir != NULL)
{
char buf[PATH_MAX];
if(gconf.cache_path == NULL)
{
snprintf(buf, sizeof(buf), "%s/.cache/%s", homedir, APPNAME);
gconf.cache_path = strdup(buf);
}
if(gconf.persistent_path == NULL)
{
snprintf(buf, sizeof(buf), "%s/.hts/showtime", homedir);
gconf.persistent_path = strdup(buf);
}
}
metadb_init
-> db_upgrade_schema
-> fa_scandir
载入搜索的 sql files
snprintf(buf, sizeof(buf), "%s/metadb", gconf.persistent_path);
fa_makedir(buf);
snprintf(buf, sizeof(buf), "%s/metadb/meta.db", gconf.persistent_path);
// unlink(buf);
metadb_pool = db_pool_create(buf, 2);
db = metadb_get();
if(db == NULL)
return;
snprintf(buf, sizeof(buf), "%s/res/metadb", app_dataroot());
snprintf(buf2, sizeof(buf2), "%s/kvstore/kvstore.db", gconf.persistent_path);
int r = db_upgrade_schema(db, buf, "metadb", "kvstore", buf2);
while(1) {
if(ver == tgtver) {
TRACE(TRACE_DEBUG, "DB", "%s: At current version %d", dbname, ver);
if(detach[0]) db_one_statement(db, detach, NULL);
return 0;
}
ver++;
snprintf(path, sizeof(path), "%s/%03d.sql", schemadir, ver);
buf_t *sql = fa_load(path,
FA_LOAD_ERRBUF(buf, sizeof(buf)),
NULL);
if(sql == NULL) {
TRACE(TRACE_ERROR, "DB",
"%s: Unable to upgrade db schema to version %d using %s -- %s",
dbname, ver, path, buf);
if(detach[0]) db_one_statement(db, detach, NULL);
return -1;
}
if(strstr(buf_cstr(sql), "-- schema-upgrade:disable-fk")) {
db_one_statement(db, "PRAGMA foreign_keys=OFF;", NULL);
enable_fk = 1;
}
db_begin(db);
snprintf(buf, sizeof(buf), "PRAGMA user_version=%d", ver);
if(db_one_statement(db, buf, NULL)) {
free(sql);
break;
}
const char *s = buf_cstr(sql);
while(strchr(s, ';') != NULL) {
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(db, s, -1, &stmt, &s);
if(rc != SQLITE_OK) {
TRACE(TRACE_ERROR, "DB",
"%s: Unable to prepare statement in upgrade %d\n%s", dbname, ver, s);
goto fail;
}
rc = sqlite3_step(stmt);
if(rc != SQLITE_DONE) {
TRACE(TRACE_ERROR, "DB",
"%s: Unable to execute statement error %d\n%s", dbname, rc,
sqlite3_sql(stmt));
goto fail;
}
sqlite3_finalize(stmt);
}
db_commit(db);
if(enable_fk) {
db_one_statement(db, "PRAGMA foreign_keys=ON;", NULL);
enable_fk = 0;
}
TRACE(TRACE_INFO, "DB", "%s: Upgraded to version %d", dbname, ver);
buf_release(sql);
}
001.sql
载入流程,通过以上 while
循环执行了所有的 sql
脚本
前置操作 metadb_get
-> db_pool_get
-> db_open
-> sqlite3_open_v2
得到 sqlite3 *db
POSIX
创建进程函数
hts_thread_create_detached(const char *title, void *(*func)(void *), void *aux, int prio)
hts_thread_create_detached("metadata", metadata_thread, NULL, THREAD_PRIO_METADATA);
BACKEND
src/backend/backend.c
其中注册函数 BE_REGISTER(videoparams);
插件载入过程
main_init
-> plugins_init
-> plugin_load
-> ecmascript_plugin_load
void plugins_init(char **devplugs)
{
plugins_view_settings_init();
hts_mutex_init(&plugin_mutex);
plugins_setup_root_props();
hts_mutex_lock(&plugin_mutex);
if (devplugs != NULL)
{
const char *path;
for(; (path = *devplugs) != NULL; devplugs++)
{
char errbuf[200];
char buf[PATH_MAX];
if(!fa_normalize(path, buf, sizeof(buf)))
path = buf;
strvec_addp(&devplugins, path);
if (plugin_load(path, errbuf, sizeof(errbuf), PLUGIN_LOAD_FORCE | PLUGIN_LOAD_DEBUG))
{
TRACE(TRACE_ERROR, "plugins", "Unable to load development plugin: %s\n%s", path, errbuf);
}
else
{
TRACE(TRACE_INFO, "plugins", "Loaded dev plugin %s", path);
}
}
}
hts_mutex_unlock(&plugin_mutex);
}
plugin_load
使用的标志为 PLUGIN_LOAD_FORCE | PLUGIN_LOAD_DEBUG
入口
static void _process_server(void)
{
int size;
int ret;
char buf[COMMAND_BUF_ZISE];
struct ClentCommand command;
printf("server start\n");
_plugin_store_init();
while(1)
{
size = read(fd_read, buf, COMMAND_BUF_ZISE);
if(size <= 0)
continue;
if(size != COMMAND_BUF_ZISE)
{
break;
}
memcpy(&command, buf, size);
switch(command.type)
{
case COMMAND_PLUGIN_INSTALL:
{
_plugin_install(command.data);
command.type = COMMAND_REPLY;
write(fd_write, &command, COMMAND_BUF_ZISE);
break;
}
case COMMAND_PLUGIN_UNINSTALL:
{
_plugin_uninstall(command.data);
break;
}
case COMMAND_CLASSIFICATION_LIST_GET:
{
ret = _classification_list_get(command.data);
command.type = COMMAND_REPLY;
command.err = ret;
write(fd_write, &command, COMMAND_BUF_ZISE);
break;
}
case COMMAND_CLASSIFICATION_LIST_REFRESH:
{
ret = _classification_list_refresh(command.index);
command.type = COMMAND_REPLY;
command.err = ret;
write(fd_write, &command, COMMAND_BUF_ZISE);
break;
}
case COMMAND_SOURCE_LIST_GET:
{
ret = _source_list_get(command.index);
command.type = COMMAND_REPLY;
command.err = ret;
write(fd_write, &command, COMMAND_BUF_ZISE);
break;
}
case COMMAND_SOURCE_LIST_REFRESH:
{
ret = _source_list_refresh(command.index);
command.type = COMMAND_REPLY;
command.err = ret;
write(fd_write, &command, COMMAND_BUF_ZISE);
break;
}
case COMMAND_SOURCE_URL_GET:
{
ret = _source_url_get(command.index);
command.type = COMMAND_REPLY;
command.err = ret;
write(fd_write, &command, COMMAND_BUF_ZISE);
break;
}
case COMMAND_SOURCE_URL_REFRESH:
{
_source_url_refresh();
break;
}
case COMMAND_SOURCE_URL_CLOSE:
{
ret = _source_url_refresh_close();
command.type = COMMAND_REPLY;
command.err = ret;
write(fd_write, &command, COMMAND_BUF_ZISE);
}
}
usleep(10000);
}
_plugin_store_exit();
}
install
_plugin_install(const char *name)
-> plugins_init3
-> plugin_load
void plugins_init3(char *name)
{
char errbuf[200];
char path[512];
char *tmp_name;
tmp_name = name;
while(*(++tmp_name) != '\0');
if(strncmp(tmp_name - 4, ".zip", 4) == 0)
snprintf(path, sizeof(path), "zip://file://%s/%s", app_plugin_installed_path(), name);
else
snprintf(path, sizeof(path), "%s/%s", app_plugin_installed_path(), name);
hts_mutex_lock(&plugin_mutex);
if(plugin_load(path, errbuf, sizeof(errbuf), PLUGIN_LOAD_AS_INSTALLED))
{
TRACE(TRACE_ERROR, "plugins", "Unable to load %s\n", errbuf);
}
hts_mutex_unlock(&plugin_mutex);
}
plugin_load 分析
//plugin.json
{
"type": "ecmascript",
"id": "stalker",
"file": "stalker.js",
"category": "tv",
"showtimeVersion": "0.0.1",
"version": "0.0.1",
"author": "LuckXiang (@luckxiang)",
"title": "Stalker",
"icon": "stalker.bmp",
"synopsis": "stalker for test",
"description":"<p>just for test stalker api.</p>",
"homepage":"https://github.com/luckxiang",
"setting":{
"url": "",
"mac": "00:1A:79:f5:75:d2"
}
}
static int plugin_load(const char *url, char *errbuf, size_t errlen, int flags)
{
char ctrlfile[URL_MAX];
char errbuf2[1024];
buf_t *b;
htsmsg_t *ctrl;
//url: zip://file:///home/gx/plugin_env/plugins/stalker.zip
snprintf(ctrlfile, sizeof(ctrlfile), "%s/plugin.json", url);
if((b = fa_load(ctrlfile, FA_LOAD_ERRBUF(errbuf2, sizeof(errbuf2)), NULL)) == NULL)
{
snprintf(errbuf, errlen, "Unable to load %s -- %s", ctrlfile, errbuf2);
return -1;
}
//将内存文件转为htsmsg_t结构体
ctrl = htsmsg_json_deserialize2(buf_cstr(b), errbuf, errlen);
if(ctrl == NULL) goto bad;
//ecmascript
const char *type = htsmsg_get_str(ctrl, "type");
//stalker
const char *id = htsmsg_get_str(ctrl, "id");
//0.0.1
const char *version = htsmsg_get_str(ctrl, "version");
if(type == NULL)
{
snprintf(errbuf, errlen, "Missing \"type\" element in control file %s", ctrlfile);
goto bad;
}
if(id == NULL)
{
snprintf(errbuf, errlen, "Missing \"id\" element in control file %s", ctrlfile);
goto bad;
}
//根据id查找plugin_t
plugin_t *pl = plugin_find(id, 1);
if(version != NULL)
{
rstr_t *notifymsg;
if(is_plugin_blacklisted(id, version, ¬ifymsg))
{
const char *title = htsmsg_get_str(ctrl, "title") ?: id;
char tmp[512];
rstr_t *fmt = _("Plugin %s has been uninstalled - %s");
snprintf(tmp, sizeof(tmp), rstr_get(fmt), title, rstr_get(notifymsg));
rstr_release(notifymsg);
notify_add(NULL, NOTIFY_ERROR, NULL, 10, rstr_alloc(tmp));
plugin_remove(pl);
goto bad;
}
}
if(!(flags & PLUGIN_LOAD_FORCE) && pl->pl_loaded)
{
snprintf(errbuf, errlen, "Plugin \"%s\" already loaded", id);
goto bad;
}
plugin_unload(pl);
int r;
char fullpath[URL_MAX];
if(!strcmp(type, "views"))
{
// No special tricks here, we always loads 'glwviews' from all plugins
r = 0;
}
else if(!strcmp(type, "ecmascript"))
{
// stalker.js
const char *file = htsmsg_get_str(ctrl, "file");
if(file == NULL)
{
snprintf(errbuf, errlen, "Missing \"file\" element in control file %s", ctrlfile);
goto bad;
}
snprintf(fullpath, sizeof(fullpath), "%s/%s", url, file);
int version = htsmsg_get_u32_or_default(ctrl, "apiversion", 1);
int pflags = 0;
if(htsmsg_get_u32_or_default(ctrl, "debug", 0) || flags & PLUGIN_LOAD_DEBUG)
pflags |= ECMASCRIPT_DEBUG;
htsmsg_t *e = htsmsg_get_map(ctrl, "entitlements");
if(e != NULL)
{
if(htsmsg_get_u32_or_default(e, "bypassFileACLRead", 0))
pflags |= ECMASCRIPT_FILE_BYPASS_ACL_READ;
if(htsmsg_get_u32_or_default(e, "bypassFileACLWrite", 0))
pflags |= ECMASCRIPT_FILE_BYPASS_ACL_WRITE;
}
// 载入plugin
hts_mutex_unlock(&plugin_mutex);
r = ecmascript_plugin_load(id, fullpath, errbuf, errlen, version, buf_cstr(b), pflags);
hts_mutex_lock(&plugin_mutex);
if(!r)
pl->pl_unload = plugin_unload_ecmascript;
}
else
{
if(flags & PLUGIN_LOAD_BY_USER)
{
snprintf(errbuf, errlen, "Unknown type \"%s\" in control file %s", type, ctrlfile);
goto bad;
}
else
{
TRACE(TRACE_ERROR, "Plugin",
"Installed plugin at %s has unknown type %s but keeping anyway, "
"since it might be upgraded",
ctrlfile, type);
r = 0;
}
}
// Load bundled views
if(!r)
{
htsmsg_t *list = htsmsg_get_list(ctrl, "glwviews");
if(list != NULL) {
htsmsg_field_t *f;
HTSMSG_FOREACH(f, list) {
htsmsg_t *o;
if((o = htsmsg_get_map_by_field(f)) == NULL)
continue;
const char *uit = htsmsg_get_str(o, "uitype") ?: "standard";
const char *class = htsmsg_get_str(o, "class");
const char *title = htsmsg_get_str(o, "title");
const char *file = htsmsg_get_str(o, "file");
if(class == NULL || title == NULL || file == NULL)
continue;
snprintf(fullpath, sizeof(fullpath), "%s/%s", url, file);
int dosel =
htsmsg_get_u32_or_default(o, "select",
!!(flags & PLUGIN_LOAD_BY_USER));
plugins_view_add(pl, uit, class, title, fullpath, dosel, file);
}
}
}
if(!r)
{
if(flags & PLUGIN_LOAD_AS_INSTALLED)
{
plugin_prop_setup(ctrl, pl, url);
pl->pl_installed = 1;
mystrset(&pl->pl_inst_ver, htsmsg_get_str(ctrl, "version"));
}
mystrset(&pl->pl_title, htsmsg_get_str(ctrl, "title") ?: id);
pl->pl_loaded = 1;
}
buf_release(b);
htsmsg_release(ctrl);
update_state(pl);
return 0;
bad:
buf_release(b);
htsmsg_release(ctrl);
return -1;
}
es modules
movian
使用 duktape
javascript引擎,es modules加载流程
目录src/ecmascript
src/ecmascript
├── ecmascript.c
├── ecmascript.h
├── es_console.c
├── es_crypto.c
├── es_faprovider.c
├── es_fs.c
├── es_gumbo.c
├── es_hook.c
├── es_htsmsg.c
├── es_io.c
├── es_kvstore.c
├── es_metadata.c
├── es_misc.c
├── es_native_obj.c
├── es_prop.c
├── es_root.c
├── es_route.c
├── es_scrobble.c
├── es_searcher.c
├── es_service.c
├── es_sqlite.c
├── es_stats.c
├── es_string.c
├── es_subtitles.c
├── es_timer.c
└── es_websocket.c
文件 es_fs.c
static const duk_function_list_entry fnlist_fs[] = {
{ "open", es_file_open, 3 },
{ "read", es_file_read, 5 },
{ "write", es_file_write, 5 },
{ "fsize", es_file_fsize, 1 },
{ "ftrunctae", es_file_ftruncate, 2 },
{ "rename", es_file_rename, 2 },
{ "mkdirs", es_file_mkdirs, 2 },
{ "dirname", es_file_dirname, 1 },
{ "basename", es_file_basename, 1 },
{ "copyfile", es_file_copy, 2 },
{ NULL, NULL, 0}
};
ES_MODULE("fs", fnlist_fs);
文件 es_sqlite.c
static const duk_function_list_entry fnlist_sqlite[] =
{
{ "create", es_sqlite_create, 1 },
{ "query", es_sqlite_query, DUK_VARARGS },
{ "changes", es_db_changes, 1 },
{ "step", es_sqlite_step, 1 },
{ "lastErrorCode", es_db_last_error_code, 1 },
{ "lastErrorString", es_db_last_error_str, 1 },
{ "lastRowId", es_db_last_insert_row_id, 1 },
{ "upgradeSchema", es_db_upgrade_schema, 2 },
{ NULL, NULL, 0}
};
ES_MODULE("sqlite", fnlist_sqlite);
ES_MODULE
分析
#define INITIALIZER(f) \
static void f(void) __attribute__((constructor)); \
static void f(void)
#define HTS_GLUE(a, b) a ## b
#define HTS_JOIN(a, b) HTS_GLUE(a, b)
typedef struct ecmascript_module
{
LIST_ENTRY(ecmascript_module) link;
const char *name;
const duk_function_list_entry *functions;
} ecmascript_module_t;
void ecmascript_register_module(ecmascript_module_t *m);
#define ES_MODULE(nam, fn) \
static ecmascript_module_t HTS_JOIN(esmoduledef, __LINE__) = { \
.name = nam, \
.functions = fn \
}; \
INITIALIZER(HTS_JOIN(esmoduledefinit, __LINE__)) \
{ ecmascript_register_module(&HTS_JOIN(esmoduledef, __LINE__));}
void ecmascript_register_module(ecmascript_module_t *m)
{
LIST_INSERT_HEAD(&modules, m, link);
}
通过 __attribute__((constructor))
将 module
添加到 LIST modules
中,进而在函数 es_modsearch
/* for native modules */
const char *nativemod = mystrbegins(id, "native/");
if(nativemod != NULL)
{
ecmascript_module_t *m;
LIST_FOREACH(m, &modules, link)
{
if(!strcmp(m->name, nativemod))
break;
}
if(m == NULL)
duk_error(ctx, DUK_ERR_ERROR, "Can't find native module %s", id);
for(int i = 0; m->functions[i].key != NULL; i++)
{
duk_push_c_lightfunc(ctx, m->functions[i].value,
m->functions[i].nargs, 0, 0);
duk_put_prop_string(ctx, 2, m->functions[i].key);
}
return 0;
}
通过 duk_push_c_lightfunc
将 module
中的所有函数推进 duktape stack
prop 系统
main_init
-> service_init
创建 prop services
void service_init(void)
{
struct prop_nf *pnf;
prop_t *gs = prop_create(prop_get_global(), "services");
hts_mutex_init(&service_mutex);
hts_cond_init(&service_cond, &service_mutex);
hts_thread_create_detached("service probe", service_probe_loop, NULL, THREAD_PRIO_BGTASK);
// $global.service.all
all_services = prop_create(gs, "all");
#if ENABLE_PLUGINS
service_create0("showtime:plugin",
NULL, _p("Plugins"), "plugin:start",
"plugin", NULL, 0, 1, SVC_ORIGIN_SYSTEM);
#endif
service_create0("showtime:discovered",
NULL, _p("Local network"), "discovered:",
"network", NULL, 0, 1, SVC_ORIGIN_SYSTEM);
service_create0("showtime:settings",
NULL, _p("Settings"), "settings:",
"setting", NULL, 0, 1, SVC_ORIGIN_SYSTEM);
// $global.service.enabled
prop_t *enabled = prop_create(gs, "enabled");
pnf = prop_nf_create(enabled, all_services, NULL, 0);
prop_nf_pred_int_add(pnf, "node.enabled",
PROP_NF_CMP_EQ, 0, NULL,
PROP_NF_MODE_EXCLUDE);
// $global.service.stable
prop_t *tmp = prop_create_root(NULL);
pnf = prop_nf_create(tmp, all_services, NULL, 0);
prop_nf_pred_int_add(pnf, "node.enabled",
PROP_NF_CMP_EQ, 0, NULL,
PROP_NF_MODE_EXCLUDE);
prop_t *stable = prop_create(gs, "stable");
prop_reorder_create(stable, tmp, 0, "allSourcesOrder");
// $global.service.discovered
discovered_nodes = prop_create(gs, "discovered");
pnf = prop_nf_create(discovered_nodes, all_services, NULL, 0);
prop_nf_pred_str_add(pnf, "node.origin",
PROP_NF_CMP_NEQ, "discovered", NULL,
PROP_NF_MODE_EXCLUDE);
}
生产数据
消费数据
int _classification_list_get(char *id)
{
event_openurl_t event;
char plugin[128];
struct prop *p;
snprintf(plugin, 128, "plugin:%s", id);
p = prop_find(prop_get_global(), "services", "all", plugin, 0);
if(p == NULL)
return -1;
printf("id:%s,%p\n", plugin, p);
memset(&event, 0, sizeof(event));
event.url = rstr_get(prop_get_string(p, "url", 0));
event.h.e_type = EVENT_OPENURL;
nav_eventsink(nav, &event);
return 0;
}