NO-OP: whitespace
[ardour.git] / libs / ardour / luascripting.cc
1 /*
2  * Copyright (C) 2016-2019 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program 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
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 #include <cstring>
19 #include <glibmm.h>
20
21 #include "pbd/error.h"
22 #include "pbd/file_utils.h"
23 #include "pbd/compose.h"
24
25 #include "ardour/directory_names.h"
26 #include "ardour/filesystem_paths.h"
27 #include "ardour/luascripting.h"
28 #include "ardour/lua_script_params.h"
29 #include "ardour/search_paths.h"
30 #include "ardour/utils.h"
31
32 #include "lua/luastate.h"
33 #include "LuaBridge/LuaBridge.h"
34
35 #include "pbd/i18n.h"
36 #include "sha1.c"
37
38 using namespace ARDOUR;
39 using namespace PBD;
40 using namespace std;
41
42 LuaScripting* LuaScripting::_instance = 0;
43
44 LuaScripting&
45 LuaScripting::instance ()
46 {
47         if (!_instance) {
48                 _instance = new LuaScripting;
49         }
50         return *_instance;
51 }
52
53 LuaScripting::LuaScripting ()
54         : _sl_dsp (0)
55         , _sl_session (0)
56         , _sl_hook (0)
57         , _sl_action (0)
58         , _sl_snippet (0)
59         , _sl_setup (0)
60         , _sl_tracks (0)
61 {
62         ;
63 }
64
65 LuaScripting::~LuaScripting ()
66 {
67         if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
68                 // don't bother, just exit quickly.
69                 delete _sl_dsp;
70                 delete _sl_session;
71                 delete _sl_hook;
72                 delete _sl_action;
73                 delete _sl_snippet;
74                 delete _sl_setup;
75                 delete _sl_tracks;
76         }
77 }
78
79
80 void
81 LuaScripting::refresh (bool run_scan)
82 {
83         Glib::Threads::Mutex::Lock lm (_lock);
84
85         delete _sl_dsp;
86         delete _sl_session;
87         delete _sl_hook;
88         delete _sl_action;
89         delete _sl_snippet;
90         delete _sl_setup;
91         delete _sl_tracks;
92
93         _sl_dsp = 0;
94         _sl_session = 0;
95         _sl_hook = 0;
96         _sl_action = 0;
97         _sl_snippet = 0;
98         _sl_setup = 0;
99         _sl_tracks = 0;
100
101         if (run_scan) {
102                 lm.release ();
103                 scan ();
104         }
105 }
106
107 bool
108 LuaScripting::Sorter::operator() (LuaScriptInfoPtr const a, LuaScriptInfoPtr const b) const {
109         return ARDOUR::cmp_nocase_utf8 (a->name, b->name) < 0;
110 }
111
112 LuaScriptInfoPtr
113 LuaScripting::script_info (const std::string &script) {
114         return scan_script ("", script);
115 }
116
117 void
118 LuaScripting::scan ()
119 {
120         Glib::Threads::Mutex::Lock lm (_lock);
121
122 #define CLEAR_OR_NEW(LIST) \
123         if (LIST) { LIST->clear (); } else { LIST = new LuaScriptList (); }
124
125         CLEAR_OR_NEW (_sl_dsp)
126         CLEAR_OR_NEW (_sl_session)
127         CLEAR_OR_NEW (_sl_hook)
128         CLEAR_OR_NEW (_sl_action)
129         CLEAR_OR_NEW (_sl_snippet)
130         CLEAR_OR_NEW (_sl_setup)
131         CLEAR_OR_NEW (_sl_tracks)
132
133 #undef CLEAR_OR_NEW
134
135         vector<string> luascripts;
136         find_files_matching_pattern (luascripts, lua_search_path (), "*.lua");
137
138         for (vector<string>::iterator i = luascripts.begin(); i != luascripts.end (); ++i) {
139                 LuaScriptInfoPtr lsi = scan_script (*i);
140                 if (!lsi) {
141                         PBD::info << string_compose (_("Script '%1' has no valid descriptor."), *i) << endmsg;
142                         continue;
143                 }
144                 switch (lsi->type) {
145                         case LuaScriptInfo::DSP:
146                                 _sl_dsp->push_back(lsi);
147                                 break;
148                         case LuaScriptInfo::Session:
149                                 _sl_session->push_back(lsi);
150                                 break;
151                         case LuaScriptInfo::EditorHook:
152                                 _sl_hook->push_back(lsi);
153                                 break;
154                         case LuaScriptInfo::EditorAction:
155                                 _sl_action->push_back(lsi);
156                                 break;
157                         case LuaScriptInfo::Snippet:
158                                 _sl_snippet->push_back(lsi);
159                                 break;
160                         case LuaScriptInfo::SessionInit:
161                                 _sl_setup->push_back(lsi);
162                                 break;
163                         default:
164                                 break;
165                 }
166         }
167
168         std::sort (_sl_dsp->begin(), _sl_dsp->end(), Sorter());
169         std::sort (_sl_session->begin(), _sl_session->end(), Sorter());
170         std::sort (_sl_hook->begin(), _sl_hook->end(), Sorter());
171         std::sort (_sl_action->begin(), _sl_action->end(), Sorter());
172         std::sort (_sl_snippet->begin(), _sl_snippet->end(), Sorter());
173         std::sort (_sl_setup->begin(), _sl_setup->end(), Sorter());
174         std::sort (_sl_tracks->begin(), _sl_tracks->end(), Sorter());
175
176         scripts_changed (); /* EMIT SIGNAL */
177 }
178
179 void
180 LuaScripting::lua_print (std::string s) {
181         PBD::info << "Lua: " << s << "\n";
182 }
183
184 LuaScriptInfoPtr
185 LuaScripting::scan_script (const std::string &fn, const std::string &sc)
186 {
187         LuaState lua;
188         if (!(fn.empty() ^ sc.empty())){
189                 // give either file OR script
190                 assert (0);
191                 return LuaScriptInfoPtr();
192         }
193
194         lua_State* L = lua.getState();
195         lua.Print.connect (&LuaScripting::lua_print);
196         lua.sandbox (true);
197
198         lua.do_command (
199                         "ardourluainfo = {}"
200                         "function ardour (entry)"
201                         "  ardourluainfo['type'] = assert(entry['type'])"
202                         "  ardourluainfo['name'] = assert(entry['name'])"
203                         "  ardourluainfo['category'] = entry['category'] or 'Unknown'"
204                         "  ardourluainfo['author'] = entry['author'] or 'Unknown'"
205                         "  ardourluainfo['license'] = entry['license'] or ''"
206                         "  ardourluainfo['description'] = entry['description'] or ''"
207                         " end"
208                         );
209
210         try {
211                 int err;
212                 if (fn.empty()) {
213                         err = lua.do_command (sc);
214                 } else {
215                         err = lua.do_file (fn);
216                 }
217                 if (err) {
218 #ifndef NDEBUG
219                 cerr << "failed to load lua script\n";
220 #endif
221                         return LuaScriptInfoPtr();
222                 }
223         } catch (...) { // luabridge::LuaException
224 #ifndef NDEBUG
225                 cerr << "failed to parse lua script\n";
226 #endif
227                 return LuaScriptInfoPtr();
228         }
229         luabridge::LuaRef nfo = luabridge::getGlobal (L, "ardourluainfo");
230         if (nfo.type() != LUA_TTABLE) {
231 #ifndef NDEBUG
232                 cerr << "failed to get ardour{} table from script\n";
233 #endif
234                 return LuaScriptInfoPtr();
235         }
236
237         if (nfo["name"].type() != LUA_TSTRING || nfo["type"].type() != LUA_TSTRING) {
238 #ifndef NDEBUG
239                 cerr << "script-type or script-name is not a string\n";
240 #endif
241                 return LuaScriptInfoPtr();
242         }
243
244         std::string name = nfo["name"].cast<std::string>();
245         LuaScriptInfo::ScriptType type = LuaScriptInfo::str2type (nfo["type"].cast<std::string>());
246
247         if (name.empty() || type == LuaScriptInfo::Invalid) {
248 #ifndef NDEBUG
249                 cerr << "invalid script-type or missing script name\n";
250 #endif
251                 return LuaScriptInfoPtr();
252         }
253
254         char hash[41];
255         Sha1Digest s;
256         sha1_init (&s);
257
258         if (fn.empty()) {
259                 sha1_write (&s, (const uint8_t *) sc.c_str(), sc.size ());
260         } else {
261                 try {
262                         std::string script = Glib::file_get_contents (fn);
263                         sha1_write (&s, (const uint8_t *) script.c_str(), script.size ());
264                 } catch (Glib::FileError const& err) {
265                         return LuaScriptInfoPtr();
266                 }
267         }
268         sha1_result_hash (&s, hash);
269
270
271         LuaScriptInfoPtr lsi (new LuaScriptInfo (type, name, fn, hash));
272
273         for (luabridge::Iterator i(nfo); !i.isNil (); ++i) {
274                 if (!i.key().isString() || !i.value().isString()) {
275                         return LuaScriptInfoPtr();
276                 }
277                 std::string key = i.key().tostring();
278                 std::string val = i.value().tostring();
279
280                 if (key == "author") { lsi->author = val; }
281                 if (key == "license") { lsi->license = val; }
282                 if (key == "description") { lsi->description = val; }
283                 if (key == "category") { lsi->category = val; }
284         }
285
286
287         if (type == LuaScriptInfo::EditorAction) {
288
289                 luabridge::LuaRef lua_rs = luabridge::getGlobal (L, "route_setup");
290                 if (lua_rs.isFunction ()) {
291                         lsi->subtype |= LuaScriptInfo::RouteSetup;
292                 }
293
294                 luabridge::LuaRef lua_ss = luabridge::getGlobal (L, "session_setup");
295                 if (lua_ss.isFunction ()) {
296                         try {
297                                 if (lua_ss () == true) {
298                                         lsi->subtype |= LuaScriptInfo::SessionSetup;
299                                 }
300                         } catch (...) { }
301                 }
302
303         }
304
305         return lsi;
306 }
307
308 LuaScriptList &
309 LuaScripting::scripts (LuaScriptInfo::ScriptType type) {
310
311         if (!_sl_dsp || !_sl_session || !_sl_hook || !_sl_action || !_sl_snippet || ! _sl_setup || ! _sl_tracks) {
312                 scan ();
313         }
314
315         switch (type) {
316                 case LuaScriptInfo::DSP:
317                         return *_sl_dsp;
318                         break;
319                 case LuaScriptInfo::Session:
320                         return *_sl_session;
321                         break;
322                 case LuaScriptInfo::EditorHook:
323                         return *_sl_hook;
324                         break;
325                 case LuaScriptInfo::EditorAction:
326                         return *_sl_action;
327                         break;
328                 case LuaScriptInfo::Snippet:
329                         return *_sl_snippet;
330                         break;
331                 case LuaScriptInfo::SessionInit:
332                         return *_sl_setup;
333                         break;
334                 default:
335                         break;
336         }
337         return _empty_script_info; // make some compilers happy
338 }
339
340
341 std::string
342 LuaScriptInfo::type2str (const ScriptType t) {
343         switch (t) {
344                 case LuaScriptInfo::DSP: return "DSP";
345                 case LuaScriptInfo::Session: return "Session";
346                 case LuaScriptInfo::EditorHook: return "EditorHook";
347                 case LuaScriptInfo::EditorAction: return "EditorAction";
348                 case LuaScriptInfo::Snippet: return "Snippet";
349                 case LuaScriptInfo::SessionInit: return "SessionInit";
350                 default: return "Invalid";
351         }
352 }
353
354 LuaScriptInfo::ScriptType
355 LuaScriptInfo::str2type (const std::string& str) {
356         const char* type = str.c_str();
357         if (!strcasecmp (type, "DSP")) {return LuaScriptInfo::DSP;}
358         if (!strcasecmp (type, "Session")) {return LuaScriptInfo::Session;}
359         if (!strcasecmp (type, "EditorHook")) {return LuaScriptInfo::EditorHook;}
360         if (!strcasecmp (type, "EditorAction")) {return LuaScriptInfo::EditorAction;}
361         if (!strcasecmp (type, "Snippet")) {return LuaScriptInfo::Snippet;}
362         if (!strcasecmp (type, "SessionInit")) {return LuaScriptInfo::SessionInit;}
363         return LuaScriptInfo::Invalid;
364 }
365
366 LuaScriptParamList
367 LuaScriptParams::script_params (const LuaScriptInfoPtr& lsi, const std::string &pname)
368 {
369         assert (lsi);
370         return LuaScriptParams::script_params (lsi->path, pname);
371 }
372
373 LuaScriptParamList
374 LuaScriptParams::script_params (const std::string& s, const std::string &pname, bool file)
375 {
376         LuaState lua;
377         return LuaScriptParams::script_params (lua, s, pname, file);
378 }
379
380 LuaScriptParamList
381 LuaScriptParams::script_params (LuaState& lua, const std::string& s, const std::string &pname, bool file)
382 {
383         LuaScriptParamList rv;
384
385         lua_State* L = lua.getState();
386         lua.sandbox (true);
387         lua.do_command ("function ardour () end");
388
389         try {
390                 if (file) {
391                         lua.do_file (s);
392                 } else {
393                         lua.do_command (s);
394                 }
395         } catch (...) {
396                 return rv;
397         }
398
399         luabridge::LuaRef lua_params = luabridge::getGlobal (L, pname.c_str());
400         if (lua_params.isFunction ()) {
401                 luabridge::LuaRef params = lua_params ();
402                 if (params.isTable ()) {
403                         for (luabridge::Iterator i (params); !i.isNil (); ++i) {
404                                 if (!i.key ().isString ())            { continue; }
405                                 if (!i.value ().isTable ())           { continue; }
406                                 if (!i.value ()["title"].isString ()) { continue; }
407
408                                 std::string name = i.key ().cast<std::string> ();
409                                 std::string title = i.value ()["title"].cast<std::string> ();
410                                 std::string dflt;
411                                 bool optional = false;
412                                 bool preseeded = false;
413
414                                 if (i.value ()["default"].isString ()) {
415                                         dflt = i.value ()["default"].cast<std::string> ();
416                                 }
417                                 if (i.value ()["optional"].isBoolean ()) {
418                                         optional = i.value ()["optional"].cast<bool> ();
419                                 }
420                                 if (i.value ()["preseeded"].isBoolean ()) {
421                                         preseeded = i.value ()["preseeded"].cast<bool> ();
422                                 }
423                                 LuaScriptParamPtr lsspp (new LuaScriptParam(name, title, dflt, optional, preseeded));
424                                 rv.push_back (lsspp);
425                         }
426                 }
427         }
428         return rv;
429 }
430
431 void
432 LuaScriptParams::params_to_ref (luabridge::LuaRef *tbl_args, const LuaScriptParamList& args)
433 {
434         assert (tbl_args &&  (*tbl_args).isTable ());
435         for (LuaScriptParamList::const_iterator i = args.begin(); i != args.end(); ++i) {
436                 if ((*i)->optional && !(*i)->is_set) { continue; }
437                 (*tbl_args)[(*i)->name] = (*i)->value;
438         }
439 }
440
441 void
442 LuaScriptParams::ref_to_params (LuaScriptParamList& args, luabridge::LuaRef *tbl_ref)
443 {
444         assert (tbl_ref &&  (*tbl_ref).isTable ());
445         for (luabridge::Iterator i (*tbl_ref); !i.isNil (); ++i) {
446                 if (!i.key ().isString ()) { assert(0); continue; }
447                 std::string name = i.key ().cast<std::string> ();
448                 std::string value = i.value ().cast<std::string> ();
449                 for (LuaScriptParamList::const_iterator ii = args.begin(); ii != args.end(); ++ii) {
450                         if ((*ii)->name == name) {
451                                 (*ii)->value = value;
452                                 break;
453                         }
454                 }
455         }
456 }
457
458 bool
459 LuaScripting::try_compile (const std::string& script, const LuaScriptParamList& args)
460 {
461         const std::string& bytecode = get_factory_bytecode (script);
462         if (bytecode.empty()) {
463                 return false;
464         }
465         LuaState l;
466         l.Print.connect (&LuaScripting::lua_print);
467         l.sandbox (true);
468         lua_State* L = l.getState();
469
470         l.do_command (""
471                         " function checkfactory (b, a)"
472                         "  assert(type(b) == 'string', 'ByteCode must be string')"
473                         "  load(b)()" // assigns f
474                         "  assert(type(f) == 'string', 'Assigned ByteCode must be string')"
475                         "  local factory = load(f)"
476                         "  assert(type(factory) == 'function', 'Factory is a not a function')"
477                         "  local env = _ENV; env.f = nil env.os = nil env.io = nil"
478                         "  load (string.dump(factory, true), nil, nil, env)(a)"
479                         " end"
480                         );
481
482         try {
483                 luabridge::LuaRef lua_test = luabridge::getGlobal (L, "checkfactory");
484                 l.do_command ("checkfactory = nil"); // hide it.
485                 l.do_command ("collectgarbage()");
486
487                 luabridge::LuaRef tbl_arg (luabridge::newTable(L));
488                 LuaScriptParams::params_to_ref (&tbl_arg, args);
489                 lua_test (bytecode, tbl_arg);
490                 return true; // OK
491         } catch (luabridge::LuaException const& e) {
492 #ifndef NDEBUG
493                 cerr << e.what() << "\n";
494 #endif
495                 lua_print (e.what());
496         } catch (...) { }
497
498         return false;
499 }
500
501 std::string
502 LuaScripting::get_factory_bytecode (const std::string& script, const std::string& ffn, const std::string& fp)
503 {
504         LuaState l;
505         l.Print.connect (&LuaScripting::lua_print);
506         l.sandbox (true);
507         lua_State* L = l.getState();
508
509         l.do_command (
510                         " function ardour () end"
511                         ""
512                         " function dump_function (f)"
513                         "  assert(type(f) == 'function', 'Factory is a not a function')"
514                         "  return string.format(\"" + fp + " = %q\", string.dump(f, true))"
515                         " end"
516                         );
517
518         try {
519                 luabridge::LuaRef lua_dump = luabridge::getGlobal (L, "dump_function");
520                 l.do_command ("dump_function = nil"); // hide it
521                 l.do_command (script); // register "factory"
522                 luabridge::LuaRef lua_factory = luabridge::getGlobal (L, ffn.c_str());
523
524                 if (lua_factory.isFunction()) {
525                         return (lua_dump(lua_factory)).cast<std::string> ();
526                 }
527         } catch (...) { }
528         return "";
529 }
530
531 std::string
532 LuaScripting::user_script_dir ()
533 {
534         std::string dir = Glib::build_filename (user_config_directory(), lua_dir_name);
535         g_mkdir_with_parents (dir.c_str(), 0744);
536         return dir;
537 }