0%

movian

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, &notifymsg))
    {
      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_lightfuncmodule 中的所有函数推进 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;
}

Ref

  1. An Introduction To The SQLite C/C++ Interface
  2. sqlite3用法详解
  3. sqlite3使用简介