1 /* ************************************************************************** */
2 /* */
3 /* Copyright (C) 2000-2008 Cédric Auger (cedric@grisbi.org) */
4 /* https://www.grisbi.org */
5 /* */
6 /* This program is free software; you can redistribute it and/or modify */
7 /* it under the terms of the GNU General Public License as published by */
8 /* the Free Software Foundation; either version 2 of the License, or */
9 /* (at your option) any later version. */
10 /* */
11 /* This program is distributed in the hope that it will be useful, */
12 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 /* GNU General Public License for more details. */
15 /* */
16 /* You should have received a copy of the GNU General Public License */
17 /* along with this program; if not, write to the Free Software */
18 /* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
19 /* */
20 /* ************************************************************************** */
21
22 /**
23 * \file file_obfuscate.c
24 * anonymyze the file to send with bugreports
25 */
26
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include "include.h"
33 #include <stdlib.h>
34 #include <glib/gi18n.h>
35
36 /*START_INCLUDE*/
37 #include "file_obfuscate.h"
38 #include "bet_data.h"
39 #include "dialog.h"
40 #include "grisbi_win.h"
41 #include "gsb_assistant.h"
42 #include "gsb_data_account.h"
43 #include "gsb_data_bank.h"
44 #include "gsb_data_budget.h"
45 #include "gsb_data_category.h"
46 #include "gsb_data_partial_balance.h"
47 #include "gsb_data_payee.h"
48 #include "gsb_data_reconcile.h"
49 #include "gsb_data_report.h"
50 #include "gsb_data_scheduled.h"
51 #include "gsb_data_transaction.h"
52 #include "gsb_dirs.h"
53 #include "gsb_file_save.h"
54 #include "gsb_file_util.h"
55 #include "structures.h"
56 #include "utils.h"
57 /*END_INCLUDE*/
58
59
60 /*START_STATIC*/
61 static GtkWidget *file_obfuscate_page_1 ( void );
62 static GtkWidget *file_obfuscate_page_2 ( void );
63 /*END_STATIC*/
64
65 /*START_EXTERN*/
66 /*END_EXTERN*/
67
68
69 static GtkWidget *button_accounts_details;
70 static GtkWidget *button_amount;
71 static GtkWidget *button_accounts_names;
72 static GtkWidget *button_payee;
73 static GtkWidget *button_categories;
74 static GtkWidget *button_budgets;
75 static GtkWidget *button_notes;
76 static GtkWidget *button_banks;
77 static GtkWidget *button_reports;
78 static GtkWidget *button_reconcile;
79
80 /**
81 * called by menubar to obfuscate the file
82 *
83 * \param
84 *
85 * \return TRUE
86 * */
file_obfuscate_run(void)87 gboolean file_obfuscate_run ( void )
88 {
89 GtkWidget *assistant;
90 gint result;
91
92 grisbi_win_status_bar_message ( _("Obfuscating file...") );
93
94 assistant = gsb_assistant_new ( _("Grisbi file obfuscation"),
95 _("This assistant produces anonymized copies of account files, with "
96 "all personal data replaced with harmless random data, in order to "
97 "attach an anonimized copy of your Grisbi file with any bug report "
98 "you submit."
99 "\n\n"
100 "That said, please check that bugs you submit are still valid with "
101 "anonymized version of your files.\n"
102 "\n"
103 "To avoid any problems in your file, after saving the modified file, "
104 "Grisbi will close without letting you saving anything. "
105 "So if you didn't save your changes, please stop this assistant, "
106 "save your work and restart the obfuscation process.\n\n"
107 "In next page, you will be able to select individual features to "
108 "obfuscate or to keep depending on the level of privacy needed."),
109 "gsb-bug-32.png",
110 NULL );
111
112 gsb_assistant_add_page ( assistant, file_obfuscate_page_1 (), 1, 0, 2, NULL );
113 gsb_assistant_add_page ( assistant, file_obfuscate_page_2 (), 2, 1, -1, NULL );
114
115 result = gsb_assistant_run ( assistant );
116
117 if ( result == GTK_RESPONSE_APPLY )
118 {
119 /* obfuscate the file */
120 GSList *tmp_list;
121 gchar *filename;
122 gchar *nom_fichier_comptes;
123
124 /* on récupère d'abord le nom du fichier */
125 nom_fichier_comptes = g_strdup (grisbi_win_get_filename (NULL));
126 /* remove the swp file */
127 gsb_file_util_modify_lock (nom_fichier_comptes, FALSE);
128
129 /* hide the accounts data */
130 tmp_list = gsb_data_account_get_list_accounts ();
131 while ( tmp_list )
132 {
133 gint account_number = gsb_data_account_get_no_account ( tmp_list -> data );
134
135 /* hide the IBAN number */
136 gsb_data_account_set_bank_account_iban ( account_number, NULL );
137
138 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_accounts_details ) ) )
139 {
140 /* hide the details of account but not the names */
141 gchar *tmp_str;
142
143 tmp_str = g_strdup_printf ( "id account %d", account_number );
144 gsb_data_account_set_id ( account_number, tmp_str );
145 g_free ( tmp_str );
146
147 gsb_data_account_set_comment (account_number, NULL);
148 gsb_data_account_set_holder_name (account_number, NULL);
149 gsb_data_account_set_holder_address (account_number, NULL);
150 gsb_data_account_set_init_balance (account_number, null_real);
151 gsb_data_account_set_mini_balance_wanted (account_number, null_real);
152 gsb_data_account_set_mini_balance_authorized (account_number, null_real);
153 gsb_data_account_set_bank_branch_code (account_number, NULL);
154 gsb_data_account_set_bank_account_number (account_number, NULL);
155 gsb_data_account_set_bank_account_key (account_number, NULL);
156 }
157
158 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_accounts_names ) ) )
159 {
160 /* hide the accounts names */
161 gchar *tmp_str;
162
163 tmp_str = g_strdup_printf ( "Account n°%d", account_number );
164 gsb_data_account_set_name ( account_number, tmp_str );
165 g_free ( tmp_str );
166
167 }
168
169 /* hide the budgetary data */
170 if ( gsb_data_account_get_bet_use_budget ( account_number ) )
171 {
172 /* hide the historiques data */
173 gint bet_hist_data;
174
175 bet_hist_data = gsb_data_account_get_bet_hist_data ( account_number );
176
177 if ( bet_hist_data == 0 && gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_categories ) ) )
178 {
179 bet_data_hist_reset_all_amounts ( account_number );
180 }
181 if ( bet_hist_data == 1 && gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_budgets ) ) )
182 {
183 bet_data_hist_reset_all_amounts ( account_number );
184 }
185 gsb_data_account_set_bet_finance_capital ( account_number, 0.0 );
186 gsb_data_account_set_bet_finance_taux_annuel ( account_number, 0.0 );
187 gsb_data_account_set_bet_finance_frais ( account_number, 0.0 );
188 gsb_data_account_set_bet_finance_capital ( account_number, 0.0 );
189 gsb_data_account_set_bet_months ( account_number, 0 );
190 }
191
192 tmp_list = tmp_list -> next;
193 }
194
195 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_accounts_names ) ) )
196 {
197 /* hide the partial balance accounts names */
198 tmp_list = gsb_data_partial_balance_get_list ();
199 while ( tmp_list )
200 {
201 gint partial_balance_number;
202 gchar *tmp_str;
203
204 partial_balance_number = gsb_data_partial_balance_get_number ( tmp_list->data );
205 tmp_str = g_strdup_printf ( "Partial balance n°%d", partial_balance_number );
206 gsb_data_partial_balance_set_name ( partial_balance_number, tmp_str );
207 g_free ( tmp_str );
208
209 tmp_list = tmp_list->next;
210 }
211 }
212
213 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_amount ) ) )
214 {
215 /* hide the amounts of transactions */
216 tmp_list = gsb_data_transaction_get_complete_transactions_list ();
217 while ( tmp_list )
218 {
219 gint transaction_number;
220
221 transaction_number = gsb_data_transaction_get_transaction_number ( tmp_list -> data );
222 gsb_data_transaction_set_amount ( transaction_number, null_real );
223 gsb_data_transaction_set_voucher ( transaction_number, NULL );
224 gsb_data_transaction_set_bank_references ( transaction_number, NULL );
225
226 tmp_list = tmp_list -> next;
227 }
228
229 /* hide the amounts of scheduled transactions */
230 tmp_list = gsb_data_scheduled_get_scheduled_list ();
231 while ( tmp_list )
232 {
233 gint scheduled_number;
234
235 scheduled_number = gsb_data_scheduled_get_scheduled_number ( tmp_list -> data );
236 gsb_data_scheduled_set_amount ( scheduled_number, null_real );
237 tmp_list = tmp_list -> next;
238 }
239 }
240
241 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_payee ) ) )
242 {
243 /* hide the payees names */
244 tmp_list = gsb_data_payee_get_payees_list ();
245 while ( tmp_list )
246 {
247 gint payee_number;
248 gchar *tmp_str;
249
250 payee_number = gsb_data_payee_get_no_payee ( tmp_list -> data );
251 tmp_str = g_strdup_printf ( "Payee n°%d", payee_number );
252 gsb_data_payee_set_name ( payee_number, tmp_str );
253 g_free ( tmp_str );
254
255 gsb_data_payee_set_description ( payee_number, NULL );
256 gsb_data_payee_set_search_string ( payee_number, NULL );
257
258 tmp_list = tmp_list -> next;
259 }
260 }
261
262 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_categories ) ) )
263 {
264 /* hide the categories */
265 tmp_list = gsb_data_category_get_categories_list ();
266 while ( tmp_list )
267 {
268 GSList *list_sub_categ;
269 gint category_number;
270 gchar *tmp_str;
271
272 category_number = gsb_data_category_get_no_category ( tmp_list -> data );
273 tmp_str = g_strdup_printf ( "Category n°%d", category_number );
274 gsb_data_category_set_name ( category_number, tmp_str );
275 g_free ( tmp_str );
276
277 list_sub_categ = gsb_data_category_get_sub_category_list ( category_number );
278 while ( list_sub_categ )
279 {
280 gint sub_categ_number;
281
282 sub_categ_number = gsb_data_category_get_no_sub_category ( list_sub_categ -> data );
283 tmp_str = g_strdup_printf ( "Sub-category n°%d", sub_categ_number );
284 gsb_data_category_set_sub_category_name ( category_number, sub_categ_number, tmp_str );
285 g_free ( tmp_str );
286
287 list_sub_categ = list_sub_categ -> next;
288 }
289 tmp_list = tmp_list -> next;
290 }
291 }
292
293 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_budgets ) ) )
294 {
295 /* hide the budgets */
296 tmp_list = gsb_data_budget_get_budgets_list ();
297 while ( tmp_list )
298 {
299 GSList *list_sub_budget;
300 gint budget_number;
301 gchar *tmp_str;
302
303 budget_number = gsb_data_budget_get_no_budget ( tmp_list -> data );
304 tmp_str = g_strdup_printf ( "Budget n°%d", budget_number );
305 gsb_data_budget_set_name ( budget_number, tmp_str );
306 g_free ( tmp_str );
307
308 list_sub_budget = gsb_data_budget_get_sub_budget_list (budget_number);
309 while ( list_sub_budget )
310 {
311 gint sub_budget_number;
312
313 sub_budget_number = gsb_data_budget_get_no_sub_budget (list_sub_budget -> data);
314 tmp_str = g_strdup_printf ( "Sub-budget n°%d", sub_budget_number );
315 gsb_data_budget_set_sub_budget_name ( budget_number, sub_budget_number, tmp_str );
316 g_free ( tmp_str );
317
318 list_sub_budget = list_sub_budget -> next;
319 }
320 tmp_list = tmp_list -> next;
321 }
322 }
323
324 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_notes ) ) )
325 {
326 /* hide the notes */
327 tmp_list = gsb_data_transaction_get_complete_transactions_list ();
328 while ( tmp_list )
329 {
330 gint transaction_number;
331
332 transaction_number = gsb_data_transaction_get_transaction_number ( tmp_list -> data );
333 gsb_data_transaction_set_notes ( transaction_number, NULL );
334
335 tmp_list = tmp_list -> next;
336 }
337
338 /* hide the notes of scheduled transactions */
339 tmp_list = gsb_data_scheduled_get_scheduled_list ();
340 while ( tmp_list )
341 {
342 gint scheduled_number;
343
344 scheduled_number = gsb_data_scheduled_get_scheduled_number ( tmp_list -> data );
345 gsb_data_scheduled_set_notes (scheduled_number, NULL);
346 tmp_list = tmp_list -> next;
347 }
348 }
349
350 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_banks ) ) )
351 {
352 /* hide the banks */
353 tmp_list = gsb_data_bank_get_bank_list ();
354 while ( tmp_list )
355 {
356 gint bank_number;
357 gchar *tmp_str;
358
359 bank_number = gsb_data_bank_get_no_bank ( tmp_list -> data );
360 tmp_str = g_strdup_printf ( "Bank n°%d", bank_number );
361 gsb_data_bank_set_name ( bank_number, tmp_str );
362 g_free ( tmp_str );
363
364 gsb_data_bank_set_code ( bank_number, NULL );
365 gsb_data_bank_set_bic ( bank_number, NULL );
366 gsb_data_bank_set_bank_address ( bank_number, NULL );
367 gsb_data_bank_set_bank_tel ( bank_number, NULL );
368 gsb_data_bank_set_bank_mail ( bank_number, NULL );
369 gsb_data_bank_set_bank_web ( bank_number, NULL );
370 gsb_data_bank_set_bank_note ( bank_number, NULL );
371 gsb_data_bank_set_correspondent_name ( bank_number, NULL );
372 gsb_data_bank_set_correspondent_tel ( bank_number, NULL );
373 gsb_data_bank_set_correspondent_mail ( bank_number, NULL );
374 gsb_data_bank_set_correspondent_fax ( bank_number, NULL );
375
376 tmp_list = tmp_list -> next;
377 }
378 }
379
380 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_reports ) ) )
381 {
382 /* hide the reports names*/
383 tmp_list = gsb_data_report_get_report_list ();
384 while ( tmp_list )
385 {
386 gint report_number;
387 gchar *tmp_str;
388
389 report_number = gsb_data_report_get_report_number ( tmp_list -> data );
390 tmp_str = g_strdup_printf ( "Report n°%d", report_number );
391 gsb_data_report_set_report_name ( report_number, tmp_str );
392 g_free ( tmp_str );
393
394 tmp_list = tmp_list -> next;
395 }
396 }
397
398 if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( button_reconcile ) ) )
399 {
400 /* hide the reconciliations */
401 GList *reconcile_list;
402 reconcile_list = gsb_data_reconcile_get_reconcile_list ();
403 while ( reconcile_list )
404 {
405 gint reconcile_number;
406
407 reconcile_number = gsb_data_reconcile_get_no_reconcile (reconcile_list -> data);
408 gsb_data_reconcile_set_init_balance (reconcile_number, null_real);
409 gsb_data_reconcile_set_final_balance (reconcile_number, null_real);
410
411 reconcile_list = reconcile_list -> next;
412 }
413 }
414
415 if ( nom_fichier_comptes )
416 {
417 /* remove the .gsb */
418 nom_fichier_comptes[strlen ( nom_fichier_comptes ) -4] = 0;
419 filename = g_strconcat ( nom_fichier_comptes, "-obfuscated.gsb", NULL);
420 }
421 else
422 filename = g_build_filename ( gsb_dirs_get_default_dir (), "No_name-obfuscated.gsb", NULL);
423
424 if ( gsb_file_save_save_file ( filename, FALSE,0 ) )
425 dialogue_hint ( g_strdup_printf ( _("Obfuscated file saved as\n'%s'"), filename ),
426 _("Obfuscation succeeded") );
427 else
428 dialogue_error_hint (g_strdup_printf (_("Grisbi couldn't save the file\n'%s'"), filename ),
429 _("Obfuscation failed") );
430
431 g_free ( filename );
432 g_free (nom_fichier_comptes);
433
434 /* bye bye */
435 exit (0);
436 }
437
438 gtk_widget_destroy ( assistant );
439 grisbi_win_status_bar_message ( _("Done.") );
440
441 return FALSE;
442 }
443
444
445
446 /**
447 * first page of the assistant
448 * to select what we want to anonymyze
449 *
450 * \param
451 *
452 * \return the new page
453 * */
file_obfuscate_page_1(void)454 GtkWidget *file_obfuscate_page_1 ( void )
455 {
456 GtkWidget *vbox, *paddingbox;
457 GtkWidget *button_everything;
458
459 vbox = gtk_box_new ( GTK_ORIENTATION_VERTICAL, MARGIN_BOX );
460 gtk_container_set_border_width ( GTK_CONTAINER ( vbox ), BOX_BORDER_WIDTH );
461
462 paddingbox = new_paddingbox_with_title ( vbox, FALSE, _("Select features to hide :\n") );
463
464 button_everything = gtk_check_button_new_with_label ( _("Hide everything") );
465 g_signal_connect ( button_everything, "toggled", G_CALLBACK ( radio_set_active_linked_widgets ), NULL );
466 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_everything, FALSE, FALSE, 0 );
467
468 button_accounts_names = gtk_check_button_new_with_label ( _("Hide accounts names") );
469 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_accounts_names, FALSE, FALSE, 0 );
470 register_button_as_linked ( button_everything, button_accounts_names );
471
472 button_accounts_details = gtk_check_button_new_with_label ( _("Hide accounts details") );
473 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_accounts_details, FALSE, FALSE, 0 );
474 register_button_as_linked ( button_everything, button_accounts_details );
475
476 button_amount = gtk_check_button_new_with_label ( _("Hide amounts") );
477 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_amount, FALSE, FALSE, 0 );
478 register_button_as_linked ( button_everything, button_amount );
479
480 button_payee = gtk_check_button_new_with_label ( _("Hide payees names") );
481 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_payee, FALSE, FALSE, 0 );
482 register_button_as_linked ( button_everything, button_payee );
483
484 button_categories = gtk_check_button_new_with_label ( _("Hide categories names") );
485 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_categories, FALSE, FALSE, 0 );
486 register_button_as_linked ( button_everything, button_categories );
487
488 button_budgets = gtk_check_button_new_with_label ( _("Hide budgets names") );
489 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_budgets, FALSE, FALSE, 0 );
490 register_button_as_linked ( button_everything, button_budgets );
491
492 button_notes = gtk_check_button_new_with_label ( _("Hide notes") );
493 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_notes, FALSE, FALSE, 0 );
494 register_button_as_linked ( button_everything, button_notes );
495
496 button_banks = gtk_check_button_new_with_label ( _("Hide banks details") );
497 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_banks, FALSE, FALSE, 0 );
498 register_button_as_linked ( button_everything, button_banks );
499
500 button_reports = gtk_check_button_new_with_label ( _("Hide reports names") );
501 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_reports, FALSE, FALSE, 0 );
502 register_button_as_linked ( button_everything, button_reports );
503
504 button_reconcile = gtk_check_button_new_with_label (_("Hide reconciliation names and amounts"));
505 gtk_box_pack_start ( GTK_BOX ( paddingbox ), button_reconcile, FALSE, FALSE, 0 );
506 register_button_as_linked ( button_everything, button_reconcile );
507
508 gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( button_everything ), TRUE );
509
510 gtk_widget_show_all ( vbox );
511 return vbox;
512 }
513
514
515
516 /**
517 * second page of the assistant
518 *
519 * \param
520 *
521 * \return the new page
522 * */
file_obfuscate_page_2(void)523 GtkWidget *file_obfuscate_page_2 ( void )
524 {
525 GtkWidget *vbox, *text_view;
526 GtkTextBuffer *buffer;
527 GtkTextIter iter;
528 gchar *text, *filename;
529 const gchar *nom_fichier_comptes;
530
531 vbox = gtk_box_new ( GTK_ORIENTATION_VERTICAL, MARGIN_BOX );
532
533 text_view = gtk_text_view_new ();
534 gtk_text_view_set_wrap_mode ( GTK_TEXT_VIEW ( text_view ), GTK_WRAP_WORD );
535 gtk_text_view_set_editable ( GTK_TEXT_VIEW ( text_view ), FALSE );
536 gtk_text_view_set_cursor_visible ( GTK_TEXT_VIEW ( text_view ), FALSE );
537 gtk_text_view_set_left_margin ( GTK_TEXT_VIEW ( text_view ), 12 );
538 gtk_text_view_set_right_margin ( GTK_TEXT_VIEW ( text_view ), 12 );
539
540 buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( text_view ) );
541 gtk_text_buffer_create_tag ( buffer, "bold", "weight", PANGO_WEIGHT_BOLD, NULL );
542 gtk_text_buffer_create_tag ( buffer, "x-large", "scale", PANGO_SCALE_X_LARGE, NULL );
543 gtk_text_buffer_create_tag ( buffer, "indented", "left-margin", 24, NULL );
544
545 gtk_text_buffer_get_iter_at_offset ( buffer, &iter, 1 );
546 gtk_text_buffer_insert ( buffer, &iter, "\n", -1 );
547
548 nom_fichier_comptes = grisbi_win_get_filename (NULL);
549 if ( nom_fichier_comptes )
550 {
551 gchar *base_filename;
552 gchar *complete_filename;
553
554 base_filename = g_strdup ( nom_fichier_comptes );
555 base_filename[strlen ( base_filename ) - 4] = 0;
556 complete_filename = g_strconcat ( base_filename, "-obfuscated.gsb", NULL );
557 filename = g_path_get_basename ( complete_filename );
558 g_free ( complete_filename );
559 g_free ( base_filename );
560 }
561 else
562 {
563 filename = g_strconcat ( "No_name-obfuscated.gsb", NULL );
564 }
565
566 text = g_strdup_printf ( _("Please press the 'Close' button to obfuscate your file\n\n"
567 "Obfuscated file will be named %s, in the same directory as original file.\n\n"
568 "Please check the bug is still there and send your file with the explanation to "
569 "make the bug at bugsreports@listes.grisbi.org or on the bugracker (Mantis) "
570 "at http://grisbi.tuxfamily.org/mantis/main_page.php\n\n"
571 "The account is saved in text, you may double check with a text editor if there "
572 "is no personal information anymore in this file."
573 "Grisbi will close immediately after saving the obfuscated file."),
574 filename );
575 gtk_text_buffer_insert ( buffer, &iter, text, -1 );
576
577
578 gtk_box_pack_start ( GTK_BOX ( vbox ), text_view, TRUE, TRUE, 0 );
579
580 g_free ( text );
581 g_free ( filename );
582
583 gtk_widget_show_all ( vbox );
584
585 return vbox;
586 }
587
588
589
590 /* Local Variables: */
591 /* c-basic-offset: 4 */
592 /* End: */
593