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