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 <api/na-core-utils.h>
35 #include <api/na-gconf-utils.h>
36 #include <api/na-object-api.h>
37 
38 #include "na-io-provider.h"
39 #include "na-settings.h"
40 #include "na-updater.h"
41 
42 /* private class data
43  */
44 struct _NAUpdaterClassPrivate {
45 	void *empty;						/* so that gcc -pedantic is happy */
46 };
47 
48 /* private instance data
49  */
50 struct _NAUpdaterPrivate {
51 	gboolean dispose_has_run;
52 	gboolean are_preferences_locked;
53 	gboolean is_level_zero_writable;
54 };
55 
56 static NAPivotClass *st_parent_class = NULL;
57 
58 static GType    register_type( void );
59 static void     class_init( NAUpdaterClass *klass );
60 static void     instance_init( GTypeInstance *instance, gpointer klass );
61 static void     instance_dispose( GObject *object );
62 static void     instance_finalize( GObject *object );
63 
64 static gboolean are_preferences_locked( const NAUpdater *updater );
65 static gboolean is_level_zero_writable( const NAUpdater *updater );
66 static void     set_writability_status( NAObjectItem *item, const NAUpdater *updater );
67 
68 GType
na_updater_get_type(void)69 na_updater_get_type( void )
70 {
71 	static GType object_type = 0;
72 
73 	if( !object_type ){
74 		object_type = register_type();
75 	}
76 
77 	return( object_type );
78 }
79 
80 static GType
register_type(void)81 register_type( void )
82 {
83 	static const gchar *thisfn = "na_updater_register_type";
84 	GType type;
85 
86 	static GTypeInfo info = {
87 		sizeof( NAUpdaterClass ),
88 		( GBaseInitFunc ) NULL,
89 		( GBaseFinalizeFunc ) NULL,
90 		( GClassInitFunc ) class_init,
91 		NULL,
92 		NULL,
93 		sizeof( NAUpdater ),
94 		0,
95 		( GInstanceInitFunc ) instance_init
96 	};
97 
98 	g_debug( "%s", thisfn );
99 
100 	type = g_type_register_static( NA_TYPE_PIVOT, "NAUpdater", &info, 0 );
101 
102 	return( type );
103 }
104 
105 static void
class_init(NAUpdaterClass * klass)106 class_init( NAUpdaterClass *klass )
107 {
108 	static const gchar *thisfn = "na_updater_class_init";
109 	GObjectClass *object_class;
110 
111 	g_debug( "%s: klass=%p", thisfn, ( void * ) klass );
112 
113 	st_parent_class = g_type_class_peek_parent( klass );
114 
115 	object_class = G_OBJECT_CLASS( klass );
116 	object_class->dispose = instance_dispose;
117 	object_class->finalize = instance_finalize;
118 
119 	klass->private = g_new0( NAUpdaterClassPrivate, 1 );
120 }
121 
122 static void
instance_init(GTypeInstance * instance,gpointer klass)123 instance_init( GTypeInstance *instance, gpointer klass )
124 {
125 	static const gchar *thisfn = "na_updater_instance_init";
126 	NAUpdater *self;
127 
128 	g_return_if_fail( NA_IS_UPDATER( instance ));
129 
130 	g_debug( "%s: instance=%p (%s), klass=%p",
131 			thisfn, ( void * ) instance, G_OBJECT_TYPE_NAME( instance ), ( void * ) klass );
132 
133 	self = NA_UPDATER( instance );
134 
135 	self->private = g_new0( NAUpdaterPrivate, 1 );
136 
137 	self->private->dispose_has_run = FALSE;
138 }
139 
140 static void
instance_dispose(GObject * object)141 instance_dispose( GObject *object )
142 {
143 	static const gchar *thisfn = "na_updater_instance_dispose";
144 	NAUpdater *self;
145 
146 	g_return_if_fail( NA_IS_UPDATER( object ));
147 
148 	self = NA_UPDATER( object );
149 
150 	if( !self->private->dispose_has_run ){
151 
152 		g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));
153 
154 		self->private->dispose_has_run = TRUE;
155 
156 		/* chain up to the parent class */
157 		if( G_OBJECT_CLASS( st_parent_class )->dispose ){
158 			G_OBJECT_CLASS( st_parent_class )->dispose( object );
159 		}
160 	}
161 }
162 
163 static void
instance_finalize(GObject * object)164 instance_finalize( GObject *object )
165 {
166 	static const gchar *thisfn = "na_updater_instance_finalize";
167 	NAUpdater *self;
168 
169 	g_return_if_fail( NA_IS_UPDATER( object ));
170 
171 	g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));
172 
173 	self = NA_UPDATER( object );
174 
175 	g_free( self->private );
176 
177 	/* chain call to parent class */
178 	if( G_OBJECT_CLASS( st_parent_class )->finalize ){
179 		G_OBJECT_CLASS( st_parent_class )->finalize( object );
180 	}
181 }
182 
183 /*
184  * na_updater_new:
185  *
186  * Returns: a newly allocated #NAUpdater object.
187  */
188 NAUpdater *
na_updater_new(void)189 na_updater_new( void )
190 {
191 	static const gchar *thisfn = "na_updater_new";
192 	NAUpdater *updater;
193 
194 	g_debug( "%s", thisfn );
195 
196 	updater = g_object_new( NA_TYPE_UPDATER, NULL );
197 
198 	updater->private->are_preferences_locked = are_preferences_locked( updater );
199 	updater->private->is_level_zero_writable = is_level_zero_writable( updater );
200 
201 	g_debug( "%s: is_level_zero_writable=%s",
202 			thisfn,
203 			updater->private->is_level_zero_writable ? "True":"False" );
204 
205 	return( updater );
206 }
207 
208 static gboolean
are_preferences_locked(const NAUpdater * updater)209 are_preferences_locked( const NAUpdater *updater )
210 {
211 	gboolean are_locked;
212 	gboolean mandatory;
213 
214 	are_locked = na_settings_get_boolean( NA_IPREFS_ADMIN_PREFERENCES_LOCKED, NULL, &mandatory );
215 
216 	return( are_locked && mandatory );
217 }
218 
219 static gboolean
is_level_zero_writable(const NAUpdater * updater)220 is_level_zero_writable( const NAUpdater *updater )
221 {
222 	GSList *level_zero;
223 	gboolean mandatory;
224 
225 	level_zero = na_settings_get_string_list( NA_IPREFS_ITEMS_LEVEL_ZERO_ORDER, NULL, &mandatory );
226 
227 	na_core_utils_slist_free( level_zero );
228 
229 	g_debug( "na_updater_is_level_zero_writable: NA_IPREFS_ITEMS_LEVEL_ZERO_ORDER: mandatory=%s",
230 			mandatory ? "True":"False" );
231 
232 	return( !mandatory );
233 }
234 
235 /*
236  * na_updater_check_item_writability_status:
237  * @updater: this #NAUpdater object.
238  * @item: the #NAObjectItem to be written.
239  *
240  * Compute and set the writability status of the @item.
241  *
242  * For an item be actually writable:
243  * - the item must not be itself in a read-only store, which has been
244  *   checked when first reading it
245  * - the provider must be willing (resp. able) to write
246  * - the provider must not has been locked by the admin, nor by the user
247  *
248  * If the item does not have a parent, then the level zero must be writable.
249  */
250 void
na_updater_check_item_writability_status(const NAUpdater * updater,const NAObjectItem * item)251 na_updater_check_item_writability_status( const NAUpdater *updater, const NAObjectItem *item )
252 {
253 	gboolean writable;
254 	NAIOProvider *provider;
255 	NAObjectItem *parent;
256 	guint reason;
257 
258 	g_return_if_fail( NA_IS_UPDATER( updater ));
259 	g_return_if_fail( NA_IS_OBJECT_ITEM( item ));
260 
261 	writable = FALSE;
262 	reason = NA_IIO_PROVIDER_STATUS_UNDETERMINED;
263 
264 	if( !updater->private->dispose_has_run ){
265 
266 		writable = TRUE;
267 		reason = NA_IIO_PROVIDER_STATUS_WRITABLE;
268 
269 		/* Writability status of the item has been determined at load time
270 		 * (cf. e.g. io-desktop/nadp-reader.c:read_done_item_is_writable()).
271 		 * Though I'm plenty conscious that this status is subject to many
272 		 * changes during the life of the item (e.g. by modifying permissions
273 		 * on the underlying store), it is just more efficient to not reevaluate
274 		 * this status each time we need it, and enough for our needs..
275 		 */
276 		if( writable ){
277 			if( na_object_is_readonly( item )){
278 				writable = FALSE;
279 				reason = NA_IIO_PROVIDER_STATUS_ITEM_READONLY;
280 			}
281 		}
282 
283 		if( writable ){
284 			provider = na_object_get_provider( item );
285 			if( provider ){
286 				writable = na_io_provider_is_finally_writable( provider, &reason );
287 
288 			/* the get_writable_provider() api already takes care of above checks
289 			 */
290 			} else {
291 				provider = na_io_provider_find_writable_io_provider( NA_PIVOT( updater ));
292 				if( !provider ){
293 					writable = FALSE;
294 					reason = NA_IIO_PROVIDER_STATUS_NO_PROVIDER_FOUND;
295 				}
296 			}
297 		}
298 
299 		/* if needed, the level zero must be writable
300 		 */
301 		if( writable ){
302 			parent = ( NAObjectItem * ) na_object_get_parent( item );
303 			if( !parent ){
304 				if( updater->private->is_level_zero_writable ){
305 					reason = NA_IIO_PROVIDER_STATUS_LEVEL_ZERO;
306 				}
307 			}
308 		}
309 	}
310 
311 	na_object_set_writability_status( item, writable, reason );
312 }
313 
314 /*
315  * na_updater_are_preferences_locked:
316  * @updater: the #NAUpdater application object.
317  *
318  * Returns: %TRUE if preferences have been globally locked down by an
319  * admin, %FALSE else.
320  */
321 gboolean
na_updater_are_preferences_locked(const NAUpdater * updater)322 na_updater_are_preferences_locked( const NAUpdater *updater )
323 {
324 	gboolean are_locked;
325 
326 	g_return_val_if_fail( NA_IS_UPDATER( updater ), TRUE );
327 
328 	are_locked = TRUE;
329 
330 	if( !updater->private->dispose_has_run ){
331 
332 		are_locked = updater->private->are_preferences_locked;
333 	}
334 
335 	return( are_locked );
336 }
337 
338 /*
339  * na_updater_is_level_zero_writable:
340  * @updater: the #NAUpdater application object.
341  *
342  * As of 3.1.0, level-zero is written as a user preference.
343  *
344  * This function considers that the level_zero is writable if it is not
345  * a mandatory preference.
346  * Whether preferences themselves are or not globally locked is not
347  * considered here (as imho, level zero is not really and semantically
348  * part of user preferences).
349  *
350  * This function only considers the case of the level zero itself.
351  * It does not take into account whether the i/o provider (if any)
352  * is writable, or if the item iself is not read only.
353  *
354  * Returns: %TRUE if we are able to update the level-zero list of items,
355  * %FALSE else.
356  */
357 gboolean
na_updater_is_level_zero_writable(const NAUpdater * updater)358 na_updater_is_level_zero_writable( const NAUpdater *updater )
359 {
360 	gboolean is_writable;
361 
362 	g_return_val_if_fail( NA_IS_UPDATER( updater ), FALSE );
363 
364 	is_writable = FALSE;
365 
366 	if( !updater->private->dispose_has_run ){
367 
368 		is_writable = updater->private->is_level_zero_writable;
369 	}
370 
371 	return( is_writable );
372 }
373 
374 /*
375  * na_updater_append_item:
376  * @updater: this #NAUpdater object.
377  * @item: a #NAObjectItem-derived object to be appended to the tree.
378  *
379  * Append a new item at the end of the global tree.
380  */
381 void
na_updater_append_item(NAUpdater * updater,NAObjectItem * item)382 na_updater_append_item( NAUpdater *updater, NAObjectItem *item )
383 {
384 	GList *tree;
385 
386 	g_return_if_fail( NA_IS_UPDATER( updater ));
387 	g_return_if_fail( NA_IS_OBJECT_ITEM( item ));
388 
389 	if( !updater->private->dispose_has_run ){
390 
391 		g_object_get( G_OBJECT( updater ), PIVOT_PROP_TREE, &tree, NULL );
392 		tree = g_list_append( tree, item );
393 		g_object_set( G_OBJECT( updater ), PIVOT_PROP_TREE, tree, NULL );
394 	}
395 }
396 
397 /*
398  * na_updater_insert_item:
399  * @updater: this #NAUpdater object.
400  * @item: a #NAObjectItem-derived object to be inserted in the tree.
401  * @parent_id: the id of the parent, or %NULL.
402  * @pos: the position in the children of the parent, starting at zero, or -1.
403  *
404  * Insert a new item in the global tree.
405  */
406 void
na_updater_insert_item(NAUpdater * updater,NAObjectItem * item,const gchar * parent_id,gint pos)407 na_updater_insert_item( NAUpdater *updater, NAObjectItem *item, const gchar *parent_id, gint pos )
408 {
409 	GList *tree;
410 	NAObjectItem *parent;
411 
412 	g_return_if_fail( NA_IS_UPDATER( updater ));
413 	g_return_if_fail( NA_IS_OBJECT_ITEM( item ));
414 
415 	if( !updater->private->dispose_has_run ){
416 
417 		parent = NULL;
418 		g_object_get( G_OBJECT( updater ), PIVOT_PROP_TREE, &tree, NULL );
419 
420 		if( parent_id ){
421 			parent = na_pivot_get_item( NA_PIVOT( updater ), parent_id );
422 		}
423 
424 		if( parent ){
425 			na_object_insert_at( parent, item, pos );
426 
427 		} else {
428 			tree = g_list_append( tree, item );
429 			g_object_set( G_OBJECT( updater ), PIVOT_PROP_TREE, tree, NULL );
430 		}
431 	}
432 }
433 
434 /*
435  * na_updater_remove_item:
436  * @updater: this #NAPivot instance.
437  * @item: the #NAObjectItem to be removed from the list.
438  *
439  * Removes a #NAObjectItem from the hierarchical tree. Does not delete it.
440  */
441 void
na_updater_remove_item(NAUpdater * updater,NAObject * item)442 na_updater_remove_item( NAUpdater *updater, NAObject *item )
443 {
444 	GList *tree;
445 	NAObjectItem *parent;
446 
447 	g_return_if_fail( NA_IS_PIVOT( updater ));
448 
449 	if( !updater->private->dispose_has_run ){
450 
451 		g_debug( "na_updater_remove_item: updater=%p, item=%p (%s)",
452 				( void * ) updater,
453 				( void * ) item, G_IS_OBJECT( item ) ? G_OBJECT_TYPE_NAME( item ) : "(null)" );
454 
455 		parent = na_object_get_parent( item );
456 		if( parent ){
457 			tree = na_object_get_items( parent );
458 			tree = g_list_remove( tree, ( gconstpointer ) item );
459 			na_object_set_items( parent, tree );
460 
461 		} else {
462 			g_object_get( G_OBJECT( updater ), PIVOT_PROP_TREE, &tree, NULL );
463 			tree = g_list_remove( tree, ( gconstpointer ) item );
464 			g_object_set( G_OBJECT( updater ), PIVOT_PROP_TREE, tree, NULL );
465 		}
466 	}
467 }
468 
469 /**
470  * na_updater_should_pasted_be_relabeled:
471  * @updater: this #NAUpdater instance.
472  * @object: the considered #NAObject-derived object.
473  *
474  * Whether the specified object should be relabeled when pasted ?
475  *
476  * Returns: %TRUE if the object should be relabeled, %FALSE else.
477  */
478 gboolean
na_updater_should_pasted_be_relabeled(const NAUpdater * updater,const NAObject * item)479 na_updater_should_pasted_be_relabeled( const NAUpdater *updater, const NAObject *item )
480 {
481 	static const gchar *thisfn = "na_updater_should_pasted_be_relabeled";
482 	gboolean relabel;
483 
484 	if( NA_IS_OBJECT_MENU( item )){
485 		relabel = na_settings_get_boolean( NA_IPREFS_RELABEL_DUPLICATE_MENU, NULL, NULL );
486 
487 	} else if( NA_IS_OBJECT_ACTION( item )){
488 		relabel = na_settings_get_boolean( NA_IPREFS_RELABEL_DUPLICATE_ACTION, NULL, NULL );
489 
490 	} else if( NA_IS_OBJECT_PROFILE( item )){
491 		relabel = na_settings_get_boolean( NA_IPREFS_RELABEL_DUPLICATE_PROFILE, NULL, NULL );
492 
493 	} else {
494 		g_warning( "%s: unknown item type at %p", thisfn, ( void * ) item );
495 		g_return_val_if_reached( FALSE );
496 	}
497 
498 	return( relabel );
499 }
500 
501 /*
502  * na_updater_load_items:
503  * @updater: this #NAUpdater instance.
504  *
505  * Loads the items, updating simultaneously their writability status.
506  *
507  * Returns: a pointer (not a ref) on the loaded tree.
508  *
509  * Since: 3.1
510  */
511 GList *
na_updater_load_items(NAUpdater * updater)512 na_updater_load_items( NAUpdater *updater )
513 {
514 	static const gchar *thisfn = "na_updater_load_items";
515 	GList *tree;
516 
517 	g_return_val_if_fail( NA_IS_UPDATER( updater ), NULL );
518 
519 	tree = NULL;
520 
521 	if( !updater->private->dispose_has_run ){
522 		g_debug( "%s: updater=%p (%s)", thisfn, ( void * ) updater, G_OBJECT_TYPE_NAME( updater ));
523 
524 		na_pivot_load_items( NA_PIVOT( updater ));
525 		tree = na_pivot_get_items( NA_PIVOT( updater ));
526 		g_list_foreach( tree, ( GFunc ) set_writability_status, ( gpointer ) updater );
527 	}
528 
529 	return( tree );
530 }
531 
532 static void
set_writability_status(NAObjectItem * item,const NAUpdater * updater)533 set_writability_status( NAObjectItem *item, const NAUpdater *updater )
534 {
535 	GList *children;
536 
537 	na_updater_check_item_writability_status( updater, item );
538 
539 	if( NA_IS_OBJECT_MENU( item )){
540 		children = na_object_get_items( item );
541 		g_list_foreach( children, ( GFunc ) set_writability_status, ( gpointer ) updater );
542 	}
543 }
544 
545 /*
546  * na_updater_write_item:
547  * @updater: this #NAUpdater instance.
548  * @item: a #NAObjectItem to be written down to the storage subsystem.
549  * @messages: the I/O provider can allocate and store here its error
550  * messages.
551  *
552  * Writes an item (an action or a menu).
553  *
554  * Returns: the #NAIIOProvider return code.
555  */
556 guint
na_updater_write_item(const NAUpdater * updater,NAObjectItem * item,GSList ** messages)557 na_updater_write_item( const NAUpdater *updater, NAObjectItem *item, GSList **messages )
558 {
559 	guint ret;
560 
561 	ret = NA_IIO_PROVIDER_CODE_PROGRAM_ERROR;
562 
563 	g_return_val_if_fail( NA_IS_UPDATER( updater ), ret );
564 	g_return_val_if_fail( NA_IS_OBJECT_ITEM( item ), ret );
565 	g_return_val_if_fail( messages, ret );
566 
567 	if( !updater->private->dispose_has_run ){
568 
569 		NAIOProvider *provider = na_object_get_provider( item );
570 
571 		if( !provider ){
572 			provider = na_io_provider_find_writable_io_provider( NA_PIVOT( updater ));
573 			g_return_val_if_fail( provider, NA_IIO_PROVIDER_STATUS_NO_PROVIDER_FOUND );
574 		}
575 
576 		if( provider ){
577 			ret = na_io_provider_write_item( provider, item, messages );
578 		}
579 	}
580 
581 	return( ret );
582 }
583 
584 /*
585  * na_updater_delete_item:
586  * @updater: this #NAUpdater instance.
587  * @item: the #NAObjectItem to be deleted from the storage subsystem.
588  * @messages: the I/O provider can allocate and store here its error
589  * messages.
590  *
591  * Deletes an item, action or menu, from the I/O storage subsystem.
592  *
593  * Returns: the #NAIIOProvider return code.
594  *
595  * Note that a new item, not already written to an I/O subsystem,
596  * doesn't have any attached provider. We so do nothing and return OK...
597  */
598 guint
na_updater_delete_item(const NAUpdater * updater,const NAObjectItem * item,GSList ** messages)599 na_updater_delete_item( const NAUpdater *updater, const NAObjectItem *item, GSList **messages )
600 {
601 	guint ret;
602 	NAIOProvider *provider;
603 
604 	g_return_val_if_fail( NA_IS_UPDATER( updater ), NA_IIO_PROVIDER_CODE_PROGRAM_ERROR );
605 	g_return_val_if_fail( NA_IS_OBJECT_ITEM( item ), NA_IIO_PROVIDER_CODE_PROGRAM_ERROR );
606 	g_return_val_if_fail( messages, NA_IIO_PROVIDER_CODE_PROGRAM_ERROR );
607 
608 	ret = NA_IIO_PROVIDER_CODE_OK;
609 
610 	if( !updater->private->dispose_has_run ){
611 
612 		provider = na_object_get_provider( item );
613 
614 		/* provider may be NULL if the item has been deleted from the UI
615 		 * without having been ever saved
616 		 */
617 		if( provider ){
618 			g_return_val_if_fail( NA_IS_IO_PROVIDER( provider ), NA_IIO_PROVIDER_CODE_PROGRAM_ERROR );
619 			ret = na_io_provider_delete_item( provider, item, messages );
620 		}
621 	}
622 
623 	return( ret );
624 }
625