0%

movian-plugin-somafm

分析 Movian 插件 somafm

native

fs.js 中对文件的操作是通过 var fs = require('native/fs'); 来实现的,而 native 通过 duktape 来实现

目录

movian > ll src/ecmascript/es_*
1.9K 10 月 13 15:20 src/ecmascript/es_console.c
3.5K 10 月 13 15:20 src/ecmascript/es_crypto.c
 15K 10 月 13 15:20 src/ecmascript/es_faprovider.c
8.0K 10 月 13 15:20 src/ecmascript/es_fs.c
9.7K 10 月 13 15:20 src/ecmascript/es_gumbo.c
3.3K 10 月 13 15:20 src/ecmascript/es_hook.c
4.5K 10 月 13 15:20 src/ecmascript/es_htsmsg.c
 21K 10 月 13 15:20 src/ecmascript/es_io.c
3.3K 10 月 13 15:20 src/ecmascript/es_kvstore.c
3.0K 10 月 13 15:20 src/ecmascript/es_metadata.c
7.1K 10 月 13 15:20 src/ecmascript/es_misc.c
3.6K 10 月 13 15:20 src/ecmascript/es_native_obj.c
 24K 10 月 13 15:20 src/ecmascript/es_prop.c
1.9K 10 月 13 15:20 src/ecmascript/es_root.c
5.4K 10 月 13 15:20 src/ecmascript/es_route.c
2.3K 10 月 13 15:20 src/ecmascript/es_scrobble.c
1.4K 10 月 13 15:20 src/ecmascript/es_searcher.c
3.9K 10 月 13 15:20 src/ecmascript/es_service.c
7.3K 10 月 13 15:20 src/ecmascript/es_sqlite.c
3.8K 10 月 13 15:20 src/ecmascript/es_stats.c
8.2K 10 月 13 15:20 src/ecmascript/es_string.c
6.2K 10 月 13 15:20 src/ecmascript/es_subtitles.c
4.9K 10 月 13 15:20 src/ecmascript/es_timer.c
 24K 10 月 13 15:20 src/ecmascript/es_websocket.c

例如 es_fs.c 实现模块 native/fs

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);

Link es_modsearch

Duktape

The Duktape object

  • Property Description
  • version Duktape version number: (major * 10000) + (minor * 100) + patch.
  • env Cryptic, version dependent summary of most important effective options like endianness and architecture.
  • fin Set or get finalizer of an object.
  • enc Encode a value (hex, base-64, JX, JC): Duktape.enc(‘hex’, ‘foo’).
  • dec Decode a value (hex, base-64, JX, JC): Duktape.dec(‘base64’, ‘Zm9v’).
  • info Get internal information (such as heap address and alloc size) of a value in a version specific format. The C API equivalent is duk_inspect_value().
  • act Get information about call stack entry.
  • gc Trigger mark-and-sweep garbage collection.
  • compact Compact the memory allocated for a value (object).
  • errCreate Callback to modify/replace a created error.
  • errThrow Callback to modify/replace an error about to be thrown.
  • Pointer Pointer constructor (function).
  • Thread Thread constructor (function).

static void es_create_env(es_context_t *ec, const char *loaddir, const char *storage)

if(storage != NULL) {
  duk_push_string(ctx, storage);
  duk_put_prop_string(ctx, obj_idx, "storagePath");
}

duk_put_function_list(ctx, obj_idx, fnlist_core);
duk_put_prop_string(ctx, -2, "Core");

// Initialize modSearch helper

duk_get_prop_string(ctx, -1, "Duktape");
duk_push_c_function(ctx, es_modsearch, 4);
duk_put_prop_string(ctx, -2, "modSearch");
duk_pop(ctx);

duk_put_function_list(ctx, -1, es_fnlist_timer);

duk_push_object(ctx);
duk_put_function_list(ctx, -1, es_fnlist_console);
duk_put_prop_string(ctx, -2, "console");

api-v1.js

var prop = require('movian/prop');

var cryptodigest = function(algo, str) {
  var crypto = require('native/crypto');
  var hash = crypto.hashCreate(algo);
  crypto.hashUpdate(hash, str);
  var digest = crypto.hashFinalize(hash);
  return Duktape.enc('hex', digest);
}

var misc = require('native/misc');
var string = require('native/string');
var popup = require('native/popup');

showtime

showtime = {

  // 打印
  print: print,

  // JSON 解码与编码
  JSONDecode: JSON.parse,
  JSONEncode: JSON.stringify,

  // http Get from URL
  httpGet: function(url, args, headers, ctrl) {

    var c = {
      args: args,
      headers: headers
    };

    for(var p in ctrl)
      c[p] = ctrl[p];

    return require('movian/http').request(url, c);
  },

  // Core object from Fun:es_create_env
  currentVersionInt: Core.currentVersionInt,
  currentVersionString: Core.currentVersionString,
  deviceId: Core.deviceId,

  httpReq: function(url, ctrl, cb) {
    return require('movian/http').request(url, ctrl, cb);
  },

  entityDecode: string.entityDecode,
  queryStringSplit: string.queryStringSplit,
  pathEscape: string.pathEscape,
  paramEscape: string.paramEscape,
  durationToString: string.durationToString,

  message: popup.message,
  textDialog: popup.textDialog,
  notify: popup.notify,

  probe: require('native/io').probe,

  print: print,
  trace: console.log,
  basename: require('native/fs').basename,

  // sha1
  sha1digest: function(str) {
    return cryptodigest('sha1', str);
  },

  // md5
  md5digest: function(str) {
    return cryptodigest('md5', str);
  },

  RichText: function(x) {
    this.str = x.toString();
  },

  systemIpAddress: function() {
      return misc.systemIpAddress();
  },

  getSubtitleLanguages: require('native/subtitle').getLanguages,

  xmlrpc: function() {
    var a = [];
    for(var i = 2; i < arguments.length; i++)
      a.push(arguments[i]);
    var json = JSON.stringify(a);
    var x = require('native/io').xmlrpc(arguments[0], arguments[1], json);
    return require('movian/xml').htsmsg(x);
  },

  sleep: function(x) {
      return Core.sleep(x);
  }
};

plugin

var plugin = {

  // Fun: es_service_create
  createService: function(title, url, type, enabled, icon) {
    return require('movian/service').create(title, url, type, enabled, icon);
  },

  // Fun: es_file_mkdirs
  createStore: function(name) {
    return require('movian/store').create(name);
  },

  // page.js Route Fun: es_route_create
  addURI: function(re, callback) {
    var page = require('movian/page');
    return new page.Route(re, callback);
  },

  // page.js Searcher Fun: es_hook_register
  addSearcher: function(title, icon, cb) {
    var page = require('movian/page');
    return new page.Searcher(title, icon,cb);
  },

  // Plugin object from Fun: ecmascript_plugin_load
  path: Plugin.path,

  // parse JSON from Plugin.manifest
  getDescriptor: function() {
    if(this.descriptor === undefined)
      this.descriptor = JSON.parse(Plugin.manifest);

    return this.descriptor;
  },

  //popup object Fun: es_getAuthCredentials [fnlist_popup] file: es_misc.c
  getAuthCredentials: popup.getAuthCredentials,

  // Fun: es_http_inspector_create file: es_io.c
  addHTTPAuth: require('native/io').httpInspectorCreate,

  // Fun: es_file_copy file: es_fs.c
  copyFile: require('native/fs').copyfile,

  // Fun: es_select_view file: es_misc.c
  selectView: misc.selectView,

  // settings.js
  createSettings: function(title, icon, description) {
    var settings = require('movian/settings');
    return new settings.globalSettings(Plugin.id, title, icon, description);
  },

  // Fun: es_cachePut
  cachePut: function(stash, key, obj, maxage) {
    misc.cachePut('plugin/' + Plugin.id + '/' + stash,
                      key, JSON.stringify(obj), maxage);
  },

  // Fun: es_cacheGet
  cacheGet: function(stash, key) {
    var v = misc.cacheGet('plugin/' + Plugin.id + '/' + stash, key);
    return v ? JSON.parse(v) : null;
  },

  config: {},

  properties: prop.global.plugin[Plugin.id],

  // itemhook.js
  addItemHook: function(conf) {
    require('movian/itemhook').create(conf);
  },

  // Fun: es_subtitleprovideradd
  addSubtitleProvider: function(fn) {
    require('native/subtitle').addProvider(function(root, query, basescore, autosel) {
      var req = Object.create(query);
      req.addSubtitle = function(url, title, language, format,
                                 source, score) {
        require('native/subtitle').addItem(root, url, title, language, format, source,
                                 basescore + score, autosel);
      }
      fn(req);
    }, Plugin.id, Plugin.id);
  }

};

// This is the return value
plugin;

somafm.js

可以使用 showtime.print(Plugin.manifest);shell 输出信息

(function(plugin) {
    var BASE_URL = "http://www.somafm.com";
    var logo = plugin.path + "somafm.png";

    // Create Service for Plugin somafm
    plugin.createService(plugin.getDescriptor().title, plugin.getDescriptor().id + ":start", "music", true, logo);

    function descr(s) {
        var tmp = s.match(/<p class="descr">([\S\s]*?)<dl>/);
        if (tmp) return tmp[1].replace("<!--","").replace("-->","").replace("</p>","").replace(/^\s+|\s+$/g, '');
        tmp = s.match(/<h1>([\S\s]*?)<\/h1>/);
        if (tmp) return tmp[1];
        return null;
    }

    // Start page
    // URI: [soamfm:start]
    plugin.addURI(plugin.getDescriptor().id + ":start", function(page) {
    page.type = "directory";
    page.metadata.title = plugin.getDescriptor().title;
    page.metadata.logo = logo;
        page.loading = true;

        if (showtime.currentVersionInt < 49900000) {
           page.metadata.glwview = plugin.path + 'views/array.view';
        page.contents = 'items';
            page.options.createInt('childTilesX', 'Tiles by X', 6, 1, 10, 1, '', function(v) {
                page.metadata.childTilesX = v;
            }, true);

            page.options.createInt('childTilesY', 'Tiles by Y', 2, 1, 4, 1, '', function(v) {
                page.metadata.childTilesY = v;
            }, true);

            page.options.createBool('informationBar', 'Information Bar', 1, function(v) {
                page.metadata.informationBar = v;
            }, true);
        } else
            page.model.contents = 'grid';

        // 请求网页数据 http://somafm.com/listen/
        var doc = showtime.httpReq(BASE_URL + "/listen").toString();

        // re 筛选数据
        // 1-id, 2-listeners, 3-icon, 4-title, 5-(description/now playing)
        var re = /<!-- Channel: (.*) Listeners: (.*) -->[\S\s]*?<img src="([\S\s]*?)"[\S\s]*?<h3>([\S\s]*?)<\/h3>([\S\s]*?)<\/li>/g;
        var match = re.exec(doc);
        while (match) {
        page.appendItem("icecast:" + BASE_URL + "/startstream=" + match[1] + ".pls", "station", {
            station: match[4],
            title: match[4],
            description: descr(match[5]),
            icon: BASE_URL + match[3],
                album_art: BASE_URL + match[3],
                nowplaying: (match[5].match(/<span class="playing"><a href="[\S\s]*?">([\S\s]*?)<\/a>/) ? match[5].match(/<span class="playing"><a href="[\S\s]*?">([\S\s]*?)<\/a>/)[1] : null),
            listeners: match[2]
        });
            match = re.exec(doc);
        };
    page.loading = false;
    });
})(this);