1 /*
2  * Nautilus-Actions
3  * A Nautilus extension which offers configurable context menu actions.
4  *
5  * Copyright (C) 2005 The GNOME Foundation
6  * Copyright (C) 2006-2008 Frederic Ruaudel and others (see AUTHORS)
7  * Copyright (C) 2009-2014 Pierre Wieser and others (see AUTHORS)
8  *
9  * Nautilus-Actions is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of
12  * the License, or (at your option) any later version.
13  *
14  * Nautilus-Actions is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Nautilus-Actions; see the file COPYING. If not, see
21  * <http://www.gnu.org/licenses/>.
22  *
23  * Authors:
24  *   Frederic Ruaudel <grumz@grumz.net>
25  *   Rodrigo Moya <rodrigo@gnome-db.org>
26  *   Pierre Wieser <pwieser@trychlos.org>
27  *   ... and many others (see AUTHORS)
28  */
29 
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 #include <glib.h>
35 #include <glib/gi18n.h>
36 #include <locale.h>
37 #include <stdlib.h>
38 #include <string.h>
39 
40 #include <api/na-core-utils.h>
41 #include <api/na-object-api.h>
42 #include <api/na-dbus.h>
43 
44 #include <core/na-gconf-migration.h>
45 #include <core/na-pivot.h>
46 #include <core/na-selected-info.h>
47 #include <core/na-tokens.h>
48 
49 #include "console-utils.h"
50 #include "nautilus-actions-run-bindings.h"
51 
52 static gchar     *id               = "";
53 static gchar    **targets_array    = NULL;
54 static gboolean   version          = FALSE;
55 
56 static GOptionEntry entries[] = {
57 
58 	{ "id"                   , 'i', 0, G_OPTION_ARG_STRING        , &id,
59 			N_( "The internal identifier of the action to be launched" ), N_( "<STRING>" ) },
60 	{ "target"               , 't', 0, G_OPTION_ARG_FILENAME_ARRAY, &targets_array,
61 			N_( "A target, file or folder, for the action. More than one options may be specified" ), N_( "<URI>" ) },
62 	{ NULL }
63 };
64 
65 static GOptionEntry misc_entries[] = {
66 
67 	{ "version"              , 'v', 0, G_OPTION_ARG_NONE        , &version,
68 			N_( "Output the version number" ), NULL },
69 	{ NULL }
70 };
71 
72 static GOptionContext  *init_options( void );
73 static NAObjectAction  *get_action( const gchar *id );
74 static GList           *targets_from_selection( void );
75 static GList           *targets_from_commandline( void );
76 static GList           *get_selection_from_strv( const gchar **strv, gboolean has_mimetype );
77 static NAObjectProfile *get_profile_for_targets( NAObjectAction *action, GList *targets );
78 static void             execute_action( NAObjectAction *action, NAObjectProfile *profile, GList *targets );
79 static void             dump_targets( GList *targets );
80 static void             exit_with_usage( void );
81 
82 int
main(int argc,char ** argv)83 main( int argc, char** argv )
84 {
85 	static const gchar *thisfn = "nautilus_actions_run_main";
86 	int status = EXIT_SUCCESS;
87 	GOptionContext *context;
88 	GError *error = NULL;
89 	gchar *help;
90 	gint errors;
91 	NAObjectAction *action;
92 	NAObjectProfile *profile;
93 	GList *targets;
94 
95 #if !GLIB_CHECK_VERSION( 2,36, 0 )
96 	g_type_init();
97 #endif
98 
99 	setlocale( LC_ALL, "" );
100 	console_init_log_handler();
101 
102 	/* pwi 2011-01-05
103 	 * run GConf migration tools before doing anything else
104 	 * above all before allocating a new NAPivot
105 	 */
106 	na_gconf_migration_run();
107 
108 	context = init_options();
109 
110 	if( argc == 1 ){
111 		g_set_prgname( argv[0] );
112 		help = g_option_context_get_help( context, FALSE, NULL );
113 		g_print( "\n%s", help );
114 		g_free( help );
115 		exit( status );
116 	}
117 
118 	if( !g_option_context_parse( context, &argc, &argv, &error )){
119 		g_printerr( _( "Syntax error: %s\n" ), error->message );
120 		g_error_free (error);
121 		exit_with_usage();
122 	}
123 
124 	g_option_context_free( context );
125 
126 	if( version ){
127 		na_core_utils_print_version();
128 		exit( status );
129 	}
130 
131 	errors = 0;
132 
133 	if( !id || !strlen( id )){
134 		g_printerr( _( "Error: action id is mandatory.\n" ));
135 		errors += 1;
136 	}
137 
138 	action = get_action( id );
139 	if( !action ){
140 		errors += 1;
141 	} else {
142 		g_debug( "%s: action %s have been found, and is enabled and valid", thisfn, id );
143 	}
144 
145 	if( errors ){
146 		exit_with_usage();
147 	}
148 
149 	if( targets_array ){
150 		targets = targets_from_commandline();
151 
152 	} else {
153 		targets = targets_from_selection();
154 	}
155 
156 	dump_targets( targets );
157 
158 	if( g_list_length( targets ) == 0 ){
159 		g_print( _( "No current selection. Nothing to do. Exiting.\n" ));
160 		exit( status );
161 	}
162 
163 	if( !na_icontext_is_candidate( NA_ICONTEXT( action ), ITEM_TARGET_ANY, targets )){
164 		g_printerr( _( "Action %s is not a valid candidate. Exiting.\n" ), id );
165 		exit( status );
166 	}
167 
168 	profile = get_profile_for_targets( action, targets );
169 	if( !profile ){
170 		g_print( _( "No valid profile is candidate to execution. Exiting.\n" ));
171 		exit( status );
172 	}
173 	g_debug( "%s: profile %p found", thisfn, ( void * ) profile );
174 
175 	execute_action( action, profile, targets );
176 
177 	na_selected_info_free_list( targets );
178 	exit( status );
179 }
180 
181 /*
182  * init options context
183  */
184 static GOptionContext *
init_options(void)185 init_options( void )
186 {
187 	GOptionContext *context;
188 	gchar* description;
189 	GOptionGroup *misc_group;
190 
191 	context = g_option_context_new( _( "Execute an action on the specified target." ));
192 	g_option_context_set_translation_domain( context, GETTEXT_PACKAGE );
193 
194 #ifdef ENABLE_NLS
195 	bindtextdomain( GETTEXT_PACKAGE, GNOMELOCALEDIR );
196 # ifdef HAVE_BIND_TEXTDOMAIN_CODESET
197 	bind_textdomain_codeset( GETTEXT_PACKAGE, "UTF-8" );
198 # endif
199 	textdomain( GETTEXT_PACKAGE );
200 	g_option_context_add_main_entries( context, entries, GETTEXT_PACKAGE );
201 #else
202 	g_option_context_add_main_entries( context, entries, NULL );
203 #endif
204 
205 	description = console_cmdline_get_description();
206 	g_option_context_set_description( context, description );
207 	g_free( description );
208 
209 	misc_group = g_option_group_new(
210 			"misc", _( "Miscellaneous options" ), _( "Miscellaneous options" ), NULL, NULL );
211 	g_option_group_add_entries( misc_group, misc_entries );
212 	g_option_group_set_translation_domain( misc_group, GETTEXT_PACKAGE );
213 	g_option_context_add_group( context, misc_group );
214 
215 	return( context );
216 }
217 
218 /*
219  * search for the action in the repository
220  */
221 static NAObjectAction *
get_action(const gchar * id)222 get_action( const gchar *id )
223 {
224 	NAPivot *pivot;
225 	NAObjectAction *action;
226 
227 	action = NULL;
228 
229 	pivot = na_pivot_new();
230 	na_pivot_set_loadable( pivot, !PIVOT_LOAD_DISABLED & !PIVOT_LOAD_INVALID );
231 	na_pivot_load_items( pivot );
232 
233 	action = ( NAObjectAction * ) na_pivot_get_item( pivot, id );
234 
235 	if( !action ){
236 		g_printerr( _( "Error: action '%s' doesn't exist.\n" ), id );
237 
238 	} else {
239 		if( !na_object_is_enabled( action )){
240 			g_printerr( _( "Error: action '%s' is disabled.\n" ), id );
241 			g_object_unref( action );
242 			action = NULL;
243 		}
244 		if( !na_object_is_valid( action )){
245 			g_printerr( _( "Error: action '%s' is not valid.\n" ), id );
246 			g_object_unref( action );
247 			action = NULL;
248 		}
249 	}
250 
251 	return( action );
252 }
253 
254 /*
255  * the DBus.Tracker.Properties1 interface returns a list of strings
256  * where each selected item brings up both its URI and its Nautilus
257  * mime type.
258  *
259  * We return to the caller a GList of NASelectedInfo objects
260  */
261 static GList *
targets_from_selection(void)262 targets_from_selection( void )
263 {
264 	static const gchar *thisfn = "nautilus_actions_run_targets_from_selection";
265 	GList *selection;
266 	GError *error;
267 	gchar **paths;
268 
269 	g_debug( "%s", thisfn );
270 
271 	selection = NULL;
272 	error = NULL;
273 	paths = NULL;
274 
275 #ifdef HAVE_GDBUS
276 	GDBusObjectManager *manager;
277 	gchar *name_owner;
278 	GDBusObject *object;
279 	GDBusInterface *iface;
280 
281 	manager = na_tracker_object_manager_client_new_for_bus_sync(
282 			G_BUS_TYPE_SESSION,
283 			G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
284 			NAUTILUS_ACTIONS_DBUS_SERVICE,
285 			NAUTILUS_ACTIONS_DBUS_TRACKER_PATH,
286 			NULL,
287 			&error );
288 
289 	if( !manager ){
290 		g_printerr( "%s: unable to allocate an ObjectManagerClient: %s\n", thisfn, error->message );
291 		g_error_free( error );
292 		return( NULL );
293 	}
294 
295 	name_owner = g_dbus_object_manager_client_get_name_owner( G_DBUS_OBJECT_MANAGER_CLIENT( manager ));
296 	g_debug( "%s: name_owner=%s", thisfn, name_owner );
297 	g_free( name_owner );
298 
299 	object = g_dbus_object_manager_get_object( manager, NAUTILUS_ACTIONS_DBUS_TRACKER_PATH "/0" );
300 	if( !object ){
301 		g_printerr( "%s: unable to get object at %s path\n", thisfn, NAUTILUS_ACTIONS_DBUS_TRACKER_PATH "/0" );
302 		g_object_unref( manager );
303 		return( NULL );
304 	}
305 
306 	iface = g_dbus_object_get_interface( object, NAUTILUS_ACTIONS_DBUS_TRACKER_IFACE );
307 	if( !iface ){
308 		g_printerr( "%s: unable to get %s interface\n", thisfn, NAUTILUS_ACTIONS_DBUS_TRACKER_IFACE );
309 		g_object_unref( object );
310 		g_object_unref( manager );
311 		return( NULL );
312 	}
313 
314 	/* note that @iface is really a GDBusProxy instance
315 	 * and additionally also a NATrackerProperties1 instance
316 	 */
317 	na_tracker_properties1_call_get_selected_paths_sync(
318 			NA_TRACKER_PROPERTIES1( iface ),
319 			&paths,
320 			NULL,
321 			&error );
322 
323 #else
324 # ifdef HAVE_DBUS_GLIB
325 	DBusGConnection *connection;
326 	DBusGProxy *proxy = NULL;
327 
328 	connection = dbus_g_bus_get( DBUS_BUS_SESSION, &error );
329 	if( !connection ){
330 		if( error ){
331 			g_printerr( _( "Error: unable to get a connection to session DBus: %s" ), error->message );
332 			g_error_free( error );
333 		}
334 		return( NULL );
335 	}
336 	g_debug( "%s: connection is ok", thisfn );
337 
338 	proxy = dbus_g_proxy_new_for_name( connection,
339 			NAUTILUS_ACTIONS_DBUS_SERVICE,
340 			NAUTILUS_ACTIONS_DBUS_TRACKER_PATH "/0",
341 			NAUTILUS_ACTIONS_DBUS_TRACKER_IFACE );
342 
343 	if( !proxy ){
344 		g_printerr( _( "Error: unable to get a proxy on %s service" ), NAUTILUS_ACTIONS_DBUS_SERVICE );
345 		dbus_g_connection_unref( connection );
346 		return( NULL );
347 	}
348 	g_debug( "%s: proxy is ok", thisfn );
349 
350 	if( !dbus_g_proxy_call( proxy, "GetSelectedPaths", &error,
351 			G_TYPE_INVALID,
352 			G_TYPE_STRV, &paths, G_TYPE_INVALID )){
353 
354 		g_printerr( _( "Error on GetSelectedPaths call: %s" ), error->message );
355 		g_error_free( error );
356 		/* TODO: unref proxy */
357 		dbus_g_connection_unref( connection );
358 		return( NULL );
359 	}
360 	g_debug( "%s: function call is ok", thisfn );
361 
362 	/* TODO: unref proxy */
363 	dbus_g_connection_unref( connection );
364 # endif
365 #endif
366 
367 	selection = get_selection_from_strv(( const gchar ** ) paths, TRUE );
368 
369 	g_strfreev( paths );
370 
371 	return( selection );
372 }
373 
374 /*
375  * get targets from command-line
376  *
377  * We return to the caller a GList of NASelectedInfo objects.
378  */
379 static GList *
targets_from_commandline(void)380 targets_from_commandline( void )
381 {
382 	static const gchar *thisfn = "nautilus_actions_run_targets_from_commandline";
383 	GList *targets;
384 
385 	g_debug( "%s", thisfn );
386 
387 	targets = get_selection_from_strv(( const gchar ** ) targets_array, FALSE );
388 
389 	return( targets );
390 }
391 
392 static GList *
get_selection_from_strv(const gchar ** strv,gboolean has_mimetype)393 get_selection_from_strv( const gchar **strv, gboolean has_mimetype )
394 {
395 	GList *list;
396 	gchar **iter;
397 	gchar *errmsg;
398 
399 	list = NULL;
400 	iter = ( gchar ** ) strv;
401 
402 	while( *iter ){
403 		const gchar *uri = ( const gchar * ) *iter;
404 		const gchar *mimetype = NULL;
405 		if( has_mimetype ){
406 			iter++;
407 			mimetype = ( const gchar * ) *iter;
408 		}
409 
410 		errmsg = NULL;
411 		NASelectedInfo *nsi = na_selected_info_create_for_uri( uri, mimetype, &errmsg );
412 
413 		if( errmsg ){
414 			g_printerr( "%s\n", errmsg );
415 			g_free( errmsg );
416 		}
417 
418 		if( nsi ){
419 			list = g_list_prepend( list, nsi );
420 		}
421 		iter++;
422 	}
423 
424 	return( g_list_reverse( list ));
425 }
426 
427 /*
428  * find a profile candidate to be executed for the given uris
429  */
430 static NAObjectProfile *
get_profile_for_targets(NAObjectAction * action,GList * targets)431 get_profile_for_targets( NAObjectAction *action, GList *targets )
432 {
433 	/*static const gchar *thisfn = "nautilus_actions_run_get_profile_for_targets";*/
434 	GList *profiles, *ip;
435 	NAObjectProfile *candidate;
436 
437 	candidate = NULL;
438 	profiles = na_object_get_items( action );
439 
440 	for( ip = profiles ; ip && !candidate ; ip = ip->next ){
441 		if( na_icontext_is_candidate( NA_ICONTEXT( ip->data ), ITEM_TARGET_ANY, targets )){
442 			candidate = NA_OBJECT_PROFILE( ip->data );
443 		}
444 	}
445 
446 	return( candidate );
447 }
448 
449 static void
execute_action(NAObjectAction * action,NAObjectProfile * profile,GList * targets)450 execute_action( NAObjectAction *action, NAObjectProfile *profile, GList *targets )
451 {
452 	/*static const gchar *thisfn = "nautilus_action_run_execute_action";*/
453 	NATokens *tokens;
454 
455 	tokens = na_tokens_new_from_selection( targets );
456 	na_tokens_execute_action( tokens, profile );
457 }
458 
459 /*
460  *
461  */
462 static void
dump_targets(GList * targets)463 dump_targets( GList *targets )
464 {
465 	GList *it;
466 	gchar *uri, *mimetype;
467 
468 	for( it = targets ; it ; it = it->next ){
469 		NASelectedInfo *nsi = NA_SELECTED_INFO( it->data );
470 		uri = na_selected_info_get_uri( nsi );
471 		mimetype = na_selected_info_get_mime_type( nsi );
472 		g_print( "%s\t[%s]\n", uri, mimetype );
473 		g_free( mimetype );
474 		g_free( uri );
475 	}
476 }
477 
478 /*
479  * print a help message and exit with failure
480  */
481 static void
exit_with_usage(void)482 exit_with_usage( void )
483 {
484 	g_printerr( _( "Try %s --help for usage.\n" ), g_get_prgname());
485 	exit( EXIT_FAILURE );
486 }
487