#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_VERSION #include "gm-support.h" #include "gm-debug.h" #include "gm-world.h" #include "gm-app.h" #include "gm-scripts.h" #include "gm-world.h" #include "gm-string.h" #define GM_SCRIPTS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ GM_TYPE_SCRIPTS, GmScriptsPrivate)) #define RB_CALLBACK(x) (VALUE (*)())(x) #define GM_SCRIPTS_GLOBAL PACKAGE_DATA_DIR "/" PACKAGE "/scripts" static VALUE rb_world_class, rb_client_class, rb_scripts_class; VALUE script_world_name(VALUE self); VALUE gm_scripts_rb_world_new(GmWorld *world); VALUE gm_scripts_rb_scripts_new(GmScripts *scripts); void gm_scripts_rb_world_class_init(); void gm_scripts_rb_client_class_init(); void gm_scripts_rb_scripts_class_init(); void gm_scripts_unload(GmScripts *scripts); void gm_scripts_rb_init(); struct _GmScriptsPrivate { GList *files; GmScript *loading; GList *monitors; }; /* Signals */ enum { SCRIPT_ADDED, SCRIPT_CHANGED, SCRIPT_REMOVED, RELOAD, MESSAGE, ERROR, RUN, NUM_SIGNALS }; static guint gm_scripts_signals[NUM_SIGNALS] = {0}; G_DEFINE_TYPE(GmScripts, gm_scripts, G_TYPE_OBJECT) static void gm_scripts_finalize(GObject *object) { GmScripts *scripts = GM_SCRIPTS(object); GList *monitors; gm_scripts_unload(scripts); for (monitors = scripts->priv->monitors; monitors; monitors = monitors->next) { gnome_vfs_monitor_cancel((GnomeVFSMonitorHandle *)(monitors->data)); } g_list_free(scripts->priv->monitors); scripts->priv->monitors = NULL; G_OBJECT_CLASS(gm_scripts_parent_class)->finalize(object); } static void gm_scripts_class_init(GmScriptsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = gm_scripts_finalize; gm_scripts_signals[SCRIPT_ADDED] = g_signal_new("script_added", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmScriptsClass, script_added), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); gm_scripts_signals[SCRIPT_CHANGED] = g_signal_new("script_changed", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmScriptsClass, script_changed), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); gm_scripts_signals[SCRIPT_REMOVED] = g_signal_new("script_removed", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmScriptsClass, script_removed), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); gm_scripts_signals[RELOAD] = g_signal_new("reload", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmScriptsClass, reload), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gm_scripts_signals[MESSAGE] = g_signal_new("message", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmScriptsClass, message), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); gm_scripts_signals[ERROR] = g_signal_new("error", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmScriptsClass, error), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); gm_scripts_signals[RUN] = g_signal_new("run", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmScriptsClass, run), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); g_type_class_add_private(object_class, sizeof(GmScriptsPrivate)); gm_scripts_rb_init(); } void gm_scripts_rb_init(GmScriptsClass *klass) { ruby_init(); gm_scripts_rb_world_class_init(); gm_scripts_rb_client_class_init(); gm_scripts_rb_scripts_class_init(); } static void gm_scripts_init(GmScripts *scripts) { scripts->priv = GM_SCRIPTS_GET_PRIVATE(scripts); scripts->priv->monitors = NULL; scripts->priv->files = NULL; } GmScripts * gm_scripts_new() { GmScripts *scripts = GM_SCRIPTS(g_object_new(GM_TYPE_SCRIPTS, NULL)); return scripts; } void gm_scripts_monitor_cb (GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType event_type, GmScripts *scripts) { gchar *filename = gnome_vfs_get_local_path_from_uri(info_uri); switch (event_type) { case GNOME_VFS_MONITOR_EVENT_CHANGED: gm_scripts_reload_file(scripts, filename); break; case GNOME_VFS_MONITOR_EVENT_DELETED: gm_scripts_remove_file(scripts, filename); break; case GNOME_VFS_MONITOR_EVENT_CREATED: gm_scripts_add_file(scripts, filename); break; default: break; } g_free(filename); } void gm_script_function_destroy(GmScriptFunction *fi) { g_free(fi->name); g_free(fi->fname); g_free(fi->description); g_free(fi); } void gm_script_destroy_functions(GmScript *script) { GList *functions; for (functions = script->functions; functions; functions = functions->next) { gm_script_function_destroy((GmScriptFunction *)(functions->data)); } g_list_free(script->functions); script->functions = NULL; } void gm_script_destroy(GmScript *script) { gm_script_destroy_functions(script); g_free(script->filename); g_free(script); } void gm_scripts_unload(GmScripts *scripts) { GList *files; for (files = scripts->priv->files; files; files = files->next) { gm_script_destroy((GmScript *)(files->data)); } g_list_free(scripts->priv->files); scripts->priv->files = NULL; } GmScriptFunction * gm_scripts_find(GmScripts *scripts, gchar *name) { GList *files; GList *functions; GmScriptFunction *func; GmScript *script; for (files = scripts->priv->files; files; files = files->next) { script = (GmScript *)(files->data); for (functions = script->functions; functions; functions = functions->next) { func = (GmScriptFunction *)(functions->data); if (strcasecmp(func->name, name) == 0) { return func; } } } return NULL; } gboolean gm_scripts_add(GmScripts *scripts, gchar *name, gchar *fname, gchar *description) { GmScriptFunction *func; if (gm_scripts_find(scripts, name) == NULL) { func = g_new(GmScriptFunction, 1); func->script = scripts->priv->loading; func->name = g_strdup(name); func->fname = g_strdup(fname);; func->description = g_strdup(description); scripts->priv->loading->functions = g_list_append(scripts->priv->loading->functions, func); return TRUE; } else { return FALSE; } } VALUE gm_scripts_rb_register_functions_wrap(VALUE arg) { return rb_eval_string("register_functions"); } void gm_scripts_rb_script_define_world(VALUE *world) { rb_define_variable("$world", world); } void gm_scripts_rb_error(GmScripts *scripts) { int c; VALUE lasterr; char *err; gchar *msg; VALUE ary; if(!NIL_P(ruby_errinfo)) { lasterr = rb_gv_get("$!"); err = RSTRING(rb_obj_as_string(lasterr))->ptr; gm_debug_msg(DEBUG_DEFAULT, "GmScripts.Error: Error while executing Ruby code: %s", err); msg = g_strdup_printf(_("Error in execution: %s"), err); g_signal_emit(scripts, gm_scripts_signals[MESSAGE], 0, msg); g_free(msg); ary = rb_funcall(ruby_errinfo, rb_intern("backtrace"), 0); gm_debug_msg(DEBUG_DEFAULT, "GmScripts.Error: Ruby backtrace:"); g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, _("Ruby backtrace:")); for (c = 0; c < RARRAY(ary)->len; c++) { gm_debug_msg(DEBUG_DEFAULT, "GmScripts.Error: \tfrom %s", RSTRING(RARRAY(ary)->ptr[c])->ptr); msg = g_strdup_printf(_("\tfrom %s"), RSTRING(RARRAY(ary)->ptr[c])->ptr); g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, msg); g_free(msg); } } } int gm_scripts_rb_do(GmScripts *scripts, VALUE (*body)(), VALUE arg) { int status; rb_protect(body, arg, &status); if (status != 0) { gm_scripts_rb_error(scripts); ruby_cleanup(status); return 0; } return 1; } VALUE gm_scripts_run_function(GmScriptInfo *arg) { VALUE ret; gchar *argstr; gchar *func_all; if (arg->argstr) { argstr = gm_string_escape(arg->argstr); func_all = g_strconcat(arg->name, "(\"", argstr, "\")", NULL); g_free(argstr); } else { func_all = g_strconcat(arg->name, "()", NULL); } ret = rb_eval_string(func_all); g_free(func_all); return ret; } gboolean gm_scripts_run(GmScripts *scripts, GmWorld *world, gchar *name, gchar *argstr) { VALUE rbWorld, rbClient; gchar *msg; GmScriptInfo *info; GmScriptFunction *f = gm_scripts_find(scripts, name); if (!f) { return FALSE; } info = g_new0(GmScriptInfo, 1); info->name = g_strdup(f->fname); if (argstr) { info->argstr = g_strdup(argstr); msg = g_strdup_printf(_("Run script '%s' from '%s' (%s)"), f->fname, f->script->filename, argstr); } else { info->argstr = NULL; msg = g_strdup_printf(_("Run script '%s' from '%s' ()"), f->fname, f->script->filename); } g_signal_emit(scripts, gm_scripts_signals[RUN], 0, msg); g_free(msg); gm_scripts_rb_do(scripts, RB_CALLBACK(rb_load_file), (VALUE)(f->script->filename)); ruby_exec(); rbWorld = gm_scripts_rb_world_new(world); rb_define_variable("$world", &rbWorld); rbClient = rb_class_new_instance(0, NULL, rb_client_class); rb_define_variable("$client", &rbClient); gm_scripts_rb_do(scripts, RB_CALLBACK(gm_scripts_run_function), (VALUE)info); g_free(info->name); g_free(info->argstr); g_free(info); return TRUE; } VALUE gm_scripts_rb_register_func_old(int argc, VALUE *argv) { gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RegisterFunc: This is the deprecated way to " "register functions is does no longer work. Use $scripts.register " "instead."); return Qfalse; } void gm_scripts_register_functions(GmScripts *scripts) { gchar *msg; VALUE rbScripts; gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RegisterFunctions: registering functions in %s", scripts->priv->loading->filename); msg = g_strdup_printf(_("Registering functions from '%s'"), scripts->priv->loading->filename); g_signal_emit(scripts, gm_scripts_signals[MESSAGE], 0, msg); g_free(msg); // Okay, I'm desperate... define an empty register_functions method so // that the previous when gets cleared ... :( rb_eval_string("def register_functions() end"); if (!gm_scripts_rb_do(scripts, RB_CALLBACK(rb_load_file), (VALUE) scripts->priv->loading->filename)) { return; } ruby_exec(); rbScripts = gm_scripts_rb_scripts_new(scripts); rb_define_variable("$scripts", &rbScripts); rb_define_global_function("register_func", &gm_scripts_rb_register_func_old, -1); gm_scripts_rb_do(scripts, RB_CALLBACK(gm_scripts_rb_register_functions_wrap), 0); } void gm_scripts_remove_file(GmScripts *scripts, const gchar *uri) { GList *f, *l; GmScript *script; l = g_list_copy(scripts->priv->files); for (f = l; f; f = f->next) { script = (GmScript *)(f->data); if (strcmp(script->filename, uri) == 0) { scripts->priv->files = g_list_remove(scripts->priv->files, script); gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RemoveFile: Removing scripts from `%s'", script->filename); g_signal_emit(scripts, gm_scripts_signals[SCRIPT_REMOVED], 0, script); gm_script_destroy(script); } } g_list_free(l); } gboolean gm_scripts_add_file(GmScripts *scripts, const gchar *uri) { GList *f; GmScript *script; gchar *msg; gchar *ext; // Only .rb files ext = strrchr(uri, '.'); if (ext == NULL || strncmp(ext, ".rb", 2) != 0) { msg = g_strdup_printf(_("File `%s' is not a valid ruby file"), uri); gm_debug_msg(DEBUG_DEFAULT, "GmScripts.AddFile: %s", msg); g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, msg); g_free(msg); return FALSE; } for (f = scripts->priv->files; f; f = f->next) { script = (GmScript *)(f->data); if (strcmp(script->filename, uri) == 0) { msg = g_strdup_printf(_("File `%s' already loaded"), uri); gm_debug_msg(DEBUG_DEFAULT, "GmScripts.AddFile: %s", msg); g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, msg); g_free(msg); return FALSE; } } msg = g_strdup_printf(_("File `%s' added"), uri); gm_debug_msg(DEBUG_DEFAULT, "GmScripts.AddFile: %s", msg); g_free(msg); script = g_new0(GmScript, 1); script->filename = g_strdup(uri); if (strncmp(uri, GM_SCRIPTS_GLOBAL, strlen(GM_SCRIPTS_GLOBAL)) == 0) { script->type = GM_SCRIPT_TYPE_SHARE; } else { script->type = GM_SCRIPT_TYPE_USER; } scripts->priv->files = g_list_append(scripts->priv->files, script); scripts->priv->loading = script; gm_scripts_register_functions(scripts); g_signal_emit(scripts, gm_scripts_signals[SCRIPT_ADDED], 0, script); return TRUE; } void gm_scripts_reload_file(GmScripts *scripts, const gchar *uri) { GList *files; GmScript *script; for (files = scripts->priv->files; files; files = files->next) { script = (GmScript *)(files->data); if (strcmp(script->filename, uri) == 0) { // Remove all functions and reregister the file scripts->priv->loading = script; gm_script_destroy_functions(script); gm_scripts_register_functions(scripts); g_signal_emit(scripts, gm_scripts_signals[SCRIPT_CHANGED], 0, script); return; } } // If the script does not yet exist, add it gm_scripts_add_file(scripts, uri); } void gm_scripts_load_dir(GmScripts *scripts, gchar *dirname) { gchar *filename; gchar *file; GDir *d; GnomeVFSMonitorHandle *handle; if (g_file_test(dirname, G_FILE_TEST_EXISTS) && g_file_test(dirname, G_FILE_TEST_IS_DIR)) { if ((d = g_dir_open(dirname, 0, NULL))) { while ((file = (gchar *)g_dir_read_name(d))) { filename = g_strconcat(dirname, "/", file, NULL); gm_scripts_add_file(scripts, filename); g_free(filename); } } gnome_vfs_monitor_add(&handle, dirname, GNOME_VFS_MONITOR_DIRECTORY, (GnomeVFSMonitorCallback)gm_scripts_monitor_cb, scripts); scripts->priv->monitors = g_list_append(scripts->priv->monitors, handle); } } void gm_scripts_load(GmScripts *scripts) { gchar *path; if (scripts->priv->files) { g_signal_emit(scripts, gm_scripts_signals[RELOAD], 0); gm_scripts_unload(scripts); } path = g_strconcat(gm_app_path(gm_app_instance()), "/scripts", NULL); // Make user dir if it doesn't exist if (!g_file_test(path, G_FILE_TEST_EXISTS)) { mkdir(path, 0750); } gm_scripts_load_dir(scripts, path); gm_scripts_load_dir(scripts, GM_SCRIPTS_GLOBAL); g_free(path); } GList * gm_scripts_scripts(GmScripts *scripts) { return scripts->priv->files; } // Ruby class functions VALUE gm_scripts_rb_world_new(GmWorld *world) { VALUE tdata = Data_Wrap_Struct(rb_world_class, 0, 0, world); return tdata; } VALUE gm_scripts_rb_scripts_new(GmScripts *scripts) { VALUE tdata = Data_Wrap_Struct(rb_scripts_class, 0, 0, scripts); return tdata; } // Ruby world class functions static VALUE gm_scripts_rb_world_name(VALUE self) { GmWorld *world; Data_Get_Struct(self, GmWorld, world); return rb_str_new2(gm_options_get(gm_world_options(world), "name")); } static VALUE gm_scripts_rb_world_host(VALUE self) { GmWorld *world; Data_Get_Struct(self, GmWorld, world); return rb_str_new2(gm_options_get(gm_world_options(world), "host")); } static VALUE gm_scripts_rb_world_port(VALUE self) { GmWorld *world; Data_Get_Struct(self, GmWorld, world); return rb_str_new2(gm_options_get(gm_world_options(world), "port")); } static VALUE gm_scripts_rb_world_writeln(VALUE self, VALUE str) { GmWorld *world; gchar *strVal; Data_Get_Struct(self, GmWorld, world); strVal = rb_string_value_cstr(&str); gm_world_writeln(world, strVal); return Qnil; } static VALUE gm_scripts_rb_world_status(VALUE self, VALUE str) { GmWorld *world; gchar *strVal; Data_Get_Struct(self, GmWorld, world); strVal = rb_string_value_cstr(&str); gm_world_status(world, strVal); return Qnil; } static VALUE gm_scripts_rb_world_sendln(VALUE self, VALUE str) { GmWorld *world; gchar *strVal; Data_Get_Struct(self, GmWorld, world); strVal = rb_string_value_cstr(&str); gm_world_sendln(world, strVal); return Qnil; } static VALUE gm_scripts_rb_world_input(VALUE self, VALUE str) { GmWorld *world; gchar *strVal; Data_Get_Struct(self, GmWorld, world); strVal = rb_string_value_cstr(&str); gm_world_process_input(world, strVal); return Qnil; } static VALUE gm_scripts_rb_world_loaded(VALUE self) { GmWorld *world; Data_Get_Struct(self, GmWorld, world); if (gm_world_loaded(world)) { return Qtrue; } else { return Qfalse; } } static VALUE gm_scripts_rb_world_connected(VALUE self) { GmWorld *world; Data_Get_Struct(self, GmWorld, world); if (gm_world_state(world) == GM_NET_STATE_CONNECTED) { return Qtrue; } else { return Qfalse; } } static VALUE gm_scripts_rb_world_quit(VALUE self) { GmWorld *world; Data_Get_Struct(self, GmWorld, world); gm_world_unload(world); return Qnil; } static VALUE gm_scripts_rb_world_connect(int argc, VALUE *argv, VALUE self) { GmWorld *world; const gchar *strHost, *strPort; Data_Get_Struct(self, GmWorld, world); if (argc == 0) { strHost = gm_options_get(gm_world_options(world), "host"); } else { strHost = rb_string_value_cstr(&(argv[0])); } if (argc == 0 || argc == 1) { strPort = gm_options_get(gm_world_options(world), "port"); } else { strPort = rb_string_value_cstr(&(argv[1])); } gm_world_connect_to(world, (gchar *)strHost, (gchar *)strPort); return Qnil; } static VALUE gm_scripts_rb_world_disconnect(VALUE self) { GmWorld *world; Data_Get_Struct(self, GmWorld, world); gm_world_disconnect(world); return Qnil; } // Ruby client class functions static VALUE gm_scripts_rb_client_version(VALUE self) { return rb_str_new2(VERSION); } static VALUE gm_scripts_rb_client_worlds(VALUE self) { GList *world; VALUE rb_array = rb_ary_new(); VALUE rb_world; for (world = gm_app_worlds(gm_app_instance()); world; world = world->next) { rb_world = gm_scripts_rb_world_new((GmWorld *)(world->data)); rb_ary_push(rb_array, rb_world); } return rb_array; } static VALUE gm_scripts_rb_client_open(VALUE self, VALUE str) { GmWorld *world; gchar *strVal; strVal = rb_string_value_cstr(&str); world = gm_app_world_by_name(gm_app_instance(), strVal); if (world == NULL) { return Qfalse; } else { gm_world_load(world); return Qtrue; } } // Ruby scripts class functions VALUE gm_scripts_rb_scripts_register(int argc, VALUE *argv, VALUE self) { char *name, *fname = NULL, *description = NULL; gchar *msg; GmScripts *scripts; Data_Get_Struct(self, GmScripts, scripts); if (argc > 1) { name = rb_string_value_cstr(&argv[0]); description = rb_string_value_cstr(&argv[1]); if (argc == 2) { fname = name; } else { fname = rb_string_value_cstr(&argv[2]); } if (gm_scripts_add(scripts, name, fname, description)) { msg = g_strdup_printf(_("Register function '%s' from '%s'"), name, scripts->priv->loading->filename); gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RbScriptsRegister: Adding script function " "%s from %s", name, scripts->priv->loading->filename); g_signal_emit(scripts, gm_scripts_signals[MESSAGE], 0, msg); g_free(msg); return Qtrue; } else { msg = g_strdup_printf(_("Script '%s' is already defined"), name); gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RbScriptsRegister: Script function %s " "already defined!", name); g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, msg); g_free(msg); return Qfalse; } } else { return Qfalse; } } // Ruby class initializations void gm_scripts_rb_world_class_init() { rb_world_class = rb_define_class("World", rb_cObject); rb_define_method(rb_world_class, "name", gm_scripts_rb_world_name, 0); rb_define_method(rb_world_class, "host", gm_scripts_rb_world_host, 0); rb_define_method(rb_world_class, "port", gm_scripts_rb_world_port, 0); rb_define_method(rb_world_class, "writeln", gm_scripts_rb_world_writeln, 1); rb_define_method(rb_world_class, "println", gm_scripts_rb_world_writeln, 1); rb_define_method(rb_world_class, "status", gm_scripts_rb_world_status, 1); rb_define_method(rb_world_class, "sendln", gm_scripts_rb_world_sendln, 1); rb_define_method(rb_world_class, "input", gm_scripts_rb_world_input, 1); rb_define_method(rb_world_class, "quit", gm_scripts_rb_world_quit, 0); rb_define_method(rb_world_class, "connect", gm_scripts_rb_world_connect, -1); rb_define_method(rb_world_class, "disconnect", gm_scripts_rb_world_disconnect, 0); rb_define_method(rb_world_class, "loaded?", gm_scripts_rb_world_loaded, 0); rb_define_method(rb_world_class, "connected?", gm_scripts_rb_world_connected, 0); } void gm_scripts_rb_client_class_init() { rb_client_class = rb_define_class("Client", rb_cObject); rb_define_method(rb_client_class, "version", gm_scripts_rb_client_version, 0); rb_define_method(rb_client_class, "worlds", gm_scripts_rb_client_worlds, 0); rb_define_method(rb_client_class, "open", gm_scripts_rb_client_open, 1); } void gm_scripts_rb_scripts_class_init() { rb_scripts_class = rb_define_class("Scripts", rb_cObject); rb_define_method(rb_scripts_class, "register", gm_scripts_rb_scripts_register, -1); }