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