GnuCash  5.6-150-g038405b370+
gnc-file.c
1 /********************************************************************\
2  * FileDialog.c -- file-handling utility dialogs for gnucash. *
3  * *
4  * Copyright (C) 1997 Robin D. Clark *
5  * Copyright (C) 1998, 1999, 2000 Linas Vepstas *
6  * *
7  * This program is free software; you can redistribute it and/or *
8  * modify it under the terms of the GNU General Public License as *
9  * published by the Free Software Foundation; either version 2 of *
10  * the License, or (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License*
18  * along with this program; if not, write to the Free Software *
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
20 \********************************************************************/
21 
22 #include <config.h>
23 
24 #include <gtk/gtk.h>
25 #include <glib/gi18n.h>
26 #include <errno.h>
27 #include <string.h>
28 
29 #include "dialog-utils.h"
30 #include "assistant-xml-encoding.h"
31 #include "gnc-commodity.h"
32 #include "gnc-component-manager.h"
33 #include "gnc-engine.h"
34 #include "Account.h"
35 #include "gnc-file.h"
36 #include "gnc-features.h"
37 #include "gnc-filepath-utils.h"
38 #include "gnc-glib-utils.h"
39 #include "gnc-gui-query.h"
40 #include "gnc-hooks.h"
41 #include "gnc-keyring.h"
42 #include "gnc-splash.h"
43 #include "gnc-ui.h"
44 #include "gnc-ui-balances.h"
45 #include "gnc-ui-util.h"
46 #include "gnc-uri-utils.h"
47 #include "gnc-window.h"
49 #include "qof.h"
50 #include "Scrub.h"
51 #include "ScrubBudget.h"
52 #include "TransLog.h"
53 #include "gnc-session.h"
54 #include "gnc-state.h"
55 #include "gnc-autosave.h"
56 #include <gnc-sx-instance-model.h>
57 #include <SX-book.h>
58 
60 /* This static indicates the debugging module that this .o belongs to. */
61 static QofLogModule log_module = GNC_MOD_GUI;
62 
63 static GNCShutdownCB shutdown_cb = NULL;
64 static gint save_in_progress = 0;
65 
66 // gnc_file_dialog_int is used both by gnc_file_dialog and gnc_file_dialog_multi
67 static GSList *
68 gnc_file_dialog_int (GtkWindow *parent,
69  const char * title,
70  GList * filters,
71  const char * starting_dir,
72  GNCFileDialogType type,
73  gboolean multi
74  )
75 {
76  GtkWidget *file_box;
77  char *file_name = NULL;
78  gchar * okbutton = NULL;
79  const gchar *ok_icon = NULL;
80  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
81  gint response;
82  GSList* file_name_list = NULL;
83 
84  ENTER(" ");
85 
86  switch (type)
87  {
88  case GNC_FILE_DIALOG_OPEN:
89  action = GTK_FILE_CHOOSER_ACTION_OPEN;
90  okbutton = _("_Open");
91  if (title == NULL)
92  title = _("Open");
93  break;
94  case GNC_FILE_DIALOG_IMPORT:
95  action = GTK_FILE_CHOOSER_ACTION_OPEN;
96  okbutton = _("_Import");
97  if (title == NULL)
98  title = _("Import");
99  break;
100  case GNC_FILE_DIALOG_SAVE:
101  action = GTK_FILE_CHOOSER_ACTION_SAVE;
102  okbutton = _("_Save");
103  if (title == NULL)
104  title = _("Save");
105  break;
106  case GNC_FILE_DIALOG_EXPORT:
107  action = GTK_FILE_CHOOSER_ACTION_SAVE;
108  okbutton = _("_Export");
109  ok_icon = "go-next";
110  if (title == NULL)
111  title = _("Export");
112  break;
113 
114  }
115 
116  file_box = gtk_file_chooser_dialog_new(
117  title,
118  parent,
119  action,
120  _("_Cancel"), GTK_RESPONSE_CANCEL,
121  NULL);
122  if (multi)
123  gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (file_box), TRUE);
124 
125  if (ok_icon)
126  gnc_gtk_dialog_add_button(file_box, okbutton, ok_icon, GTK_RESPONSE_ACCEPT);
127  else
128  gtk_dialog_add_button(GTK_DIALOG(file_box),
129  okbutton, GTK_RESPONSE_ACCEPT);
130 
131  if (starting_dir)
132  gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (file_box),
133  starting_dir);
134 
135  gtk_window_set_modal(GTK_WINDOW(file_box), TRUE);
136 
137  if (filters != NULL)
138  {
139  GList* filter;
140  GtkFileFilter* all_filter = gtk_file_filter_new();
141 
142  for (filter = filters; filter; filter = filter->next)
143  {
144  g_return_val_if_fail(GTK_IS_FILE_FILTER(filter->data), NULL);
145  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_box),
146  GTK_FILE_FILTER (filter->data));
147  }
148 
149  gtk_file_filter_set_name (all_filter, _("All files"));
150  gtk_file_filter_add_pattern (all_filter, "*");
151  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_box), all_filter);
152 
153  /* Note: You cannot set a file filter and preselect a file name.
154  * The latter wins, and the filter ends up disabled. Since we are
155  * only setting the starting directory for the chooser dialog,
156  * everything works as expected. */
157  gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (file_box),
158  GTK_FILE_FILTER (filters->data));
159  g_list_free (filters);
160  }
161 
162  response = gtk_dialog_run(GTK_DIALOG(file_box));
163 
164  // Set the name for this dialog so it can be easily manipulated with css
165  gtk_widget_set_name (GTK_WIDGET(file_box), "gnc-id-file");
166 
167  if (response == GTK_RESPONSE_ACCEPT)
168  {
169  if (multi)
170  {
171  file_name_list = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (file_box));
172  }
173  else
174  {
175  /* look for constructs like postgres://foo */
176  file_name = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER (file_box));
177  if (file_name != NULL)
178  {
179  if (strstr (file_name, "file://") == file_name)
180  {
181  g_free (file_name);
182  /* nope, a local file name */
183  file_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (file_box));
184  }
185  file_name_list = g_slist_append (file_name_list, file_name);
186  }
187  }
188  }
189  gtk_widget_destroy(GTK_WIDGET(file_box));
190  LEAVE("%s", file_name ? file_name : "(null)");
191  return file_name_list;
192 }
193 
194 /********************************************************************\
195  * gnc_file_dialog *
196  * Pops up a file selection dialog (either a "Save As" or an *
197  * "Open"), and returns the name of the file the user selected. *
198  * (This function does not return until the user selects a file *
199  * or presses "Cancel" or the window manager destroy button) *
200  * *
201  * Args: title - the title of the window *
202  * filters - list of GtkFileFilters to use, will be *
203  * freed automatically *
204  * default_dir - start the chooser in this directory *
205  * type - what type of dialog (open, save, etc.) *
206  * Return: containing the name of the file the user selected *
207  \********************************************************************/
208 char *
209 gnc_file_dialog (GtkWindow *parent,
210  const char * title,
211  GList * filters,
212  const char * starting_dir,
213  GNCFileDialogType type
214  )
215 {
216  gchar* file_name = NULL;
217  GSList* ret = gnc_file_dialog_int (parent, title, filters, starting_dir, type, FALSE);
218  if (ret)
219  file_name = g_strdup (ret->data);
220  g_slist_free_full (ret, g_free);
221  return file_name;
222 }
223 
224 /********************************************************************\
225  * gnc_file_dialog_multi *
226  * Pops up a file selection dialog (either a "Save As" or an *
227  * "Open"), and returns the name of the files the user selected. *
228  * Similar to gnc_file_dialog with allowing multi-file selection *
229  * *
230  * Args: title - the title of the window *
231  * filters - list of GtkFileFilters to use, will be *
232  * freed automatically *
233  * default_dir - start the chooser in this directory *
234  * type - what type of dialog (open, save, etc.) *
235  * Return: GList containing the names of the selected files *
236  \********************************************************************/
237 
238 GSList *
239 gnc_file_dialog_multi (GtkWindow *parent,
240  const char * title,
241  GList * filters,
242  const char * starting_dir,
243  GNCFileDialogType type
244  )
245 {
246  return gnc_file_dialog_int (parent, title, filters, starting_dir, type, TRUE);
247 }
248 
249 gboolean
250 show_session_error (GtkWindow *parent,
251  QofBackendError io_error,
252  const char *newfile,
253  GNCFileDialogType type)
254 {
255  GtkWidget *dialog;
256  gboolean uh_oh = TRUE;
257  const char *fmt, *label;
258  gchar *displayname;
259  gint response;
260 
261  if (NULL == newfile)
262  {
263  displayname = g_strdup(_("(null)"));
264  }
265  else if (!gnc_uri_targets_local_fs (newfile)) /* Hide the db password in error messages */
266  displayname = gnc_uri_normalize_uri ( newfile, FALSE);
267  else
268  {
269  /* Strip the protocol from the file name and ensure absolute filename. */
270  char *uri = gnc_uri_normalize_uri(newfile, FALSE);
271  displayname = gnc_uri_get_path(uri);
272  g_free(uri);
273  }
274 
275  switch (io_error)
276  {
277  case ERR_BACKEND_NO_ERR:
278  uh_oh = FALSE;
279  break;
280 
282  fmt = _("No suitable backend was found for %s.");
283  gnc_error_dialog(parent, fmt, displayname);
284  break;
285 
287  fmt = _("The URL %s is not supported by this version of GnuCash.");
288  gnc_error_dialog (parent, fmt, displayname);
289  break;
290 
291  case ERR_BACKEND_BAD_URL:
292  fmt = _("Can't parse the URL %s.");
293  gnc_error_dialog (parent, fmt, displayname);
294  break;
295 
297  fmt = _("Can't connect to %s. "
298  "The host, username or password were incorrect.");
299  gnc_error_dialog (parent, fmt, displayname);
300  break;
301 
303  fmt = _("Can't connect to %s. "
304  "Connection was lost, unable to send data.");
305  gnc_error_dialog (parent, fmt, displayname);
306  break;
307 
308  case ERR_BACKEND_TOO_NEW:
309  fmt = _("This file/URL appears to be from a newer version "
310  "of GnuCash. You must upgrade your version of GnuCash "
311  "to work with this data.");
312  gnc_error_dialog (parent, "%s", fmt);
313  break;
314 
316  fmt = _("The database %s doesn't seem to exist. "
317  "Do you want to create it?");
318  if (gnc_verify_dialog (parent, TRUE, fmt, displayname))
319  {
320  uh_oh = FALSE;
321  }
322  break;
323 
324  case ERR_BACKEND_LOCKED:
325  switch (type)
326  {
327  case GNC_FILE_DIALOG_OPEN:
328  default:
329  label = _("Open");
330  fmt = _("GnuCash could not obtain the lock for %s. "
331  "That database may be in use by another user, "
332  "in which case you should not open the database. "
333  "Do you want to proceed with opening the database?");
334  break;
335 
336  case GNC_FILE_DIALOG_IMPORT:
337  label = _("Import");
338  fmt = _("GnuCash could not obtain the lock for %s. "
339  "That database may be in use by another user, "
340  "in which case you should not import the database. "
341  "Do you want to proceed with importing the database?");
342  break;
343 
344  case GNC_FILE_DIALOG_SAVE:
345  label = _("Save");
346  fmt = _("GnuCash could not obtain the lock for %s. "
347  "That database may be in use by another user, "
348  "in which case you should not save the database. "
349  "Do you want to proceed with saving the database?");
350  break;
351 
352  case GNC_FILE_DIALOG_EXPORT:
353  label = _("Export");
354  fmt = _("GnuCash could not obtain the lock for %s. "
355  "That database may be in use by another user, "
356  "in which case you should not export the database. "
357  "Do you want to proceed with exporting the database?");
358  break;
359  }
360 
361  dialog = gtk_message_dialog_new(parent,
362  GTK_DIALOG_DESTROY_WITH_PARENT,
363  GTK_MESSAGE_QUESTION,
364  GTK_BUTTONS_NONE,
365  fmt,
366  displayname);
367  gtk_dialog_add_buttons(GTK_DIALOG(dialog),
368  _("_Cancel"), GTK_RESPONSE_CANCEL,
369  label, GTK_RESPONSE_YES,
370  NULL);
371  if (!parent)
372  gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
373  response = gtk_dialog_run(GTK_DIALOG(dialog));
374  gtk_widget_destroy(dialog);
375  uh_oh = (response != GTK_RESPONSE_YES);
376  break;
377 
379  fmt = _("GnuCash could not write to %s. "
380  "That database may be on a read-only file system, "
381  "you may not have write permission for the directory "
382  "or your anti-virus software is preventing this action.");
383  gnc_error_dialog (parent, fmt, displayname);
384  break;
385 
387  fmt = _("The file/URL %s "
388  "does not contain GnuCash data or the data is corrupt.");
389  gnc_error_dialog (parent, fmt, displayname);
390  break;
391 
393  fmt = _("The server at URL %s "
394  "experienced an error or encountered bad or corrupt data.");
395  gnc_error_dialog (parent, fmt, displayname);
396  break;
397 
398  case ERR_BACKEND_PERM:
399  fmt = _("You do not have permission to access %s.");
400  gnc_error_dialog (parent, fmt, displayname);
401  break;
402 
403  case ERR_BACKEND_MISC:
404  fmt = _("An error occurred while processing %s.");
405  gnc_error_dialog (parent, fmt, displayname);
406  break;
407 
409  fmt = _("There was an error reading the file. "
410  "Do you want to continue?");
411  if (gnc_verify_dialog (parent, TRUE, "%s", fmt))
412  {
413  uh_oh = FALSE;
414  }
415  break;
416 
418  fmt = _("There was an error parsing the file %s.");
419  gnc_error_dialog (parent, fmt, displayname);
420  break;
421 
423  fmt = _("The file %s is empty.");
424  gnc_error_dialog (parent, fmt, displayname);
425  break;
426 
428  if (type == GNC_FILE_DIALOG_SAVE)
429  {
430  uh_oh = FALSE;
431  }
432  else
433  {
434  if (gnc_history_test_for_file (displayname))
435  {
436  fmt = _("The file/URI %s could not be found.\n\nThe file is in the history list, do you want to remove it?");
437  if (gnc_verify_dialog (parent, FALSE, fmt, displayname))
438  gnc_history_remove_file (displayname);
439  }
440  else
441  {
442  fmt = _("The file/URI %s could not be found.");
443  gnc_error_dialog (parent, fmt, displayname);
444  }
445  }
446  break;
447 
449  fmt = _("This file is from an older version of GnuCash. "
450  "Do you want to continue?");
451  if (gnc_verify_dialog (parent, TRUE, "%s", fmt))
452  {
453  uh_oh = FALSE;
454  }
455  break;
456 
458  fmt = _("The file type of file %s is unknown.");
459  gnc_error_dialog(parent, fmt, displayname);
460  break;
461 
463  fmt = _("Could not make a backup of the file %s");
464  gnc_error_dialog(parent, fmt, displayname);
465  break;
466 
468  fmt = _("Could not write to file %s. Check that you have "
469  "permission to write to this file and that "
470  "there is sufficient space to create it.");
471  gnc_error_dialog(parent, fmt, displayname);
472  break;
473 
475  fmt = _("No read permission to read from file %s.");
476  gnc_error_dialog (parent, fmt, displayname);
477  break;
478 
480  /* Translators: the first %s is a path in the filesystem,
481  the second %s is PACKAGE_NAME, which by default is "GnuCash" */
482  fmt = _("You attempted to save in\n%s\nor a subdirectory thereof. "
483  "This is not allowed as %s reserves that directory for internal use.\n\n"
484  "Please try again in a different directory.");
485  gnc_error_dialog (parent, fmt, gnc_userdata_dir(), PACKAGE_NAME);
486  break;
487 
488  case ERR_SQL_DB_TOO_OLD:
489  fmt = _("This database is from an older version of GnuCash. "
490  "Select OK to upgrade it to the current version, Cancel "
491  "to mark it read-only.");
492 
493  response = gnc_ok_cancel_dialog(parent, GTK_RESPONSE_CANCEL, "%s", fmt);
494  uh_oh = (response == GTK_RESPONSE_CANCEL);
495  break;
496 
497  case ERR_SQL_DB_TOO_NEW:
498  fmt = _("This database is from a newer version of GnuCash. "
499  "This version can read it, but cannot safely save to it. "
500  "It will be marked read-only until you do File->Save As, "
501  "but data may be lost in writing to the old version.");
502  gnc_warning_dialog (parent, "%s", fmt);
503  uh_oh = TRUE;
504  break;
505 
506  case ERR_SQL_DB_BUSY:
507  fmt = _("The SQL database is in use by other users, "
508  "and the upgrade cannot be performed until they logoff. "
509  "If there are currently no other users, consult the "
510  "documentation to learn how to clear out dangling login "
511  "sessions.");
512  gnc_error_dialog (parent, "%s", fmt);
513  break;
514 
515  case ERR_SQL_BAD_DBI:
516 
517  fmt = _("The library \"libdbi\" installed on your system doesn't correctly "
518  "store large numbers. This means GnuCash cannot use SQL databases "
519  "correctly. Gnucash will not open or save to SQL databases until this is "
520  "fixed by installing a different version of \"libdbi\". Please see "
521  "https://bugs.gnucash.org/show_bug.cgi?id=611936 for more "
522  "information.");
523 
524  gnc_error_dialog (parent, "%s", fmt);
525  break;
526 
528 
529  fmt = _("GnuCash could not complete a critical test for the presence of "
530  "a bug in the \"libdbi\" library. This may be caused by a "
531  "permissions misconfiguration of your SQL database. Please see "
532  "https://bugs.gnucash.org/show_bug.cgi?id=645216 for more "
533  "information.");
534 
535  gnc_error_dialog (parent, "%s", fmt);
536  break;
537 
539  fmt = _("This file is from an older version of GnuCash and will be "
540  "upgraded when saved by this version. You will not be able "
541  "to read the saved file from the older version of Gnucash "
542  "(it will report an \"error parsing the file\"). If you wish "
543  "to preserve the old version, exit without saving.");
544  gnc_warning_dialog (parent, "%s", fmt);
545  uh_oh = FALSE;
546  break;
547 
548  default:
549  PERR("FIXME: Unhandled error %d", io_error);
550  fmt = _("An unknown I/O error (%d) occurred.");
551  gnc_error_dialog (parent, fmt, io_error);
552  break;
553  }
554 
555  g_free (displayname);
556  return uh_oh;
557 }
558 
559 static void
560 gnc_add_history (QofSession * session)
561 {
562  const gchar *url;
563  char *file;
564 
565  if (!session) return;
566 
567  url = qof_session_get_url ( session );
568  if ( !strlen (url) )
569  return;
570 
571  if (gnc_uri_targets_local_fs (url))
572  file = gnc_uri_get_path ( url );
573  else
574  file = gnc_uri_normalize_uri ( url, FALSE ); /* Note that the password is not saved in history ! */
575 
576  gnc_history_add_file (file);
577  g_free (file);
578 }
579 
580 static void
581 gnc_book_opened (void)
582 {
583  gnc_hook_run(HOOK_BOOK_OPENED, gnc_get_current_session());
584 }
585 
586 void
587 gnc_file_new (GtkWindow *parent)
588 {
589  QofSession *session;
590 
591  /* If user attempts to start a new session before saving results of
592  * the last one, prompt them to clean up their act. */
593  if (!gnc_file_query_save (parent, TRUE))
594  return;
595 
596  if (gnc_current_session_exist())
597  {
598  session = gnc_get_current_session ();
599 
600  /* close any ongoing file sessions, and free the accounts.
601  * disable events so we don't get spammed by redraws. */
603 
604  gnc_hook_run(HOOK_BOOK_CLOSED, session);
605 
606  gnc_close_gui_component_by_session (session);
607  gnc_state_save (session);
608  gnc_clear_current_session();
609  qof_event_resume ();
610  }
611 
612  /* start a new book */
613  gnc_get_current_session ();
614 
615  gnc_hook_run(HOOK_NEW_BOOK, NULL);
616 
617  gnc_gui_refresh_all ();
618 
619  /* Call this after re-enabling events. */
620  gnc_book_opened ();
621 }
622 
623 gboolean
624 gnc_file_query_save (GtkWindow *parent, gboolean can_cancel)
625 {
626  QofBook *current_book;
627 
628  if (!gnc_current_session_exist())
629  return TRUE;
630 
631  current_book = qof_session_get_book (gnc_get_current_session ());
632  /* Remove any pending auto-save timeouts */
633  gnc_autosave_remove_timer(current_book);
634 
635  /* If user wants to mess around before finishing business with
636  * the old file, give him a chance to figure out what's up.
637  * Pose the question as a "while" loop, so that if user screws
638  * up the file-selection dialog, we don't blow him out of the water;
639  * instead, give them another chance to say "no" to the verify box.
640  */
641  while (qof_book_session_not_saved(current_book))
642  {
643  GtkWidget *dialog;
644  gint response;
645  const char *title = _("Save changes to the file?");
646  /* This should be the same message as in gnc-main-window.c */
647  time64 oldest_change;
648  gint minutes;
649 
650  dialog = gtk_message_dialog_new(parent,
651  GTK_DIALOG_DESTROY_WITH_PARENT,
652  GTK_MESSAGE_QUESTION,
653  GTK_BUTTONS_NONE,
654  "%s", title);
655  oldest_change = qof_book_get_session_dirty_time(current_book);
656  minutes = (gnc_time (NULL) - oldest_change) / 60 + 1;
657  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
658  ngettext("If you don't save, changes from the past %d minute will be discarded.",
659  "If you don't save, changes from the past %d minutes will be discarded.",
660  minutes), minutes);
661  gtk_dialog_add_button(GTK_DIALOG(dialog),
662  _("Continue _Without Saving"), GTK_RESPONSE_OK);
663 
664  if (can_cancel)
665  gtk_dialog_add_button(GTK_DIALOG(dialog),
666  _("_Cancel"), GTK_RESPONSE_CANCEL);
667  gtk_dialog_add_button(GTK_DIALOG(dialog),
668  _("_Save"), GTK_RESPONSE_YES);
669 
670  gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES);
671 
672  response = gtk_dialog_run(GTK_DIALOG(dialog));
673  gtk_widget_destroy(dialog);
674 
675  switch (response)
676  {
677  case GTK_RESPONSE_YES:
678  gnc_file_save (parent);
679  /* Go check the loop condition. */
680  break;
681 
682  case GTK_RESPONSE_CANCEL:
683  default:
684  if (can_cancel)
685  return FALSE;
686  /* No cancel function available. */
687  /* Fall through */
688 
689  case GTK_RESPONSE_OK:
690  return TRUE;
691  }
692  }
693 
694  return TRUE;
695 }
696 
697 
698 
699 static char*
700 get_account_sep_warning (QofBook *book)
701 {
702  const char *sep = gnc_get_account_separator_string ();
703  GList *violation_accts = gnc_account_list_name_violations (book, sep);
704  if (!violation_accts)
705  return NULL;
706 
707  gchar *rv = gnc_account_name_violations_errmsg (sep, violation_accts);
708  g_list_free_full (violation_accts, g_free);
709  return rv;
710 }
711 
712 /* private utilities for file open; done in two stages */
713 
714 #define RESPONSE_NEW 1
715 #define RESPONSE_OPEN 2
716 #define RESPONSE_QUIT 3
717 #define RESPONSE_READONLY 4
718 #define RESPONSE_FILE 5
719 
720 /* This function is called after loading datafile. It's meant to
721  collect all scrubbing routines. */
722 static void
723 run_post_load_scrubs (GtkWindow *parent, QofBook *book)
724 {
725  const char *budget_warning =
726  _("This book has budgets. The internal representation of "
727  "budget amounts no longer depends on the Reverse Balanced "
728  "Accounts preference. Please review the budgets and amend "
729  "signs if necessary.");
730 
731  GList *infos = NULL;
732 
734 
735  /* If feature GNC_FEATURE_BUDGET_UNREVERSED is not set, and there
736  are budgets, fix signs */
737  if (gnc_maybe_scrub_all_budget_signs (book))
738  infos = g_list_prepend (infos, g_strdup (budget_warning));
739 
740  // Fix account color slots being set to 'Not Set', should run once on a book
742 
743  /* Check for account names that may contain the current separator character
744  * and inform the user if there are any */
745  char *sep_warning = get_account_sep_warning (book);
746  if (sep_warning)
747  infos = g_list_prepend (infos, sep_warning);
748 
750 
751  if (!infos)
752  return;
753 
754  const char *header = N_("The following are noted in this file:");
755  infos = g_list_reverse (infos);
756  infos = g_list_prepend (infos, g_strdup (_(header)));
757  char *final = gnc_g_list_stringjoin (infos, "\n\nā€¢ ");
758  gnc_info_dialog (parent, "%s", final);
759 
760  g_free (final);
761  g_list_free_full (infos, g_free);
762 }
763 
764 static gboolean
765 gnc_post_file_open (GtkWindow *parent, const char * filename, gboolean is_readonly)
766 {
767  QofSession *new_session;
768  gboolean uh_oh = FALSE;
769  char * newfile;
770  QofBackendError io_err = ERR_BACKEND_NO_ERR;
771 
772  gchar *scheme = NULL;
773  gchar *hostname = NULL;
774  gchar *username = NULL;
775  gchar *password = NULL;
776  gchar *path = NULL;
777  gint32 port = 0;
778 
779 
780  ENTER("filename %s", filename);
781 RESTART:
782  if (!filename || (*filename == '\0')) return FALSE;
783 
784  /* Convert user input into a normalized uri
785  * Note that the normalized uri for internal use can have a password */
786  newfile = gnc_uri_normalize_uri ( filename, TRUE );
787  if (!newfile)
788  {
789  show_session_error (parent,
790  ERR_FILEIO_FILE_NOT_FOUND, filename,
791  GNC_FILE_DIALOG_OPEN);
792  return FALSE;
793  }
794 
795  gnc_uri_get_components (newfile, &scheme, &hostname,
796  &port, &username, &password, &path);
797 
798  /* If the file to open is a database, and no password was given,
799  * attempt to look it up in a keyring. If that fails the keyring
800  * function will ask the user to enter a password. The user can
801  * cancel this dialog, in which case the open file action will be
802  * abandoned.
803  * Note newfile is normalized uri so we can safely call
804  * gnc_uri_is_file_scheme on it.
805  */
806  if (!gnc_uri_is_file_scheme (scheme) && !password)
807  {
808  gboolean have_valid_pw = FALSE;
809  have_valid_pw = gnc_keyring_get_password ( NULL, scheme, hostname, port,
810  path, &username, &password );
811  if (!have_valid_pw)
812  return FALSE;
813 
814  /* Got password. Recreate the uri to use internally. */
815  g_free ( newfile );
816  newfile = gnc_uri_create_uri ( scheme, hostname, port,
817  username, password, path);
818  }
819 
820  /* For file based uri's, remember the directory as the default. */
821  if (gnc_uri_is_file_scheme(scheme))
822  {
823  gchar *default_dir = g_path_get_dirname(path);
824  gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE, default_dir);
825  g_free(default_dir);
826  }
827 
828  /* disable events while moving over to the new set of accounts;
829  * the mass deletion of accounts and transactions during
830  * switchover would otherwise cause excessive redraws. */
832 
833  /* Change the mouse to a busy cursor */
834  gnc_set_busy_cursor (NULL, TRUE);
835 
836  /* -------------- BEGIN CORE SESSION CODE ------------- */
837  /* -- this code is almost identical in FileOpen and FileSaveAs -- */
838  if (gnc_current_session_exist())
839  {
840  QofSession *current_session = gnc_get_current_session();
841  gnc_hook_run(HOOK_BOOK_CLOSED, current_session);
842  gnc_close_gui_component_by_session (current_session);
843  gnc_state_save (current_session);
844  gnc_clear_current_session();
845  }
846 
847  /* load the accounts from the users datafile */
848  /* but first, check to make sure we've got a session going. */
849  new_session = qof_session_new (qof_book_new());
850 
851  // Begin the new session. If we are in read-only mode, ignore the locks.
852  qof_session_begin (new_session, newfile,
853  is_readonly ? SESSION_READ_ONLY : SESSION_NORMAL_OPEN);
854  io_err = qof_session_get_error (new_session);
855 
856  if (ERR_BACKEND_BAD_URL == io_err)
857  {
858  gchar *directory;
859  show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_OPEN);
860  if (g_file_test (filename, G_FILE_TEST_IS_DIR))
861  directory = g_strdup (filename);
862  else
863  directory = gnc_get_default_directory (GNC_PREFS_GROUP_OPEN_SAVE);
864 
865  filename = gnc_file_dialog (parent, NULL, NULL, directory,
866  GNC_FILE_DIALOG_OPEN);
867  /* Suppress trying to save the empty session. */
869  qof_session_destroy (new_session);
870  new_session = NULL;
871  g_free (directory);
872  goto RESTART;
873  }
874  /* if file appears to be locked, ask the user ... */
875  else if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
876  {
877  GtkWidget *dialog;
878  gchar *displayname = NULL;
879 
880  char *fmt1 = _("GnuCash could not obtain the lock for %s.");
881  char *fmt2 = ((ERR_BACKEND_LOCKED == io_err) ?
882  _("That database may be in use by another user, "
883  "in which case you should not open the database. "
884  "What would you like to do?") :
885  _("That database may be on a read-only file system, "
886  "you may not have write permission for the directory, "
887  "or your anti-virus software is preventing this action. "
888  "If you proceed you may not be able to save any changes. "
889  "What would you like to do?")
890  );
891  int rc;
892 
893  /* Hide the db password and local filesystem schemes in error messages */
894  if (!gnc_uri_is_file_uri (newfile))
895  displayname = gnc_uri_normalize_uri ( newfile, FALSE);
896  else
897  displayname = gnc_uri_get_path (newfile);
898 
899  dialog = gtk_message_dialog_new(parent,
900  0,
901  GTK_MESSAGE_WARNING,
902  GTK_BUTTONS_NONE,
903  fmt1, displayname);
904  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
905  "%s", fmt2);
906  gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
907 
908  gnc_gtk_dialog_add_button(dialog, _("Open _Read-Only"),
909  "emblem-readonly", RESPONSE_READONLY);
910 
911  gnc_gtk_dialog_add_button(dialog, _("Create _New File"),
912  "document-new-symbolic", RESPONSE_NEW);
913 
914  gnc_gtk_dialog_add_button(dialog, _("Open _Anyway"),
915  "document-open-symbolic", RESPONSE_OPEN);
916 
917  gnc_gtk_dialog_add_button(dialog, _("Open _Folder"),
918  "folder-open-symbolic", RESPONSE_FILE);
919 
920  if (shutdown_cb)
921  {
922  gtk_dialog_add_button(GTK_DIALOG(dialog),
923  _("_Quit"), RESPONSE_QUIT);
924  gtk_dialog_set_default_response (GTK_DIALOG(dialog), RESPONSE_QUIT);
925  }
926  else
927  gtk_dialog_set_default_response (GTK_DIALOG(dialog), RESPONSE_FILE);
928 
929  rc = gtk_dialog_run(GTK_DIALOG(dialog));
930  gtk_widget_destroy(dialog);
931  g_free (displayname);
932 
933  if (rc == GTK_RESPONSE_DELETE_EVENT)
934  {
935  rc = shutdown_cb ? RESPONSE_QUIT : RESPONSE_FILE;
936  }
937  switch (rc)
938  {
939  case RESPONSE_QUIT:
940  if (shutdown_cb)
941  shutdown_cb(0);
942  g_assert(1);
943  break;
944  case RESPONSE_READONLY:
945  is_readonly = TRUE;
946  /* user told us to open readonly. We do ignore locks (just as before), but now also force the opening. */
947  qof_session_begin (new_session, newfile, SESSION_READ_ONLY);
948  break;
949  case RESPONSE_OPEN:
950  /* user told us to ignore locks. So ignore them. */
951  qof_session_begin (new_session, newfile, SESSION_BREAK_LOCK);
952  break;
953  case RESPONSE_NEW:
954  /* Can't use the given file, so just create a new
955  * database so that the user will get a window that
956  * they can click "Exit" on.
957  */
958  gnc_file_new (parent);
959  break;
960  default:
961  /* Can't use the given file, so open a file browser dialog
962  * so they can choose a different file and get a window that
963  * they can click "Exit" on.
964  */
965  gnc_file_open (parent);
966  break;
967  }
968  }
969  /* if the database doesn't exist, ask the user ... */
970  else if ((ERR_BACKEND_NO_SUCH_DB == io_err))
971  {
972  if (!show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_OPEN))
973  {
974  /* user told us to create a new database. Do it. We
975  * shouldn't have to worry about locking or clobbering,
976  * it's supposed to be new. */
977  qof_session_begin (new_session, newfile, SESSION_NEW_STORE);
978  }
979  }
980 
981  /* Check for errors again, since above may have cleared the lock.
982  * If its still locked, still, doesn't exist, still too old, then
983  * don't bother with the message, just die. */
984  io_err = qof_session_get_error (new_session);
985  if ((ERR_BACKEND_LOCKED == io_err) ||
986  (ERR_BACKEND_READONLY == io_err) ||
987  (ERR_BACKEND_NO_SUCH_DB == io_err))
988  {
989  uh_oh = TRUE;
990  }
991 
992  else
993  {
994  uh_oh = show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_OPEN);
995  }
996 
997  if (!uh_oh)
998  {
999  Account *new_root;
1000 
1001  /* If the new "file" is a database, attempt to store the password
1002  * in a keyring. GnuCash itself will not save it.
1003  */
1004  if ( !gnc_uri_is_file_scheme (scheme))
1005  gnc_keyring_set_password ( scheme, hostname, port,
1006  path, username, password );
1007 
1008  xaccLogDisable();
1009  gnc_window_show_progress(_("Loading user dataā€¦"), 0.0);
1010  qof_session_load (new_session, gnc_window_show_progress);
1011  gnc_window_show_progress(NULL, -1.0);
1012  xaccLogEnable();
1013 
1014  if (is_readonly)
1015  {
1016  // If the user chose "open read-only" above, make sure to have this
1017  // read-only here.
1019  }
1020 
1021  /* check for i/o error, put up appropriate error dialog */
1022  io_err = qof_session_pop_error (new_session);
1023 
1024  if (io_err == ERR_FILEIO_NO_ENCODING)
1025  {
1026  if (gnc_xml_convert_single_file (newfile))
1027  {
1028  /* try to load once again */
1029  gnc_window_show_progress(_("Loading user dataā€¦"), 0.0);
1030  qof_session_load (new_session, gnc_window_show_progress);
1031  gnc_window_show_progress(NULL, -1.0);
1032  xaccLogEnable();
1033  io_err = qof_session_get_error (new_session);
1034  }
1035  else
1036  {
1037  io_err = ERR_FILEIO_PARSE_ERROR;
1038  }
1039  }
1040 
1041  uh_oh = show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_OPEN);
1042  /* Attempt to update the database if it's too old */
1043  if ( !uh_oh && io_err == ERR_SQL_DB_TOO_OLD )
1044  {
1045  gnc_window_show_progress(_("Re-saving user dataā€¦"), 0.0);
1046  qof_session_safe_save(new_session, gnc_window_show_progress);
1047  io_err = qof_session_get_error(new_session);
1048  uh_oh = show_session_error(parent, io_err, newfile, GNC_FILE_DIALOG_SAVE);
1049  }
1050  /* Database is either too old and couldn't (or user didn't
1051  * want it to) be updated or it's too new. Mark it as
1052  * read-only
1053  */
1054  if (uh_oh && (io_err == ERR_SQL_DB_TOO_OLD ||
1055  io_err == ERR_SQL_DB_TOO_NEW))
1056  {
1058  uh_oh = FALSE;
1059  }
1060  new_root = gnc_book_get_root_account (qof_session_get_book (new_session));
1061  if (uh_oh) new_root = NULL;
1062 
1063  /* Umm, came up empty-handed, but no error:
1064  * The backend forgot to set an error. So make one up. */
1065  if (!uh_oh && !new_root)
1066  {
1067  uh_oh = show_session_error (parent, ERR_BACKEND_MISC, newfile,
1068  GNC_FILE_DIALOG_OPEN);
1069  }
1070 
1071  /* test for unknown features. */
1072  if (!uh_oh)
1073  {
1074  QofBook *book = qof_session_get_book (new_session);
1075  gchar *msg = gnc_features_test_unknown (book);
1076  Account *template_root = gnc_book_get_template_root (book);
1077 
1078  if (msg)
1079  {
1080  uh_oh = TRUE;
1081 
1082  // XXX: should pull out the file name here */
1083  gnc_error_dialog (parent, msg, "");
1084  g_free (msg);
1085  }
1086  if (template_root != NULL)
1087  {
1088  GList *child = NULL;
1089  GList *children = gnc_account_get_descendants (template_root);
1090 
1091  for (child = children; child; child = g_list_next (child))
1092  {
1093  Account *acc = GNC_ACCOUNT (child->data);
1094  GList *splits = xaccAccountGetSplitList (acc);
1095  g_list_foreach (splits,
1096  (GFunc)gnc_sx_scrub_split_numerics, NULL);
1097  g_list_free (splits);
1098  }
1099  g_list_free (children);
1100  }
1101  }
1102  }
1103 
1104  g_free (scheme);
1105  g_free (hostname);
1106  g_free (username);
1107  g_free (password);
1108  g_free (path);
1109 
1110  gnc_unset_busy_cursor (NULL);
1111 
1112  /* going down -- abandon ship */
1113  if (uh_oh)
1114  {
1115  xaccLogDisable();
1116  qof_session_destroy (new_session);
1117  xaccLogEnable();
1118 
1119  /* well, no matter what, I think it's a good idea to have a root
1120  * account around. For example, early in the gnucash startup
1121  * sequence, the user opens a file; if this open fails for any
1122  * reason, we don't want to leave them high & dry without a root
1123  * account, because if the user continues, then bad things will
1124  * happen. */
1125  gnc_get_current_session ();
1126 
1127  g_free (newfile);
1128 
1129  qof_event_resume ();
1130  gnc_gui_refresh_all ();
1131 
1132  return FALSE;
1133  }
1134 
1135  /* if we got to here, then we've successfully gotten a new session */
1136  /* close up the old file session (if any) */
1137  gnc_set_current_session(new_session);
1138 
1139  /* --------------- END CORE SESSION CODE -------------- */
1140 
1141  /* clean up old stuff, and then we're outta here. */
1142  gnc_add_history (new_session);
1143 
1144  g_free (newfile);
1145 
1146  qof_event_resume ();
1147  gnc_gui_refresh_all ();
1148 
1149  /* Call this after re-enabling events. */
1150  gnc_book_opened ();
1151 
1152  run_post_load_scrubs (parent, gnc_get_current_book ());
1153 
1154  return TRUE;
1155 }
1156 
1157 /* Routine that pops up a file chooser dialog
1158  *
1159  * Note: this dialog is used when dbi is not enabled
1160  * so the paths used in here are always file
1161  * paths, never db uris.
1162  */
1163 gboolean
1164 gnc_file_open (GtkWindow *parent)
1165 {
1166  const gchar * newfile;
1167  gchar *last = NULL;
1168  gchar *default_dir = NULL;
1169  gboolean result;
1170 
1171  if (!gnc_file_query_save (parent, TRUE))
1172  return FALSE;
1173 
1174  if ( last && gnc_uri_targets_local_fs (last))
1175  {
1176  gchar *filepath = gnc_uri_get_path ( last );
1177  default_dir = g_path_get_dirname( filepath );
1178  g_free ( filepath );
1179  }
1180  else
1181  default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_OPEN_SAVE);
1182 
1183  newfile = gnc_file_dialog (parent, _("Open"), NULL, default_dir, GNC_FILE_DIALOG_OPEN);
1184  g_free ( last );
1185  g_free ( default_dir );
1186 
1187  result = gnc_post_file_open (parent, newfile, /*is_readonly*/ FALSE );
1188 
1189  /* This dialogue can show up early in the startup process. If the
1190  * user fails to pick a file (by e.g. hitting the cancel button), we
1191  * might be left with a null topgroup, which leads to nastiness when
1192  * user goes to create their very first account. So create one. */
1193  gnc_get_current_session ();
1194 
1195  return result;
1196 }
1197 
1198 gboolean
1199 gnc_file_open_file (GtkWindow *parent, const char * newfile, gboolean open_readonly)
1200 {
1201  if (!newfile) return FALSE;
1202 
1203  if (!gnc_file_query_save (parent, TRUE))
1204  return FALSE;
1205 
1206  /* Reset the flag that indicates the conversion of the bayes KVP
1207  * entries has been run */
1209 
1210  return gnc_post_file_open (parent, newfile, open_readonly);
1211 }
1212 
1213 /* Note: this dialog will only be used when dbi is not enabled
1214  * paths used in it always refer to files and are
1215  * never db uris
1216  */
1217 void
1218 gnc_file_export (GtkWindow *parent)
1219 {
1220  const char *filename;
1221  char *default_dir = NULL; /* Default to last open */
1222  char *last;
1223 
1224  ENTER(" ");
1225 
1226  last = gnc_history_get_last();
1227  if ( last && gnc_uri_targets_local_fs (last))
1228  {
1229  gchar *filepath = gnc_uri_get_path ( last );
1230  default_dir = g_path_get_dirname( filepath );
1231  g_free ( filepath );
1232  }
1233  else
1234  default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_EXPORT);
1235 
1236  filename = gnc_file_dialog (parent,
1237  _("Save"), NULL, default_dir,
1238  GNC_FILE_DIALOG_SAVE);
1239  g_free ( last );
1240  g_free ( default_dir );
1241  if (!filename) return;
1242 
1243  gnc_file_do_export (parent, filename);
1244 
1245  LEAVE (" ");
1246 }
1247 
1248 /* Prevent the user from storing or exporting data files into the settings
1249  * directory.
1250  */
1251 static gboolean
1252 check_file_path (const char *path)
1253 {
1254  /* Remember the directory as the default. */
1255  gchar *dir = g_path_get_dirname(path);
1256  const gchar *dotgnucash = gnc_userdata_dir();
1257  char *dirpath = dir;
1258 
1259  /* Prevent user from storing file in GnuCash' private configuration
1260  * directory (~/.gnucash by default in linux, but can be overridden)
1261  */
1262  while (strcmp(dir = g_path_get_dirname(dirpath), dirpath) != 0)
1263  {
1264  if (strcmp(dirpath, dotgnucash) == 0)
1265  {
1266  g_free (dir);
1267  g_free (dirpath);
1268  return TRUE;
1269  }
1270  g_free (dirpath);
1271  dirpath = dir;
1272  }
1273  g_free (dirpath);
1274  g_free(dir);
1275  return FALSE;
1276 }
1277 
1278 
1279 void
1280 gnc_file_do_export(GtkWindow *parent, const char * filename)
1281 {
1282  QofSession *current_session, *new_session;
1283  gboolean ok;
1284  QofBackendError io_err = ERR_BACKEND_NO_ERR;
1285  gchar *norm_file;
1286  gchar *newfile;
1287  const gchar *oldfile;
1288 
1289  gchar *scheme = NULL;
1290  gchar *hostname = NULL;
1291  gchar *username = NULL;
1292  gchar *password = NULL;
1293  gchar *path = NULL;
1294  gint32 port = 0;
1295 
1296  ENTER(" ");
1297 
1298  /* Convert user input into a normalized uri
1299  * Note that the normalized uri for internal use can have a password */
1300  norm_file = gnc_uri_normalize_uri ( filename, TRUE );
1301  if (!norm_file)
1302  {
1303  show_session_error (parent, ERR_FILEIO_FILE_NOT_FOUND, filename,
1304  GNC_FILE_DIALOG_EXPORT);
1305  return;
1306  }
1307 
1308  newfile = gnc_uri_add_extension (norm_file, GNC_DATAFILE_EXT);
1309  g_free (norm_file);
1310  gnc_uri_get_components (newfile, &scheme, &hostname,
1311  &port, &username, &password, &path);
1312 
1313  /* Save As can't use the generic 'file' protocol. If the user didn't set
1314  * a specific protocol, assume the default 'xml'.
1315  */
1316  if (g_strcmp0 (scheme, "file") == 0)
1317  {
1318  g_free (scheme);
1319  scheme = g_strdup ("xml");
1320  norm_file = gnc_uri_create_uri (scheme, hostname, port,
1321  username, password, path);
1322  g_free (newfile);
1323  newfile = norm_file;
1324  }
1325 
1326  /* Some extra steps for file based uri's only
1327  * Note newfile is normalized uri so we can safely call
1328  * gnc_uri_is_file_scheme on it. */
1329  if (gnc_uri_is_file_scheme (scheme))
1330  {
1331  if (check_file_path (path))
1332  {
1333  show_session_error (parent, ERR_FILEIO_RESERVED_WRITE, newfile,
1334  GNC_FILE_DIALOG_SAVE);
1335  return;
1336  }
1337  gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE,
1338  g_path_get_dirname(path));
1339  }
1340  /* Check to see if the user specified the same file as the current
1341  * file. If so, prevent the export from happening to avoid killing this file */
1342  current_session = gnc_get_current_session ();
1343  oldfile = qof_session_get_url(current_session);
1344  if (strlen (oldfile) && (strcmp(oldfile, newfile) == 0))
1345  {
1346  g_free (newfile);
1347  show_session_error (parent, ERR_FILEIO_WRITE_ERROR, filename,
1348  GNC_FILE_DIALOG_EXPORT);
1349  return;
1350  }
1351 
1353 
1354  /* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
1355 
1356  new_session = qof_session_new (NULL);
1357  qof_session_begin (new_session, newfile, SESSION_NEW_STORE);
1358 
1359  io_err = qof_session_get_error (new_session);
1360  /* If the file exists and would be clobbered, ask the user */
1361  if (ERR_BACKEND_STORE_EXISTS == io_err)
1362  {
1363  const char *format = _("The file %s already exists. "
1364  "Are you sure you want to overwrite it?");
1365 
1366  const char *name;
1367  if ( gnc_uri_is_file_uri ( newfile ) )
1368  name = gnc_uri_get_path ( newfile );
1369  else
1370  name = gnc_uri_normalize_uri ( newfile, FALSE );
1371  /* if user says cancel, we should break out */
1372  if (!gnc_verify_dialog (parent, FALSE, format, name))
1373  {
1374  return;
1375  }
1376  qof_session_begin (new_session, newfile, SESSION_NEW_OVERWRITE);
1377  }
1378  /* if file appears to be locked, ask the user ... */
1379  if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
1380  {
1381  if (!show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_EXPORT))
1382  {
1383  /* user told us to ignore locks. So ignore them. */
1384  qof_session_begin (new_session, newfile, SESSION_BREAK_LOCK);
1385  }
1386  }
1387 
1388  /* --------------- END CORE SESSION CODE -------------- */
1389 
1390  /* use the current session to save to file */
1391  gnc_set_busy_cursor (NULL, TRUE);
1392  gnc_window_show_progress(_("Exporting fileā€¦"), 0.0);
1393  ok = qof_session_export (new_session, current_session,
1394  gnc_window_show_progress);
1395  gnc_window_show_progress(NULL, -1.0);
1396  gnc_unset_busy_cursor (NULL);
1397  xaccLogDisable();
1398  qof_session_destroy (new_session);
1399  xaccLogEnable();
1400  qof_event_resume();
1401 
1402  if (!ok)
1403  {
1404  /* %s is the strerror(3) error string of the error that occurred. */
1405  const char *format = _("There was an error saving the file.\n\n%s");
1406 
1407  gnc_error_dialog (parent, format, strerror(errno));
1408  return;
1409  }
1410 }
1411 
1412 static gboolean been_here_before = FALSE;
1413 
1414 void
1415 gnc_file_save (GtkWindow *parent)
1416 {
1417  QofBackendError io_err;
1418  const char * newfile;
1419  QofSession *session;
1420  ENTER (" ");
1421 
1422  if (!gnc_current_session_exist ())
1423  return; //No session means nothing to save.
1424 
1425  /* hack alert -- Somehow make sure all in-progress edits get committed! */
1426 
1427  /* If we don't have a filename/path to save to get one. */
1428  session = gnc_get_current_session ();
1429 
1430  if (!strlen (qof_session_get_url (session)))
1431  {
1432  gnc_file_save_as (parent);
1433  return;
1434  }
1435 
1437  {
1438  gint response = gnc_ok_cancel_dialog(parent,
1439  GTK_RESPONSE_CANCEL,
1440  _("The database was opened read-only. "
1441  "Do you want to save it to a different place?"));
1442  if (response == GTK_RESPONSE_OK)
1443  {
1444  gnc_file_save_as (parent);
1445  }
1446  return;
1447  }
1448 
1449  /* use the current session to save to file */
1450  save_in_progress++;
1451  gnc_set_busy_cursor (NULL, TRUE);
1452  gnc_window_show_progress(_("Writing fileā€¦"), 0.0);
1453  qof_session_save (session, gnc_window_show_progress);
1454  gnc_window_show_progress(NULL, -1.0);
1455  gnc_unset_busy_cursor (NULL);
1456  save_in_progress--;
1457 
1458  /* Make sure everything's OK - disk could be full, file could have
1459  become read-only etc. */
1460  io_err = qof_session_get_error (session);
1461  if (ERR_BACKEND_NO_ERR != io_err)
1462  {
1463  newfile = qof_session_get_url(session);
1464  show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_SAVE);
1465 
1466  if (been_here_before) return;
1467  been_here_before = TRUE;
1468  gnc_file_save_as (parent); /* been_here prevents infinite recursion */
1469  been_here_before = FALSE;
1470  return;
1471  }
1472 
1473  xaccReopenLog();
1474  gnc_add_history (session);
1475  gnc_hook_run(HOOK_BOOK_SAVED, session);
1476  LEAVE (" ");
1477 }
1478 
1479 /* Note: this dialog will only be used when dbi is not enabled
1480  * paths used in it always refer to files and are
1481  * never db uris. See gnc_file_do_save_as for that.
1482  */
1483 void
1484 gnc_file_save_as (GtkWindow *parent)
1485 {
1486  const gchar *filename;
1487  gchar *default_dir = NULL; /* Default to last open */
1488  gchar *last;
1489 
1490  ENTER(" ");
1491 
1492  if (!gnc_current_session_exist ())
1493  {
1494  LEAVE("No Session.");
1495  return;
1496  }
1497 
1498  last = gnc_history_get_last();
1499  if ( last && gnc_uri_targets_local_fs (last))
1500  {
1501  gchar *filepath = gnc_uri_get_path ( last );
1502  default_dir = g_path_get_dirname( filepath );
1503  g_free ( filepath );
1504  }
1505  else
1506  default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_OPEN_SAVE);
1507 
1508  filename = gnc_file_dialog (parent,
1509  _("Save"), NULL, default_dir,
1510  GNC_FILE_DIALOG_SAVE);
1511  g_free ( last );
1512  g_free ( default_dir );
1513  if (!filename) return;
1514 
1515  gnc_file_do_save_as (parent, filename);
1516 
1517  LEAVE (" ");
1518 }
1519 
1520 void
1521 gnc_file_do_save_as (GtkWindow *parent, const char* filename)
1522 {
1523  QofSession *new_session;
1524  QofSession *session;
1525  gchar *norm_file;
1526  gchar *newfile;
1527  const gchar *oldfile;
1528 
1529  gchar *scheme = NULL;
1530  gchar *hostname = NULL;
1531  gchar *username = NULL;
1532  gchar *password = NULL;
1533  gchar *path = NULL;
1534  gint32 port = 0;
1535 
1536 
1537  QofBackendError io_err = ERR_BACKEND_NO_ERR;
1538 
1539  ENTER(" ");
1540 
1541  /* Convert user input into a normalized uri
1542  * Note that the normalized uri for internal use can have a password */
1543  norm_file = gnc_uri_normalize_uri ( filename, TRUE );
1544  if (!norm_file)
1545  {
1546  show_session_error (parent, ERR_FILEIO_FILE_NOT_FOUND, filename,
1547  GNC_FILE_DIALOG_SAVE);
1548  return;
1549  }
1550 
1551  newfile = gnc_uri_add_extension (norm_file, GNC_DATAFILE_EXT);
1552  g_free (norm_file);
1553  gnc_uri_get_components (newfile, &scheme, &hostname,
1554  &port, &username, &password, &path);
1555 
1556  /* Save As can't use the generic 'file' protocol. If the user didn't set
1557  * a specific protocol, assume the default 'xml'.
1558  */
1559  if (g_strcmp0 (scheme, "file") == 0)
1560  {
1561  g_free (scheme);
1562  scheme = g_strdup ("xml");
1563  norm_file = gnc_uri_create_uri (scheme, hostname, port,
1564  username, password, path);
1565  g_free (newfile);
1566  newfile = norm_file;
1567  }
1568 
1569  /* Some extra steps for file based uri's only
1570  * Note newfile is normalized uri so we can safely call
1571  * gnc_uri_is_file_scheme on it. */
1572  if (gnc_uri_is_file_scheme (scheme))
1573  {
1574  if (check_file_path (path))
1575  {
1576  show_session_error (parent, ERR_FILEIO_RESERVED_WRITE, newfile,
1577  GNC_FILE_DIALOG_SAVE);
1578  return;
1579  }
1580  gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE,
1581  g_path_get_dirname (path));
1582  }
1583 
1584  /* Check to see if the user specified the same file as the current
1585  * file. If so, then just do a simple save, instead of a full save as */
1586  session = gnc_get_current_session ();
1587  oldfile = qof_session_get_url(session);
1588  if (strlen (oldfile) && (strcmp(oldfile, newfile) == 0))
1589  {
1590  g_free (newfile);
1591  gnc_file_save (parent);
1592  return;
1593  }
1594 
1595  /* Make sure all of the data from the old file is loaded */
1596  qof_event_suspend ();
1597  gnc_suspend_gui_refresh ();
1599  gnc_resume_gui_refresh ();
1600  qof_event_resume ();
1601 
1602  /* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
1603 
1604  save_in_progress++;
1605 
1606  new_session = qof_session_new (NULL);
1607  qof_session_begin (new_session, newfile, SESSION_NEW_STORE);
1608 
1609  io_err = qof_session_get_error (new_session);
1610 
1611  /* If the file exists and would be clobbered, ask the user */
1612  if (ERR_BACKEND_STORE_EXISTS == io_err)
1613  {
1614  const char *format = _("The file %s already exists. "
1615  "Are you sure you want to overwrite it?");
1616 
1617  const char *name;
1618  if ( gnc_uri_is_file_uri ( newfile ) )
1619  name = gnc_uri_get_path ( newfile );
1620  else
1621  name = gnc_uri_normalize_uri ( newfile, FALSE );
1622 
1623  /* if user says cancel, we should break out */
1624  if (!gnc_verify_dialog (parent, FALSE, format, name ))
1625  {
1626  xaccLogDisable();
1627  qof_session_destroy (new_session);
1628  xaccLogEnable();
1629  g_free (newfile);
1630  save_in_progress--;
1631  return;
1632  }
1633  qof_session_begin (new_session, newfile, SESSION_NEW_OVERWRITE);
1634  }
1635  /* if file appears to be locked, ask the user ... */
1636  else if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
1637  {
1638  if (!show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_SAVE))
1639  {
1640  // User wants to replace the file.
1641  qof_session_begin (new_session, newfile, SESSION_BREAK_LOCK);
1642  }
1643  }
1644 
1645  /* if the database doesn't exist, ask the user ... */
1646  else if ((ERR_FILEIO_FILE_NOT_FOUND == io_err) ||
1647  (ERR_BACKEND_NO_SUCH_DB == io_err) ||
1648  (ERR_SQL_DB_TOO_OLD == io_err))
1649  {
1650  if (!show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_SAVE))
1651  {
1652  /* user told us to create a new database. Do it. */
1653  qof_session_begin (new_session, newfile, SESSION_NEW_STORE);
1654  }
1655  }
1656 
1657  /* check again for session errors (since above dialog may have
1658  * cleared a file lock & moved things forward some more)
1659  * This time, errors will be fatal.
1660  */
1661  io_err = qof_session_get_error (new_session);
1662  if (ERR_BACKEND_NO_ERR != io_err)
1663  {
1664  show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_SAVE);
1665  xaccLogDisable();
1666  qof_session_destroy (new_session);
1667  xaccLogEnable();
1668  g_free (newfile);
1669  save_in_progress--;
1670  return;
1671  }
1672 
1673  /* If the new "file" is a database, attempt to store the password
1674  * in a keyring. GnuCash itself will not save it.
1675  */
1676  if ( !gnc_uri_is_file_scheme (scheme))
1677  gnc_keyring_set_password ( scheme, hostname, port,
1678  path, username, password );
1679 
1680  /* Prevent race condition between swapping the contents of the two
1681  * sessions, and actually installing the new session as the current
1682  * one. Any event callbacks that occur in this interval will have
1683  * problems if they check for the current book. */
1685 
1686  /* if we got to here, then we've successfully gotten a new session */
1687  /* close up the old file session (if any) */
1688  qof_session_swap_data (session, new_session);
1690 
1691  qof_event_resume();
1692 
1693 
1694  gnc_set_busy_cursor (NULL, TRUE);
1695  gnc_window_show_progress(_("Writing fileā€¦"), 0.0);
1696  qof_session_save (new_session, gnc_window_show_progress);
1697  gnc_window_show_progress(NULL, -1.0);
1698  gnc_unset_busy_cursor (NULL);
1699 
1700  io_err = qof_session_get_error( new_session );
1701  if ( ERR_BACKEND_NO_ERR != io_err )
1702  {
1703  /* Well, poop. The save failed, so the new session is invalid and we
1704  * need to restore the old one.
1705  */
1706  show_session_error (parent, io_err, newfile, GNC_FILE_DIALOG_SAVE);
1708  qof_session_swap_data( new_session, session );
1709  qof_session_destroy( new_session );
1710  new_session = NULL;
1711  qof_event_resume();
1712  }
1713  else
1714  {
1715  /* Yay! Save was successful, we can dump the old session */
1717  gnc_clear_current_session();
1718  gnc_set_current_session( new_session );
1719  qof_event_resume();
1720  session = NULL;
1721 
1722  xaccReopenLog();
1723  gnc_add_history (new_session);
1724  gnc_hook_run(HOOK_BOOK_SAVED, new_session);
1725  }
1726  /* --------------- END CORE SESSION CODE -------------- */
1727 
1728  save_in_progress--;
1729 
1730  g_free (newfile);
1731  LEAVE (" ");
1732 }
1733 
1734 void
1735 gnc_file_revert (GtkWindow *parent)
1736 {
1737  QofSession *session;
1738  const gchar *fileurl, *filename, *tmp;
1739  const gchar *title = _("Reverting will discard all unsaved changes to %s. Are you sure you want to proceed?");
1740 
1742  return;
1743 
1744  session = gnc_get_current_session();
1745  fileurl = qof_session_get_url(session);
1746  if (!strlen (fileurl))
1747  fileurl = _("<unknown>");
1748  if ((tmp = strrchr(fileurl, '/')) != NULL)
1749  filename = tmp + 1;
1750  else
1751  filename = fileurl;
1752 
1753  if (!gnc_verify_dialog (parent, FALSE, title, filename))
1754  return;
1755 
1757  gnc_file_open_file (parent, fileurl, qof_book_is_readonly(gnc_get_current_book()));}
1758 
1759 void
1760 gnc_file_quit (void)
1761 {
1762  QofSession *session;
1763 
1764  if (!gnc_current_session_exist ())
1765  return;
1766  gnc_set_busy_cursor (NULL, TRUE);
1767  session = gnc_get_current_session ();
1768 
1769  /* disable events; otherwise the mass deletion of accounts and
1770  * transactions during shutdown would cause massive redraws */
1771  qof_event_suspend ();
1772 
1773  gnc_hook_run(HOOK_BOOK_CLOSED, session);
1774  gnc_close_gui_component_by_session (session);
1775  gnc_state_save (session);
1776  gnc_clear_current_session();
1777 
1778  qof_event_resume ();
1779  gnc_unset_busy_cursor (NULL);
1780 }
1781 
1782 void
1783 gnc_file_set_shutdown_callback (GNCShutdownCB cb)
1784 {
1785  shutdown_cb = cb;
1786 }
1787 
1788 gboolean
1789 gnc_file_save_in_progress (void)
1790 {
1791  if (gnc_current_session_exist())
1792  {
1793  QofSession *session = gnc_get_current_session();
1794  return (qof_session_save_in_progress(session) || save_in_progress > 0);
1795  }
1796  return FALSE;
1797 }
No read access permission for the given file.
Definition: qofbackend.h:100
Lost connection to server.
Definition: qofbackend.h:65
Functions to load, save and get gui state.
void qof_session_save(QofSession *session, QofPercentageFunc percentage_func)
The qof_session_save() method will commit all changes that have been made to the session.
Definition: qofsession.cpp:625
database is busy, cannot upgrade version
Definition: qofbackend.h:115
gboolean qof_session_save_in_progress(const QofSession *session)
The qof_session_not_saved() subroutine will return TRUE if any data in the session hasn&#39;t been saved ...
Definition: qofsession.cpp:640
Functions to save and retrieve passwords.
couldn&#39;t parse the data in the file
Definition: qofbackend.h:95
SplitList * xaccAccountGetSplitList(const Account *acc)
The xaccAccountGetSplitList() routine returns a pointer to a GList of the splits in the account...
Definition: Account.cpp:3905
void gnc_sx_scrub_split_numerics(gpointer psplit, gpointer user)
Fix up numerics where they&#39;ve gotten out-of-sync with the formulas.
void gnc_history_add_file(const char *newfile)
Add a file name to the front of the file "history list".
gchar * gnc_g_list_stringjoin(GList *list_of_strings, const gchar *sep)
Return a string joining a GList whose elements are gchar* strings.
gboolean gnc_uri_is_file_uri(const gchar *uri)
Checks if the given uri defines a file (as opposed to a network service like a database or web url) ...
not found / no such file
Definition: qofbackend.h:92
utility functions for the GnuCash UI
void gnc_state_save(const QofSession *session)
Save the state to a state file on disk for the given session.
Definition: gnc-state.c:223
gboolean gnc_uri_is_file_scheme(const gchar *scheme)
Checks if the given scheme is used to refer to a file (as opposed to a network service like a databas...
Definition: gnc-uri-utils.c:90
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition: qofbackend.h:57
time64 qof_book_get_session_dirty_time(const QofBook *book)
Retrieve the earliest modification time on the book.
Definition: qofbook.cpp:420
STRUCTS.
void qof_session_safe_save(QofSession *session, QofPercentageFunc percentage_func)
A special version of save used in the sql backend which moves the existing tables aside...
Definition: qofsession.cpp:633
void qof_session_begin(QofSession *session, const char *uri, SessionOpenMode mode)
Begins a new session.
Definition: qofsession.cpp:610
void xaccLogDisable(void)
document me
Definition: TransLog.cpp:95
Functions that are supported by all types of windows.
QofBook * qof_book_new(void)
Allocate, initialise and return a new QofBook.
Definition: qofbook.cpp:290
database is old and needs upgrading
Definition: qofbackend.h:113
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
Account * gnc_book_get_template_root(const QofBook *book)
Returns the template group from the book.
Definition: SX-book.cpp:65
GList * gnc_account_list_name_violations(QofBook *book, const gchar *separator)
Runs through all the accounts and returns a list of account names that contain the provided separator...
Definition: Account.cpp:273
Can&#39;t parse url.
Definition: qofbackend.h:62
in use by another user (ETXTBSY)
Definition: qofbackend.h:66
void qof_book_mark_readonly(QofBook *book)
Mark the book as read only.
Definition: qofbook.cpp:504
Create a new store at the URI.
Definition: qofsession.h:126
couldn&#39;t write to the file
Definition: qofbackend.h:97
Open the session read-only, ignoring any existing lock and not creating one if the URI isn&#39;t locked...
Definition: qofsession.h:130
void gnc_uri_get_components(const gchar *uri, gchar **scheme, gchar **hostname, gint32 *port, gchar **username, gchar **password, gchar **path)
Converts a uri in separate components.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
void qof_session_ensure_all_data_loaded(QofSession *session)
Ensure all of the data is loaded from the session.
Definition: qofsession.cpp:589
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
file will be upgraded and not be able to be read by prior versions - warn users
Definition: qofbackend.h:103
const gchar * gnc_userdata_dir(void)
Ensure that the user&#39;s configuration directory exists and is minimally populated. ...
Create a new store at the URI even if a store already exists there.
Definition: qofsession.h:128
error in response from server
Definition: qofbackend.h:71
didn&#39;t recognize the file type
Definition: qofbackend.h:94
user login successful, but no permissions to access the desired object
Definition: qofbackend.h:73
convert single-entry accounts to clean double-entry
file does not specify encoding
Definition: qofbackend.h:99
database is newer, we can&#39;t write to it
Definition: qofbackend.h:114
QofBook * qof_session_get_book(const QofSession *session)
Returns the QofBook of this session.
Definition: qofsession.cpp:574
Account handling public routines.
char * gnc_history_get_last(void)
Retrieve the name of the file most recently accessed.
gchar * gnc_uri_normalize_uri(const gchar *uri, gboolean allow_password)
Composes a normalized uri starting from any uri (filename, db spec,...).
gchar * gnc_account_name_violations_errmsg(const gchar *separator, GList *invalid_account_names)
Composes a translatable error message showing which account names clash with the current account sepa...
Definition: Account.cpp:235
void qof_book_mark_session_saved(QofBook *book)
The qof_book_mark_saved() routine marks the book as having been saved (to a file, to a database)...
Definition: qofbook.cpp:383
Functions providing the file history menu.
User attempt to write to a directory reserved for internal use by GnuCash.
Definition: qofbackend.h:101
QofBackendError qof_session_pop_error(QofSession *session)
The qof_session_pop_error() routine can be used to obtain the reason for any failure.
Definition: qofsession.cpp:567
Anchor Scheduled Transaction info in a book.
void gnc_account_reset_convert_bayes_to_flat(void)
Reset the flag that indicates the function imap_convert_bayes_to_flat has been run.
Definition: Account.cpp:5443
void gnc_history_remove_file(const char *oldfile)
Remove all occurrences of a file name from the history list.
undetermined error
Definition: qofbackend.h:79
void gnc_keyring_set_password(const gchar *access_method, const gchar *server, guint32 port, const gchar *service, const gchar *user, const gchar *password)
Attempt to store a password in some trusted keystore.
Definition: gnc-keyring.c:68
file exists, is readable, but is empty
Definition: qofbackend.h:90
QofBackendError qof_session_get_error(QofSession *session)
The qof_session_get_error() routine can be used to obtain the reason for any failure.
Definition: qofsession.cpp:728
gchar * gnc_features_test_unknown(QofBook *book)
Test if the current book relies on features only introduced in a more recent version of GnuCash...
void qof_session_swap_data(QofSession *session_1, QofSession *session_2)
The qof_session_swap_data () method swaps the book of the two given sessions.
Definition: qofsession.cpp:654
file/db version newer than what we can read
Definition: qofbackend.h:69
no backend handler found for this access method (ENOSYS)
Definition: qofbackend.h:60
read failed or file prematurely truncated
Definition: qofbackend.h:89
gboolean qof_book_session_not_saved(const QofBook *book)
qof_book_not_saved() returns the value of the session_dirty flag, set when changes to any object in t...
Definition: qofbook.cpp:375
data in db is corrupt
Definition: qofbackend.h:70
LibDBI has numeric errors.
Definition: qofbackend.h:116
All type declarations for the whole Gnucash engine.
gchar * gnc_uri_add_extension(const gchar *uri, const gchar *extension)
Adds an extension to the uri if:
gboolean gnc_keyring_get_password(GtkWidget *parent, const gchar *access_method, const gchar *server, guint32 port, const gchar *service, gchar **user, gchar **password)
Attempt to retrieve a password to connect to a remote service.
Definition: gnc-keyring.c:167
void qof_book_mark_session_dirty(QofBook *book)
The qof_book_mark_dirty() routine marks the book as having been modified.
Definition: qofbook.cpp:397
the named database doesn&#39;t exist
Definition: qofbackend.h:63
GLib helper routines.
GList * gnc_account_get_descendants(const Account *account)
This routine returns a flat list of all of the accounts that are descendants of the specified account...
Definition: Account.cpp:3014
gboolean gnc_uri_targets_local_fs(const gchar *uri)
Checks if the given uri is either a valid file uri or a local filesystem path.
API for the transaction logger.
gboolean gnc_history_test_for_file(const char *oldfile)
Test for a file name existing in the history list.
couldn&#39;t make a backup of the file
Definition: qofbackend.h:96
File exists, data would be destroyed.
Definition: qofbackend.h:67
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition: qofbook.cpp:497
void qof_event_suspend(void)
Suspend all engine events.
Definition: qofevent.cpp:145
Backend * pointer was unexpectedly null.
Definition: qofbackend.h:61
void qof_event_resume(void)
Resume engine event generation.
Definition: qofevent.cpp:156
cannot write to file/directory
Definition: qofbackend.h:68
bad dbname/login/passwd or network failure
Definition: qofbackend.h:64
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:261
Utility functions for convert uri in separate components and back.
could not complete test for LibDBI bug
Definition: qofbackend.h:117
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
void xaccAccountScrubColorNotSet(QofBook *book)
Remove color slots that have a "Not Set" value, since 2.4.0, fixed in 3.4 This should only be run onc...
Definition: Scrub.cpp:1399
File path resolution utility functions.
gboolean gnc_main_window_all_finish_pending(void)
Tell all pages in all windows to finish any outstanding activities.
gchar * gnc_uri_create_uri(const gchar *scheme, const gchar *hostname, gint32 port, const gchar *username, const gchar *password, const gchar *path)
Composes a normalized uri starting from its separate components.
Commodity handling public routines.
file version so old we can&#39;t read it
Definition: qofbackend.h:93
Open will fail if the URI doesn&#39;t exist or is locked.
Definition: qofsession.h:124
void xaccLogEnable(void)
document me
Definition: TransLog.cpp:99
const gchar * gnc_get_account_separator_string(void)
Returns the account separation character chosen by the user.
Definition: Account.cpp:205
Utility functions for file access.