Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scripting: print2 function with log level support #2966

Merged
merged 1 commit into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 27 additions & 15 deletions doc/scripting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ This section will give you some overview on how virtual objects work and on how

.. Note::

In order to use the import scripting feature you have to change the layout type from builtin to js in ``config.xml``
In order to use the import scripting feature you have to change the attribute ``<virtual-layout type=..`` from ``builtin`` to ``js`` in ``config.xml``

.. Note::

Expand Down Expand Up @@ -309,10 +309,10 @@ transcoding are not available. The autoscan entry corresponding to the active ob

.. code-block:: js

print(config['/server/name']);
print(config['/import/library-options/id3/auxdata/add-data'][0]);
print(config['/import/layout/path']['Directories']);
print(config['/import/autoscan/directory'][object_autoscan_id].location);
print2("Info", config['/server/name']);
print2("Error", "Empty setting", config['/import/library-options/id3/auxdata/add-data'][0]);
print2("Warning", "Active mapping for", config['/import/layout/path']['Directories']);
print2("Debug", "Checking", config['/import/autoscan/directory'][object_autoscan_id].location);


Constants
Expand Down Expand Up @@ -427,38 +427,50 @@ within the import and/or the playlist script:

.. js:function:: print(...)

This function is useful for debugging scripts, it simply
prints to the standard output.
This function is useful for debugging scripts, it simply prints an info message in the log.
It it recommended to use `print2` instead to set the message level. The function may be removed in the future.

.. js:function:: f2i(string)
.. js:function:: print2(level, ...)

This function is useful for debugging scripts, it prints a message in the log with the set level.

:param string level: Set the output message level.
The following case insensitive values for ``level`` are allowed:

#) ``error``: Print an error message
#) ``warning``: Print a warning message
#) ``info``: Print an info message
#) ``debug``: Print a debug message, which is only visible if running with `--debug` or setting `debug-mode="script"` in `server` section of the config file.

.. js:function:: f2i(text)

Converts filesystem charset to internal UTF-8.

:param string string:
:param string text:

`The 'from' charsets can be defined in the server configuration`

.. js:function:: m2i(string)
.. js:function:: m2i(text)

Converts metadata charset to internal UTF-8.

:param string string:
:param string text:

`The 'from' charsets can be defined in the server configuration`

.. js:function:: p2i(string)
.. js:function:: p2i(text)

Converts playlist charset to internal UTF-8.

:param string string:
:param string text:

`The 'from' charsets can be defined in the server configuration`

.. js:function:: j2i(string)
.. js:function:: j2i(text)

Converts js charset to internal UTF-8.

:param string string:
:param string text:

`The 'from' charsets can be defined in the server configuration`

Expand Down
6 changes: 3 additions & 3 deletions scripts/js/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ function abcbox(stringtobox, boxtype, divchar) {
charttl = charttl + boxwidth[cb];
}
if (charttl != 26) {
print("Error in box-definition, length is " + charttl + ". Check the file common.js" );
print2("Error", "In box-definition, total length is " + charttl + ". Check the file common.js" );
// maybe an exit call here to stop processing the media ??
return "???";
}
Expand Down Expand Up @@ -1294,7 +1294,7 @@ function addPlaylistItem(playlist_title, playlistLocation, entry, playlistChain,

var cds = getCdsObject(entry.location);
if (!cds) {
print("Playlist " + playlist_title + " Skipping entry: " + entry.location);
print2("Warning", "Playlist '" + playlist_title + "' Skipping unknown entry: " + entry.location);
return false;
}

Expand All @@ -1307,7 +1307,7 @@ function addPlaylistItem(playlist_title, playlistLocation, entry, playlistChain,

item.extra = entry.extra;
item.writeThrough = entry.writeThrough;
// print("Playlist " + item.title + " Adding entry: " + item.playlistOrder + " " + item.location);
print2("Debug", "Playlist '" + item.title + "' Adding entry: " + item.playlistOrder + " " + item.location);

addCdsObject(item, playlistChain);
}
Expand Down
6 changes: 3 additions & 3 deletions scripts/js/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ function importItem(item, cont) {
case "object.item.textItem":
case "object.item.bookmarkItem":
case "object.item.playlistItem":
print("Unable to handle upnp class '" + item.upnpclass + "' for " + obj.location);
print2("Error", "Unable to handle upnp class '" + item.upnpclass + "' for " + obj.location);
break;
default:
print("Unable to handle unknown upnp class '" + item.upnpclass + "' for " + obj.location);
print2("Warning", "Unable to handle unknown upnp class '" + item.upnpclass + "' for " + obj.location);
if (mime === 'video' && obj.onlineservice === ONLINE_SERVICE_APPLE_TRAILERS) {
mime = 'trailer';
} else if (item.mimetype === 'application/ogg') {
Expand All @@ -94,7 +94,7 @@ function importItem(item, cont) {
addImage(obj, cont, object_script_path, grb_container_type_image);
break;
default:
print("Unable to handle mime type '" + item.mimetype + "' for " + obj.location);
print2("Error", "Unable to handle mime type '" + item.mimetype + "' for " + obj.location);
break;
}
break;
Expand Down
2 changes: 1 addition & 1 deletion scripts/js/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

function importMetadata(meta, cont, rootPath, autoscanId, containerType) {
const arr = rootPath.split('.');
print("Processing metafile: " + rootPath + " for " + meta.location + " " + arr[arr.length-1].toLowerCase());
print2("Info", "Processing metafile: " + rootPath + " for " + meta.location + " " + arr[arr.length-1].toLowerCase());
switch (arr[arr.length-1].toLowerCase()) {
case "nfo":
parseNfo(meta, rootPath);
Expand Down
6 changes: 3 additions & 3 deletions scripts/js/playlists.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@

function importPlaylist(obj, cont, rootPath, autoscanId, containerType) {
if (!obj || obj === undefined) {
print("Playlist undefined");
print2("Error", "Playlist undefined");
}

print("Processing playlist: " + obj.location);
print2("Info", "Processing playlist: " + obj.location);

const objLocation = obj.location.substring(0, obj.location.lastIndexOf('/') + 1);
const type = getPlaylistType(obj.mimetype);
Expand Down Expand Up @@ -77,7 +77,7 @@ function importPlaylist(obj, cont, rootPath, autoscanId, containerType) {
}

if (type === '') {
print("Unknown playlist mimetype: '" + obj.mimetype + "' of playlist '" + obj.location + "'");
print2("Error", "Unknown playlist mimetype: '" + obj.mimetype + "' of playlist '" + obj.location + "'");
} else if (type === 'm3u') {
readM3uPlaylist(obj_title, objLocation, objChain, objDirChain);
} else if (type === 'pls') {
Expand Down
25 changes: 23 additions & 2 deletions src/content/scripting/js_functions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,34 @@

// extern "C" {

duk_ret_t js_print2(duk_context* ctx)
{
std::string mode = toUpper(duk_get_string(ctx, 0));
duk_push_string(ctx, " ");
duk_replace(ctx, 0);
duk_join(ctx, duk_get_top(ctx) - 1);
std::string message = duk_get_string(ctx, 0);
if (mode == "ERROR")
log_error("{}", message);
else if (mode == "WARNING")
log_warning("{}", message);
else if (mode == "DEBUG")
log_debug("{}", message);
else
log_info("{}", message);
duk_push_string(ctx, fmt::format("{}: {}", mode, message).c_str());
return 1;
}

duk_ret_t js_print(duk_context* ctx)
{
duk_push_string(ctx, " ");
duk_insert(ctx, 0);
duk_join(ctx, duk_get_top(ctx) - 1);
log_debug("{}", duk_get_string(ctx, 0));
return 0;
auto message = duk_get_string(ctx, 0);
log_info("{}", message);
duk_push_string(ctx, message);
return 1;
}

duk_ret_t js_copyObject(duk_context* ctx)
Expand Down
2 changes: 2 additions & 0 deletions src/content/scripting/js_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@

extern "C" {

/// \brief Log output with log level.
duk_ret_t js_print2(duk_context* ctx);
/// \brief Log output.
duk_ret_t js_print(duk_context* ctx);

Expand Down
7 changes: 4 additions & 3 deletions src/content/scripting/script.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
#endif

static constexpr auto jsGlobalFunctions = std::array {
duk_function_list_entry { "print2", js_print2, DUK_VARARGS },
duk_function_list_entry { "print", js_print, DUK_VARARGS },
duk_function_list_entry { "addCdsObject", js_addCdsObject, 3 },
duk_function_list_entry { "addContainerTree", js_addContainerTree, 1 },
Expand Down Expand Up @@ -337,15 +338,15 @@ Script::Script(const std::shared_ptr<ContentManager>& content, const std::string
std::string commonFdrPath = config->getOption(CFG_IMPORT_SCRIPTING_COMMON_FOLDER);

if (commonScrPath.empty() && commonFdrPath.empty()) {
log_js("Common script disabled in configuration");
log_warning("Common script disabled in configuration");
} else if (commonScrPath.empty()) {
loadFolder(commonFdrPath);
} else {
try {
_load(commonScrPath);
_execute();
} catch (const std::runtime_error& e) {
log_js("Unable to load {}: {}", commonScrPath, e.what());
log_error("Unable to load {}: {}", commonScrPath, e.what());
}
}
std::string customScrPath = config->getOption(CFG_IMPORT_SCRIPTING_CUSTOM_SCRIPT);
Expand All @@ -355,7 +356,7 @@ Script::Script(const std::shared_ptr<ContentManager>& content, const std::string
_load(customScrPath);
_execute();
} catch (const std::runtime_error& e) {
log_js("Unable to load {}: {}", customScrPath, e.what());
log_error("Unable to load {}: {}", customScrPath, e.what());
}
} else if (!customFdrPath.empty()) {
loadFolder(customFdrPath);
Expand Down
1 change: 0 additions & 1 deletion src/util/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
#define log_info SPDLOG_INFO
#define log_warning SPDLOG_WARN
#define log_error SPDLOG_ERROR
#define log_js SPDLOG_INFO

enum class log_facility_t {
thread,
Expand Down
2 changes: 2 additions & 0 deletions test/scripting/mock/common_script_mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class CommonScriptInterface {
virtual ~CommonScriptInterface() = default;
virtual duk_ret_t getPlaylistType(std::string type) = 0;
virtual duk_ret_t print(std::string text) = 0;
virtual duk_ret_t print2(std::string mode, std::string text) = 0;
virtual duk_ret_t addContainerTree(std::vector<std::string> tree) = 0;
virtual duk_ret_t createContainerChain(std::vector<std::string> chain) = 0;
virtual duk_ret_t getLastPath(std::string path) = 0;
Expand All @@ -52,6 +53,7 @@ class CommonScriptMock : public CommonScriptInterface {
public:
MOCK_METHOD1(getPlaylistType, duk_ret_t(std::string type));
MOCK_METHOD1(print, duk_ret_t(std::string text));
MOCK_METHOD2(print2, duk_ret_t(std::string mode, std::string text));
MOCK_METHOD1(addContainerTree, duk_ret_t(std::vector<std::string> tree));
MOCK_METHOD1(createContainerChain, duk_ret_t(std::vector<std::string> chain));
MOCK_METHOD1(getLastPath, duk_ret_t(std::string path));
Expand Down
9 changes: 9 additions & 0 deletions test/scripting/mock/script_test_fixture.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Gerbera - https://gerbera.io/
#include "duk_helper.h"
#include "script_test_fixture.h"

std::unique_ptr<CommonScriptMock> CommonScriptTestFixture::commonScriptMock;

void ScriptTestFixture::SetUp()
{
ctx = duk_create_heap(nullptr, nullptr, nullptr, nullptr, nullptr);
Expand Down Expand Up @@ -522,6 +524,13 @@ std::string ScriptTestFixture::getPlaylistType(duk_context* ctx)
return playlistMimeType;
}

std::tuple<std::string, std::string> ScriptTestFixture::print2(duk_context* ctx)
{
std::string mode = duk_to_string(ctx, 0);
std::string result = duk_to_string(ctx, 1);
return {mode, result};
}

std::string ScriptTestFixture::print(duk_context* ctx)
{
std::string result = duk_to_string(ctx, 0);
Expand Down
47 changes: 47 additions & 0 deletions test/scripting/mock/script_test_fixture.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <memory>
#include <tuple>

#include "util/string_converter.h"

Expand Down Expand Up @@ -132,6 +133,7 @@ class ScriptTestFixture : public ::testing::Test {
// Mimics the print of text
// Returns the string sent by the script to print
static std::string print(duk_context* ctx);
static std::tuple<std::string, std::string> print2(duk_context* ctx);

// Proxy the common.js script with `getYear`
// Mimics the parsing of YYYY
Expand Down Expand Up @@ -175,5 +177,50 @@ class ScriptTestFixture : public ::testing::Test {
duk_context* ctx;
};

class CommonScriptTestFixture : public ScriptTestFixture {
public:
// As Duktape requires static methods, so must the mock expectations be
static std::unique_ptr<CommonScriptMock> commonScriptMock;

CommonScriptTestFixture()
{
commonScriptMock = std::make_unique<::testing::NiceMock<CommonScriptMock>>();
}

~CommonScriptTestFixture() override
{
commonScriptMock.reset();
}

static inline duk_ret_t js_print(duk_context* ctx)
{
std::string msg = ScriptTestFixture::print(ctx);
return CommonScriptTestFixture::commonScriptMock->print(msg);
}

static inline duk_ret_t js_print2(duk_context* ctx)
{
auto [mode, msg] = ScriptTestFixture::print2(ctx);
return CommonScriptTestFixture::commonScriptMock->print2(mode, msg);
}

static inline duk_ret_t js_getPlaylistType(duk_context* ctx)
{
std::string playlistMimeType = ScriptTestFixture::getPlaylistType(ctx);
return CommonScriptTestFixture::commonScriptMock->getPlaylistType(playlistMimeType);
}

static inline duk_ret_t js_createContainerChain(duk_context* ctx)
{
std::vector<std::string> array = ScriptTestFixture::createContainerChain(ctx);
return CommonScriptTestFixture::commonScriptMock->createContainerChain(array);
}

static inline duk_ret_t js_getLastPath(duk_context* ctx)
{
std::string inputPath = ScriptTestFixture::getLastPath(ctx);
return CommonScriptTestFixture::commonScriptMock->getLastPath(inputPath);
}
};
#endif //GERBERA_SCRIPTTESTFIXTURE_H
#endif //HAVE_JS