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