GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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-gui-query.h"
39 #include "gnc-hooks.h"
40 #include "gnc-keyring.h"
41 #include "gnc-splash.h"
42 #include "gnc-ui.h"
43 #include "gnc-ui-util.h"
44 #include "gnc-uri-utils.h"
45 #include "gnc-window.h"
47 #include "qof.h"
48 #include "TransLog.h"
49 #include "gnc-session.h"
50 #include "gnc-state.h"
51 #include "gnc-autosave.h"
52 
53 
55 /* This static indicates the debugging module that this .o belongs to. */
56 static QofLogModule log_module = GNC_MOD_GUI;
57 
58 static GNCShutdownCB shutdown_cb = NULL;
59 static gint save_in_progress = 0;
60 
61 
62 /********************************************************************\
63  * gnc_file_dialog *
64  * Pops up a file selection dialog (either a "Save As" or an *
65  * "Open"), and returns the name of the file the user selected. *
66  * (This function does not return until the user selects a file *
67  * or presses "Cancel" or the window manager destroy button) *
68  * *
69  * Args: title - the title of the window *
70  * filters - list of GtkFileFilters to use, will be *
71  freed automatically *
72  * default_dir - start the chooser in this directory *
73  * type - what type of dialog (open, save, etc.) *
74  * Return: containing the name of the file the user selected *
75 \********************************************************************/
76 
77 char *
78 gnc_file_dialog (const char * title,
79  GList * filters,
80  const char * starting_dir,
81  GNCFileDialogType type
82  )
83 {
84  GtkWidget *file_box;
85  const char *internal_name;
86  char *file_name = NULL;
87  gchar * okbutton = GTK_STOCK_OPEN;
88  const gchar *ok_icon = NULL;
89  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
90  gint response;
91 
92  ENTER(" ");
93 
94  switch (type)
95  {
96  case GNC_FILE_DIALOG_OPEN:
97  action = GTK_FILE_CHOOSER_ACTION_OPEN;
98  okbutton = GTK_STOCK_OPEN;
99  if (title == NULL)
100  title = _("Open");
101  break;
102  case GNC_FILE_DIALOG_IMPORT:
103  action = GTK_FILE_CHOOSER_ACTION_OPEN;
104  okbutton = _("_Import");
105  if (title == NULL)
106  title = _("Import");
107  break;
108  case GNC_FILE_DIALOG_SAVE:
109  action = GTK_FILE_CHOOSER_ACTION_SAVE;
110  okbutton = GTK_STOCK_SAVE;
111  if (title == NULL)
112  title = _("Save");
113  break;
114  case GNC_FILE_DIALOG_EXPORT:
115  action = GTK_FILE_CHOOSER_ACTION_SAVE;
116  okbutton = _("_Export");
117  ok_icon = GTK_STOCK_CONVERT;
118  if (title == NULL)
119  title = _("Export");
120  break;
121 
122  }
123 
124  file_box = gtk_file_chooser_dialog_new(
125  title,
126  NULL,
127  action,
128  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
129  NULL);
130  if (ok_icon)
131  gnc_gtk_dialog_add_button(file_box, okbutton, ok_icon, GTK_RESPONSE_ACCEPT);
132  else
133  gtk_dialog_add_button(GTK_DIALOG(file_box),
134  okbutton, GTK_RESPONSE_ACCEPT);
135 
136  if (starting_dir)
137  gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (file_box),
138  starting_dir);
139 
140  gtk_window_set_modal(GTK_WINDOW(file_box), TRUE);
141  /*
142  gtk_window_set_transient_for(GTK_WINDOW(file_box),
143  GTK_WINDOW(gnc_ui_get_toplevel()));
144  */
145 
146  if (filters != NULL)
147  {
148  GList* filter;
149  GtkFileFilter* all_filter = gtk_file_filter_new();
150 
151  for (filter = filters; filter; filter = filter->next)
152  {
153  g_return_val_if_fail(GTK_IS_FILE_FILTER(filter->data), NULL);
154  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_box),
155  GTK_FILE_FILTER (filter->data));
156  }
157 
158  gtk_file_filter_set_name (all_filter, _("All files"));
159  gtk_file_filter_add_pattern (all_filter, "*");
160  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_box), all_filter);
161 
162  /* Note: You cannot set a file filter and preselect a file name.
163  * The latter wins, and the filter ends up disabled. Since we are
164  * only setting the starting directory for the chooser dialog,
165  * everything works as expected. */
166  gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (file_box),
167  GTK_FILE_FILTER (filters->data));
168  g_list_free (filters);
169  }
170 
171  response = gtk_dialog_run(GTK_DIALOG(file_box));
172 
173  if (response == GTK_RESPONSE_ACCEPT)
174  {
175  /* look for constructs like postgres://foo */
176  internal_name = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER (file_box));
177  if (strstr (internal_name, "file://") == internal_name)
178  {
179  /* nope, a local file name */
180  internal_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (file_box));
181  }
182  file_name = g_strdup(internal_name);
183  }
184  gtk_widget_destroy(GTK_WIDGET(file_box));
185  LEAVE("%s", file_name ? file_name : "(null)");
186  return file_name;
187 }
188 
189 
190 gboolean
191 show_session_error (QofBackendError io_error,
192  const char *newfile,
193  GNCFileDialogType type)
194 {
195  GtkWidget *parent = gnc_ui_get_toplevel();
196  GtkWidget *dialog;
197  gboolean uh_oh = TRUE;
198  const char *fmt, *label;
199  gchar *displayname;
200  gint response;
201 
202  if (NULL == newfile)
203  {
204  displayname = g_strdup(_("(null)"));
205  }
206  else if (! gnc_uri_is_file_uri (newfile)) /* Hide the db password in error messages */
207  displayname = gnc_uri_normalize_uri ( newfile, FALSE);
208  else
209  displayname = g_strdup (newfile);
210 
211  switch (io_error)
212  {
213  case ERR_BACKEND_NO_ERR:
214  uh_oh = FALSE;
215  break;
216 
218  fmt = _("No suitable backend was found for %s.");
219  gnc_error_dialog(parent, fmt, displayname);
220  break;
221 
223  fmt = _("The URL %s is not supported by this version of GnuCash.");
224  gnc_error_dialog (parent, fmt, displayname);
225  break;
226 
227  case ERR_BACKEND_BAD_URL:
228  fmt = _("Can't parse the URL %s.");
229  gnc_error_dialog (parent, fmt, displayname);
230  break;
231 
233  fmt = _("Can't connect to %s. "
234  "The host, username or password were incorrect.");
235  gnc_error_dialog (parent, fmt, displayname);
236  break;
237 
239  fmt = _("Can't connect to %s. "
240  "Connection was lost, unable to send data.");
241  gnc_error_dialog (parent, fmt, displayname);
242  break;
243 
244  case ERR_BACKEND_TOO_NEW:
245  fmt = _("This file/URL appears to be from a newer version "
246  "of GnuCash. You must upgrade your version of GnuCash "
247  "to work with this data.");
248  gnc_error_dialog (parent, "%s", fmt);
249  break;
250 
252  fmt = _("The database %s doesn't seem to exist. "
253  "Do you want to create it?");
254  if (gnc_verify_dialog (parent, TRUE, fmt, displayname))
255  {
256  uh_oh = FALSE;
257  }
258  break;
259 
260  case ERR_BACKEND_LOCKED:
261  switch (type)
262  {
263  case GNC_FILE_DIALOG_OPEN:
264  default:
265  label = GTK_STOCK_OPEN;
266  fmt = _("GnuCash could not obtain the lock for %s. "
267  "That database may be in use by another user, "
268  "in which case you should not open the database. "
269  "Do you want to proceed with opening the database?");
270  break;
271 
272  case GNC_FILE_DIALOG_IMPORT:
273  label = _("Import");
274  fmt = _("GnuCash could not obtain the lock for %s. "
275  "That database may be in use by another user, "
276  "in which case you should not import the database. "
277  "Do you want to proceed with importing the database?");
278  break;
279 
280  case GNC_FILE_DIALOG_SAVE:
281  label = GTK_STOCK_SAVE;
282  fmt = _("GnuCash could not obtain the lock for %s. "
283  "That database may be in use by another user, "
284  "in which case you should not save the database. "
285  "Do you want to proceed with saving the database?");
286  break;
287 
288  case GNC_FILE_DIALOG_EXPORT:
289  label = _("Export");
290  fmt = _("GnuCash could not obtain the lock for %s. "
291  "That database may be in use by another user, "
292  "in which case you should not export the database. "
293  "Do you want to proceed with exporting the database?");
294  break;
295  }
296 
297  dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
298  GTK_DIALOG_DESTROY_WITH_PARENT,
299  GTK_MESSAGE_QUESTION,
300  GTK_BUTTONS_NONE,
301  fmt,
302  displayname);
303  gtk_dialog_add_buttons(GTK_DIALOG(dialog),
304  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
305  label, GTK_RESPONSE_YES,
306  NULL);
307  if (parent == NULL)
308  gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
309  response = gtk_dialog_run(GTK_DIALOG(dialog));
310  gtk_widget_destroy(dialog);
311  uh_oh = (response != GTK_RESPONSE_YES);
312  break;
313 
315  fmt = _("GnuCash could not write to %s. "
316  "That database may be on a read-only file system, "
317  "or you may not have write permission for the directory.");
318  gnc_error_dialog (parent, fmt, displayname);
319  break;
320 
322  fmt = _("The file/URL %s "
323  "does not contain GnuCash data or the data is corrupt.");
324  gnc_error_dialog (parent, fmt, displayname);
325  break;
326 
328  fmt = _("The server at URL %s "
329  "experienced an error or encountered bad or corrupt data.");
330  gnc_error_dialog (parent, fmt, displayname);
331  break;
332 
333  case ERR_BACKEND_PERM:
334  fmt = _("You do not have permission to access %s.");
335  gnc_error_dialog (parent, fmt, displayname);
336  break;
337 
338  case ERR_BACKEND_MISC:
339  fmt = _("An error occurred while processing %s.");
340  gnc_error_dialog (parent, fmt, displayname);
341  break;
342 
344  fmt = _("There was an error reading the file. "
345  "Do you want to continue?");
346  if (gnc_verify_dialog (parent, TRUE, "%s", fmt))
347  {
348  uh_oh = FALSE;
349  }
350  break;
351 
353  fmt = _("There was an error parsing the file %s.");
354  gnc_error_dialog (parent, fmt, displayname);
355  break;
356 
358  fmt = _("The file %s is empty.");
359  gnc_error_dialog (parent, fmt, displayname);
360  break;
361 
363  if (type == GNC_FILE_DIALOG_SAVE)
364  {
365  uh_oh = FALSE;
366  }
367  else
368  {
369  fmt = _("The file %s could not be found.");
370  gnc_error_dialog (parent, fmt, displayname);
371  }
372  break;
373 
375  fmt = _("This file is from an older version of GnuCash. "
376  "Do you want to continue?");
377  if (gnc_verify_dialog (parent, TRUE, "%s", fmt))
378  {
379  uh_oh = FALSE;
380  }
381  break;
382 
384  fmt = _("The file type of file %s is unknown.");
385  gnc_error_dialog(parent, fmt, displayname);
386  break;
387 
389  fmt = _("Could not make a backup of the file %s");
390  gnc_error_dialog(parent, fmt, displayname);
391  break;
392 
394  fmt = _("Could not write to file %s. Check that you have "
395  "permission to write to this file and that "
396  "there is sufficient space to create it.");
397  gnc_error_dialog(parent, fmt, displayname);
398  break;
399 
401  fmt = _("No read permission to read from file %s.");
402  gnc_error_dialog (parent, fmt, displayname);
403  break;
404 
406  /* Translators: the first %s is a path in the filesystem,
407  * the second %s is PACKAGE_NAME, which by default is "GnuCash"
408  */
409  fmt = _("You attempted to save in\n%s\nor a subdirectory thereof. "
410  "This is not allowed as %s reserves that directory for internal use.\n\n"
411  "Please try again in a different directory.");
412  gnc_error_dialog (parent, fmt, gnc_dotgnucash_dir(), PACKAGE_NAME);
413  break;
414 
415  case ERR_SQL_DB_TOO_OLD:
416  fmt = _("This database is from an older version of GnuCash. "
417  "Select OK to upgrade it to the current version, Cancel "
418  "to mark it read-only.");
419 
420  response = gnc_ok_cancel_dialog(parent, GTK_RESPONSE_CANCEL, "%s", fmt);
421  uh_oh = (response == GTK_RESPONSE_CANCEL);
422  break;
423 
424  case ERR_SQL_DB_TOO_NEW:
425  fmt = _("This database is from a newer version of GnuCash. "
426  "This version can read it, but cannot safely save to it. "
427  "It will be marked read-only until you do File>Save As, "
428  "but data may be lost in writing to the old version.");
429  gnc_warning_dialog (parent, "%s", fmt);
430  uh_oh = TRUE;
431  break;
432 
433  case ERR_SQL_DB_BUSY:
434  fmt = _("The SQL database is in use by other users, "
435  "and the upgrade cannot be performed until they logoff. "
436  "If there are currently no other users, consult the "
437  "documentation to learn how to clear out dangling login "
438  "sessions.");
439  gnc_error_dialog (parent, "%s", fmt);
440  break;
441 
442  case ERR_SQL_BAD_DBI:
443 
444  fmt = _("The library \"libdbi\" installed on your system doesn't correctly "
445  "store large numbers. This means GnuCash cannot use SQL databases "
446  "correctly. Gnucash will not open or save to SQL databases until this is "
447  "fixed by installing a different version of \"libdbi\". Please see "
448  "https://bugzilla.gnome.org/show_bug.cgi?id=611936 for more "
449  "information.");
450 
451  gnc_error_dialog (parent, "%s", fmt);
452  break;
453 
455 
456  fmt = _("GnuCash could not complete a critical test for the presence of "
457  "a bug in the \"libdbi\" library. This may be caused by a "
458  "permissions misconfiguration of your SQL database. Please see "
459  "https://bugzilla.gnome.org/show_bug.cgi?id=645216 for more "
460  "information.");
461 
462  gnc_error_dialog (parent, "%s", fmt);
463  break;
464 
466  fmt = _("This file is from an older version of GnuCash and will be "
467  "upgraded when saved by this version. You will not be able "
468  "to read the saved file from the older version of Gnucash "
469  "(it will report an \"error parsing the file\"). If you wish "
470  "to preserve the old version, exit without saving.");
471  gnc_warning_dialog (parent, "%s", fmt);
472  uh_oh = FALSE;
473  break;
474 
475  default:
476  PERR("FIXME: Unhandled error %d", io_error);
477  fmt = _("An unknown I/O error (%d) occurred.");
478  gnc_error_dialog (parent, fmt, io_error);
479  break;
480  }
481 
482  g_free (displayname);
483  return uh_oh;
484 }
485 
486 static void
487 gnc_add_history (QofSession * session)
488 {
489  const gchar *url;
490  char *file;
491 
492  if (!session) return;
493 
494  url = qof_session_get_url ( session );
495  if ( !url )
496  return;
497 
498  if ( gnc_uri_is_file_uri ( url ) )
499  file = gnc_uri_get_path ( url );
500  else
501  file = gnc_uri_normalize_uri ( url, FALSE ); /* Note that the password is not saved in history ! */
502 
503  gnc_history_add_file (file);
504 }
505 
506 static void
507 gnc_book_opened (void)
508 {
509  gnc_hook_run(HOOK_BOOK_OPENED, gnc_get_current_session());
510 }
511 
512 void
513 gnc_file_new (void)
514 {
515  QofSession *session;
516 
517  /* If user attempts to start a new session before saving results of
518  * the last one, prompt them to clean up their act. */
519  if (!gnc_file_query_save (TRUE))
520  return;
521 
522  if (gnc_current_session_exist())
523  {
524  session = gnc_get_current_session ();
525 
526  /* close any ongoing file sessions, and free the accounts.
527  * disable events so we don't get spammed by redraws. */
529 
531  gnc_hook_run(HOOK_BOOK_CLOSED, session);
532 
533  gnc_close_gui_component_by_session (session);
534  gnc_state_save (session);
535  gnc_clear_current_session();
536  qof_event_resume ();
537  }
538 
539  /* start a new book */
540  gnc_get_current_session ();
541 
542  gnc_hook_run(HOOK_NEW_BOOK, NULL);
543 
544  gnc_gui_refresh_all ();
545 
546  /* Call this after re-enabling events. */
547  gnc_book_opened ();
548 }
549 
550 gboolean
551 gnc_file_query_save (gboolean can_cancel)
552 {
553  GtkWidget *parent = gnc_ui_get_toplevel();
554  QofBook *current_book;
555 
556  if (!gnc_current_session_exist())
557  return TRUE;
558 
559  current_book = qof_session_get_book (gnc_get_current_session ());
560  /* Remove any pending auto-save timeouts */
561  gnc_autosave_remove_timer(current_book);
562 
563  /* If user wants to mess around before finishing business with
564  * the old file, give him a chance to figure out what's up.
565  * Pose the question as a "while" loop, so that if user screws
566  * up the file-selection dialog, we don't blow him out of the water;
567  * instead, give them another chance to say "no" to the verify box.
568  */
569  while (qof_book_session_not_saved(current_book))
570  {
571  GtkWidget *dialog;
572  gint response;
573  const char *title = _("Save changes to the file?");
574  /* This should be the same message as in gnc-main-window.c */
575  time64 oldest_change;
576  gint minutes;
577 
578  dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
579  GTK_DIALOG_DESTROY_WITH_PARENT,
580  GTK_MESSAGE_QUESTION,
581  GTK_BUTTONS_NONE,
582  "%s", title);
583  oldest_change = qof_book_get_session_dirty_time(current_book);
584  minutes = (gnc_time (NULL) - oldest_change) / 60 + 1;
585  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
586  ngettext("If you don't save, changes from the past %d minute will be discarded.",
587  "If you don't save, changes from the past %d minutes will be discarded.",
588  minutes), minutes);
589  gtk_dialog_add_button(GTK_DIALOG(dialog),
590  _("Continue _Without Saving"), GTK_RESPONSE_OK);
591 
592  if (can_cancel)
593  gtk_dialog_add_button(GTK_DIALOG(dialog),
594  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
595  gtk_dialog_add_button(GTK_DIALOG(dialog),
596  GTK_STOCK_SAVE, GTK_RESPONSE_YES);
597 
598  gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES);
599 
600  response = gtk_dialog_run(GTK_DIALOG(dialog));
601  gtk_widget_destroy(dialog);
602 
603  switch (response)
604  {
605  case GTK_RESPONSE_YES:
606  gnc_file_save ();
607  /* Go check the loop condition. */
608  break;
609 
610  case GTK_RESPONSE_CANCEL:
611  default:
612  if (can_cancel)
613  return FALSE;
614  /* No cancel function available. */
615  /* Fall through */
616 
617  case GTK_RESPONSE_OK:
618  return TRUE;
619  }
620  }
621 
622  return TRUE;
623 }
624 
625 
626 /* private utilities for file open; done in two stages */
627 
628 #define RESPONSE_NEW 1
629 #define RESPONSE_OPEN 2
630 #define RESPONSE_QUIT 3
631 #define RESPONSE_READONLY 4
632 
633 static gboolean
634 gnc_post_file_open (const char * filename, gboolean is_readonly)
635 {
636  QofSession *current_session, *new_session;
637  QofBook *new_book;
638  GList *invalid_account_names;
639  gboolean uh_oh = FALSE;
640  char * newfile;
641  QofBackendError io_err = ERR_BACKEND_NO_ERR;
642 
643  gchar *protocol = NULL;
644  gchar *hostname = NULL;
645  gchar *username = NULL;
646  gchar *password = NULL;
647  gchar *path = NULL;
648  gint32 port = 0;
649 
650 
651  ENTER("filename %s", filename);
652 RESTART:
653  if (!filename || (*filename == '\0')) return FALSE;
654 
655  /* Convert user input into a normalized uri
656  * Note that the normalized uri for internal use can have a password */
657  newfile = gnc_uri_normalize_uri ( filename, TRUE );
658  if (!newfile)
659  {
660  show_session_error (ERR_FILEIO_FILE_NOT_FOUND, filename,
661  GNC_FILE_DIALOG_OPEN);
662  return FALSE;
663  }
664 
665  gnc_uri_get_components (newfile, &protocol, &hostname,
666  &port, &username, &password, &path);
667 
668  /* If the file to open is a database, and no password was given,
669  * attempt to look it up in a keyring. If that fails the keyring
670  * function will ask the user to enter a password. The user can
671  * cancel this dialog, in which case the open file action will be
672  * abandoned.
673  */
674  if ( !gnc_uri_is_file_protocol (protocol) && !password)
675  {
676  gboolean have_valid_pw = FALSE;
677  have_valid_pw = gnc_keyring_get_password ( NULL, protocol, hostname, port,
678  path, &username, &password );
679  if (!have_valid_pw)
680  return FALSE;
681 
682  /* Got password. Recreate the uri to use internally. */
683  g_free ( newfile );
684  newfile = gnc_uri_create_uri ( protocol, hostname, port,
685  username, password, path);
686  }
687 
688  /* For file based uri's, remember the directory as the default. */
689  if (gnc_uri_is_file_protocol(protocol))
690  {
691  gchar *default_dir = g_path_get_dirname(path);
692  gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE, default_dir);
693  g_free(default_dir);
694  }
695 
696  /* disable events while moving over to the new set of accounts;
697  * the mass deletion of accounts and transactions during
698  * switchover would otherwise cause excessive redraws. */
700 
701  /* Change the mouse to a busy cursor */
702  gnc_set_busy_cursor (NULL, TRUE);
703 
704  /* -------------- BEGIN CORE SESSION CODE ------------- */
705  /* -- this code is almost identical in FileOpen and FileSaveAs -- */
706  if (gnc_current_session_exist())
707  {
708  current_session = gnc_get_current_session();
709  qof_session_call_close_hooks(current_session);
710  gnc_hook_run(HOOK_BOOK_CLOSED, current_session);
711  gnc_close_gui_component_by_session (current_session);
712  gnc_state_save (current_session);
713  gnc_clear_current_session();
714  }
715 
716  /* load the accounts from the users datafile */
717  /* but first, check to make sure we've got a session going. */
718  new_session = qof_session_new ();
719 
720  // Begin the new session. If we are in read-only mode, ignore the locks.
721  qof_session_begin (new_session, newfile, is_readonly, FALSE, FALSE);
722  io_err = qof_session_get_error (new_session);
723 
724  if (ERR_BACKEND_BAD_URL == io_err)
725  {
726  gchar *directory;
727  show_session_error (io_err, newfile, GNC_FILE_DIALOG_OPEN);
728  io_err = ERR_BACKEND_NO_ERR;
729  if (g_file_test (filename, G_FILE_TEST_IS_DIR))
730  directory = g_strdup (filename);
731  else
732  directory = gnc_get_default_directory (GNC_PREFS_GROUP_OPEN_SAVE);
733 
734  filename = gnc_file_dialog (NULL, NULL, directory,
735  GNC_FILE_DIALOG_OPEN);
736  qof_session_destroy (new_session);
737  new_session = NULL;
738  g_free (directory);
739  goto RESTART;
740  }
741  /* if file appears to be locked, ask the user ... */
742  else if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
743  {
744  GtkWidget *dialog;
745  gchar *displayname = NULL;
746 
747  char *fmt1 = _("GnuCash could not obtain the lock for %s.");
748  char *fmt2 = ((ERR_BACKEND_LOCKED == io_err) ?
749  _("That database may be in use by another user, "
750  "in which case you should not open the database. "
751  "What would you like to do?") :
752  _("That database may be on a read-only file system, "
753  "or you may not have write permission for the directory. "
754  "If you proceed you may not be able to save any changes. "
755  "What would you like to do?")
756  );
757  int rc;
758 
759  if (! gnc_uri_is_file_uri (newfile)) /* Hide the db password in error messages */
760  displayname = gnc_uri_normalize_uri ( newfile, FALSE);
761  else
762  displayname = g_strdup (newfile);
763 
764  // Bug#467521: on Mac (and maybe Win?), the dialog will appear below the
765  // splash, but is modal, so we can't get rid of the splash... So, get
766  // rid of it now.
767  gnc_destroy_splash_screen();
768 
769  dialog = gtk_message_dialog_new(NULL,
770  0,
771  GTK_MESSAGE_WARNING,
772  GTK_BUTTONS_NONE,
773  fmt1, displayname);
774  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
775  "%s", fmt2);
776  gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
777 
778  gnc_gtk_dialog_add_button(dialog, _("_Open Read-Only"),
779  GTK_STOCK_REVERT_TO_SAVED, RESPONSE_READONLY);
780  gnc_gtk_dialog_add_button(dialog, _("_Create New File"),
781  GTK_STOCK_NEW, RESPONSE_NEW);
782  gnc_gtk_dialog_add_button(dialog, _("Open _Anyway"),
783  GTK_STOCK_OPEN, RESPONSE_OPEN);
784  if (shutdown_cb)
785  gtk_dialog_add_button(GTK_DIALOG(dialog),
786  GTK_STOCK_QUIT, RESPONSE_QUIT);
787  rc = gtk_dialog_run(GTK_DIALOG(dialog));
788  gtk_widget_destroy(dialog);
789  g_free (displayname);
790 
791  if (rc == GTK_RESPONSE_DELETE_EVENT)
792  {
793  rc = shutdown_cb ? RESPONSE_QUIT : RESPONSE_NEW;
794  }
795  switch (rc)
796  {
797  case RESPONSE_QUIT:
798  if (shutdown_cb)
799  shutdown_cb(0);
800  g_assert(1);
801  break;
802  case RESPONSE_READONLY:
803  is_readonly = TRUE;
804  // re-enable the splash screen, file loading and display of
805  // reports may take some time
806  gnc_show_splash_screen();
807  /* user told us to open readonly. We do ignore locks (just as before), but now also force the opening. */
808  qof_session_begin (new_session, newfile, is_readonly, FALSE, TRUE);
809  break;
810  case RESPONSE_OPEN:
811  // re-enable the splash screen, file loading and display of
812  // reports may take some time
813  gnc_show_splash_screen();
814  /* user told us to ignore locks. So ignore them. */
815  qof_session_begin (new_session, newfile, TRUE, FALSE, FALSE);
816  break;
817  default:
818  /* Can't use the given file, so just create a new
819  * database so that the user will get a window that
820  * they can click "Exit" on.
821  */
822  gnc_file_new ();
823  break;
824  }
825  }
826  /* if the database doesn't exist, ask the user ... */
827  else if ((ERR_BACKEND_NO_SUCH_DB == io_err))
828  {
829  if (FALSE == show_session_error (io_err, newfile, GNC_FILE_DIALOG_OPEN))
830  {
831  /* user told us to create a new database. Do it. We
832  * shouldn't have to worry about locking or clobbering,
833  * it's supposed to be new. */
834  qof_session_begin (new_session, newfile, FALSE, TRUE, FALSE);
835  }
836  }
837 
838  /* Check for errors again, since above may have cleared the lock.
839  * If its still locked, still, doesn't exist, still too old, then
840  * don't bother with the message, just die. */
841  io_err = qof_session_get_error (new_session);
842  if ((ERR_BACKEND_LOCKED == io_err) ||
843  (ERR_BACKEND_READONLY == io_err) ||
844  (ERR_BACKEND_NO_SUCH_DB == io_err))
845  {
846  uh_oh = TRUE;
847  }
848 
849  else
850  {
851  uh_oh = show_session_error (io_err, newfile, GNC_FILE_DIALOG_OPEN);
852  }
853 
854  if (!uh_oh)
855  {
856  Account *new_root;
857 
858  /* If the new "file" is a database, attempt to store the password
859  * in a keyring. GnuCash itself will not save it.
860  */
861  if ( !gnc_uri_is_file_protocol (protocol))
862  gnc_keyring_set_password ( protocol, hostname, port,
863  path, username, password );
864 
865  xaccLogDisable();
866  gnc_window_show_progress(_("Loading user data..."), 0.0);
867  qof_session_load (new_session, gnc_window_show_progress);
868  gnc_window_show_progress(NULL, -1.0);
869  xaccLogEnable();
870 
871  if (is_readonly)
872  {
873  // If the user chose "open read-only" above, make sure to have this
874  // read-only here.
876  }
877 
878  /* check for i/o error, put up appropriate error dialog */
879  io_err = qof_session_pop_error (new_session);
880 
881  if (io_err == ERR_FILEIO_NO_ENCODING)
882  {
883  if (gnc_xml_convert_single_file (newfile))
884  {
885  /* try to load once again */
886  gnc_window_show_progress(_("Loading user data..."), 0.0);
887  qof_session_load (new_session, gnc_window_show_progress);
888  gnc_window_show_progress(NULL, -1.0);
889  xaccLogEnable();
890  io_err = qof_session_get_error (new_session);
891  }
892  else
893  {
894  io_err = ERR_FILEIO_PARSE_ERROR;
895  }
896  }
897 
898  uh_oh = show_session_error (io_err, newfile, GNC_FILE_DIALOG_OPEN);
899  /* Attempt to update the database if it's too old */
900  if ( !uh_oh && io_err == ERR_SQL_DB_TOO_OLD )
901  {
902  gnc_window_show_progress(_("Re-saving user data..."), 0.0);
903  qof_session_safe_save(new_session, gnc_window_show_progress);
904  io_err = qof_session_get_error(new_session);
905  uh_oh = show_session_error(io_err, newfile, GNC_FILE_DIALOG_SAVE);
906  }
907  /* Database is either too old and couldn't (or user didn't
908  * want it to) be updated or it's too new. Mark it as
909  * read-only
910  */
911  if (uh_oh && (io_err == ERR_SQL_DB_TOO_OLD ||
912  io_err == ERR_SQL_DB_TOO_NEW))
913  {
915  uh_oh = FALSE;
916  }
917  new_root = gnc_book_get_root_account (qof_session_get_book (new_session));
918  if (uh_oh) new_root = NULL;
919 
920  /* Umm, came up empty-handed, but no error:
921  * The backend forgot to set an error. So make one up. */
922  if (!uh_oh && !new_root)
923  {
924  uh_oh = show_session_error (ERR_BACKEND_MISC, newfile,
925  GNC_FILE_DIALOG_OPEN);
926  }
927 
928  /* test for unknown features. */
929  if (!uh_oh)
930  {
931  gchar *msg = gnc_features_test_unknown(qof_session_get_book (new_session));
932 
933  if (msg)
934  {
935  uh_oh = TRUE;
936 
937  // XXX: should pull out the file name here */
938  gnc_error_dialog(gnc_ui_get_toplevel(), msg, "");
939  g_free (msg);
940  }
941  }
942  }
943 
944  g_free (protocol);
945  g_free (hostname);
946  g_free (username);
947  g_free (password);
948  g_free (path);
949 
950  gnc_unset_busy_cursor (NULL);
951 
952  /* going down -- abandon ship */
953  if (uh_oh)
954  {
955  xaccLogDisable();
956  qof_session_destroy (new_session);
957  xaccLogEnable();
958 
959  /* well, no matter what, I think it's a good idea to have a root
960  * account around. For example, early in the gnucash startup
961  * sequence, the user opens a file; if this open fails for any
962  * reason, we don't want to leave them high & dry without a root
963  * account, because if the user continues, then bad things will
964  * happen. */
965  gnc_get_current_session ();
966 
967  g_free (newfile);
968 
969  qof_event_resume ();
970  gnc_gui_refresh_all ();
971 
972  return FALSE;
973  }
974 
975  /* if we got to here, then we've successfully gotten a new session */
976  /* close up the old file session (if any) */
977  gnc_set_current_session(new_session);
978 
979  /* --------------- END CORE SESSION CODE -------------- */
980 
981  /* clean up old stuff, and then we're outta here. */
982  gnc_add_history (new_session);
983 
984  g_free (newfile);
985 
986  qof_event_resume ();
987  gnc_gui_refresh_all ();
988 
989  /* Call this after re-enabling events. */
990  gnc_book_opened ();
991 
992  /* Check for account names that may contain the current separator character
993  * and inform the user if there are any */
994  new_book = gnc_get_current_book();
995  invalid_account_names = gnc_account_list_name_violations ( new_book,
997  if ( invalid_account_names )
998  {
1000  invalid_account_names );
1001  gnc_warning_dialog(NULL, "%s", message);
1002  g_free ( message );
1003  }
1004 
1005  return TRUE;
1006 }
1007 
1008 /* Routine that pops up a file chooser dialog
1009  *
1010  * Note: this dialog is used when dbi is not enabled
1011  * so the paths used in here are always file
1012  * paths, never db uris.
1013  */
1014 gboolean
1015 gnc_file_open (void)
1016 {
1017  const gchar * newfile;
1018  gchar *last = NULL;
1019  gchar *default_dir = NULL;
1020  gboolean result;
1021 
1022  if (!gnc_file_query_save (TRUE))
1023  return FALSE;
1024 
1025  if ( last && gnc_uri_is_file_uri ( last ) )
1026  {
1027  gchar *filepath = gnc_uri_get_path ( last );
1028  default_dir = g_path_get_dirname( filepath );
1029  g_free ( filepath );
1030  }
1031  else
1032  default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_OPEN_SAVE);
1033 
1034  newfile = gnc_file_dialog (_("Open"), NULL, default_dir, GNC_FILE_DIALOG_OPEN);
1035  g_free ( last );
1036  g_free ( default_dir );
1037 
1038  result = gnc_post_file_open ( newfile, /*is_readonly*/ FALSE );
1039 
1040  /* This dialogue can show up early in the startup process. If the
1041  * user fails to pick a file (by e.g. hitting the cancel button), we
1042  * might be left with a null topgroup, which leads to nastiness when
1043  * user goes to create their very first account. So create one. */
1044  gnc_get_current_session ();
1045 
1046  return result;
1047 }
1048 
1049 gboolean
1050 gnc_file_open_file (const char * newfile, gboolean open_readonly)
1051 {
1052  if (!newfile) return FALSE;
1053 
1054  if (!gnc_file_query_save (TRUE))
1055  return FALSE;
1056 
1057  return gnc_post_file_open (newfile, open_readonly);
1058 }
1059 
1060 /* Note: this dialog will only be used when dbi is not enabled
1061  * paths used in it always refer to files and are
1062  * never db uris
1063  */
1064 void
1065 gnc_file_export (void)
1066 {
1067  const char *filename;
1068  char *default_dir = NULL; /* Default to last open */
1069  char *last;
1070 
1071  ENTER(" ");
1072 
1073  last = gnc_history_get_last();
1074  if ( last && gnc_uri_is_file_uri ( last ) )
1075  {
1076  gchar *filepath = gnc_uri_get_path ( last );
1077  default_dir = g_path_get_dirname( filepath );
1078  g_free ( filepath );
1079  }
1080  else
1081  default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_EXPORT);
1082 
1083  filename = gnc_file_dialog (_("Save"), NULL, default_dir,
1084  GNC_FILE_DIALOG_SAVE);
1085  g_free ( last );
1086  g_free ( default_dir );
1087  if (!filename) return;
1088 
1089  gnc_file_do_export( filename );
1090 
1091  LEAVE (" ");
1092 }
1093 
1094 void
1095 gnc_file_do_export(const char * filename)
1096 {
1097  QofSession *current_session, *new_session;
1098  gboolean ok;
1099  QofBackendError io_err = ERR_BACKEND_NO_ERR;
1100  gchar *norm_file;
1101  gchar *newfile;
1102  const gchar *oldfile;
1103 
1104  gchar *protocol = NULL;
1105  gchar *hostname = NULL;
1106  gchar *username = NULL;
1107  gchar *password = NULL;
1108  gchar *path = NULL;
1109  gint32 port = 0;
1110 
1111  ENTER(" ");
1112 
1113  /* Convert user input into a normalized uri
1114  * Note that the normalized uri for internal use can have a password */
1115  norm_file = gnc_uri_normalize_uri ( filename, TRUE );
1116  if (!norm_file)
1117  {
1118  show_session_error (ERR_FILEIO_FILE_NOT_FOUND, filename,
1119  GNC_FILE_DIALOG_EXPORT);
1120  return;
1121  }
1122 
1123  newfile = gnc_uri_add_extension (norm_file, GNC_DATAFILE_EXT);
1124  g_free (norm_file);
1125  gnc_uri_get_components (newfile, &protocol, &hostname,
1126  &port, &username, &password, &path);
1127 
1128  /* Save As can't use the generic 'file' protocol. If the user didn't set
1129  * a specific protocol, assume the default 'xml'.
1130  */
1131  if (g_strcmp0 (protocol, "file") == 0)
1132  {
1133  g_free (protocol);
1134  protocol = g_strdup ("xml");
1135  norm_file = gnc_uri_create_uri (protocol, hostname, port,
1136  username, password, path);
1137  g_free (newfile);
1138  newfile = norm_file;
1139  }
1140 
1141  /* Some extra steps for file based uri's only */
1142  if (gnc_uri_is_file_protocol(protocol))
1143  {
1144  /* Remember the directory as the default. */
1145  gchar *default_dir = g_path_get_dirname(path);
1146  gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE, default_dir);
1147  g_free(default_dir);
1148 
1149  /* Prevent user to store file in GnuCash' private configuration
1150  * directory (~/.gnucash by default in linux, but can be overridden)
1151  */
1152  DEBUG("User path: %s, dotgnucash_dir: %s", path, gnc_dotgnucash_dir());
1153  if (g_str_has_prefix(path, gnc_dotgnucash_dir()))
1154  {
1155  show_session_error (ERR_FILEIO_RESERVED_WRITE, newfile, GNC_FILE_DIALOG_SAVE);
1156  return;
1157  }
1158  }
1159 
1160  /* Check to see if the user specified the same file as the current
1161  * file. If so, prevent the export from happening to avoid killing this file */
1162  current_session = gnc_get_current_session ();
1163  oldfile = qof_session_get_url(current_session);
1164  if (oldfile && (strcmp(oldfile, newfile) == 0))
1165  {
1166  g_free (newfile);
1167  show_session_error (ERR_FILEIO_WRITE_ERROR, filename,
1168  GNC_FILE_DIALOG_EXPORT);
1169  return;
1170  }
1171 
1173 
1174  /* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
1175 
1176  new_session = qof_session_new ();
1177  qof_session_begin (new_session, newfile, FALSE, TRUE, FALSE);
1178 
1179  io_err = qof_session_get_error (new_session);
1180  /* If the file exists and would be clobbered, ask the user */
1181  if (ERR_BACKEND_STORE_EXISTS == io_err)
1182  {
1183  const char *format = _("The file %s already exists. "
1184  "Are you sure you want to overwrite it?");
1185 
1186  const char *name;
1187  if ( gnc_uri_is_file_uri ( newfile ) )
1188  name = gnc_uri_get_path ( newfile );
1189  else
1190  name = gnc_uri_normalize_uri ( newfile, FALSE );
1191  /* if user says cancel, we should break out */
1192  if (!gnc_verify_dialog (NULL, FALSE, format, name))
1193  {
1194  return;
1195  }
1196  qof_session_begin (new_session, newfile, FALSE, TRUE, TRUE);
1197  }
1198  /* if file appears to be locked, ask the user ... */
1199  if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
1200  {
1201  if (FALSE == show_session_error (io_err, newfile, GNC_FILE_DIALOG_EXPORT))
1202  {
1203  /* user told us to ignore locks. So ignore them. */
1204  qof_session_begin (new_session, newfile, TRUE, FALSE, FALSE);
1205  }
1206  }
1207 
1208  /* --------------- END CORE SESSION CODE -------------- */
1209 
1210  /* use the current session to save to file */
1211  gnc_set_busy_cursor (NULL, TRUE);
1212  gnc_window_show_progress(_("Exporting file..."), 0.0);
1213  ok = qof_session_export (new_session, current_session,
1214  gnc_window_show_progress);
1215  gnc_window_show_progress(NULL, -1.0);
1216  gnc_unset_busy_cursor (NULL);
1217  xaccLogDisable();
1218  qof_session_destroy (new_session);
1219  xaccLogEnable();
1220  qof_event_resume();
1221 
1222  if (!ok)
1223  {
1224  /* %s is the strerror(3) error string of the error that occurred. */
1225  const char *format = _("There was an error saving the file.\n\n%s");
1226 
1227  gnc_error_dialog (NULL, format, strerror(errno));
1228  return;
1229  }
1230 }
1231 
1232 static gboolean been_here_before = FALSE;
1233 
1234 void
1235 gnc_file_save (void)
1236 {
1237  QofBackendError io_err;
1238  const char * newfile;
1239  QofSession *session;
1240  ENTER (" ");
1241 
1242  /* hack alert -- Somehow make sure all in-progress edits get committed! */
1243 
1244  /* If we don't have a filename/path to save to get one. */
1245  session = gnc_get_current_session ();
1246 
1247  if (!qof_session_get_url(session))
1248  {
1249  gnc_file_save_as ();
1250  return;
1251  }
1252 
1254  {
1255  gint response = gnc_ok_cancel_dialog(gnc_ui_get_toplevel(),
1256  GTK_RESPONSE_CANCEL,
1257  _("The database was opened read-only. "
1258  "Do you want to save it to a different place?"));
1259  if (response == GTK_RESPONSE_OK)
1260  {
1261  gnc_file_save_as ();
1262  }
1263  return;
1264  }
1265 
1266  /* use the current session to save to file */
1267  save_in_progress++;
1268  gnc_set_busy_cursor (NULL, TRUE);
1269  gnc_window_show_progress(_("Writing file..."), 0.0);
1270  qof_session_save (session, gnc_window_show_progress);
1271  gnc_window_show_progress(NULL, -1.0);
1272  gnc_unset_busy_cursor (NULL);
1273  save_in_progress--;
1274 
1275  /* Make sure everything's OK - disk could be full, file could have
1276  become read-only etc. */
1277  io_err = qof_session_get_error (session);
1278  if (ERR_BACKEND_NO_ERR != io_err)
1279  {
1280  newfile = qof_session_get_url(session);
1281  show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE);
1282 
1283  if (been_here_before) return;
1284  been_here_before = TRUE;
1285  gnc_file_save_as (); /* been_here prevents infinite recursion */
1286  been_here_before = FALSE;
1287  return;
1288  }
1289 
1290  xaccReopenLog();
1291  gnc_add_history (session);
1292  gnc_hook_run(HOOK_BOOK_SAVED, session);
1293  LEAVE (" ");
1294 }
1295 
1296 /* Note: this dialog will only be used when dbi is not enabled
1297  * paths used in it always refer to files and are
1298  * never db uris. See gnc_file_do_save_as for that.
1299  */
1300 void
1301 gnc_file_save_as (void)
1302 {
1303  const gchar *filename;
1304  gchar *default_dir = NULL; /* Default to last open */
1305  gchar *last;
1306 
1307  ENTER(" ");
1308 
1309  last = gnc_history_get_last();
1310  if ( last && gnc_uri_is_file_uri ( last ) )
1311  {
1312  gchar *filepath = gnc_uri_get_path ( last );
1313  default_dir = g_path_get_dirname( filepath );
1314  g_free ( filepath );
1315  }
1316  else
1317  default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_OPEN_SAVE);
1318 
1319  filename = gnc_file_dialog (_("Save"), NULL, default_dir,
1320  GNC_FILE_DIALOG_SAVE);
1321  g_free ( last );
1322  g_free ( default_dir );
1323  if (!filename) return;
1324 
1325  gnc_file_do_save_as( filename );
1326 
1327  LEAVE (" ");
1328 }
1329 
1330 void
1331 gnc_file_do_save_as (const char* filename)
1332 {
1333  QofSession *new_session;
1334  QofSession *session;
1335  gchar *norm_file;
1336  gchar *newfile;
1337  const gchar *oldfile;
1338 
1339  gchar *protocol = NULL;
1340  gchar *hostname = NULL;
1341  gchar *username = NULL;
1342  gchar *password = NULL;
1343  gchar *path = NULL;
1344  gint32 port = 0;
1345 
1346 
1347  QofBackendError io_err = ERR_BACKEND_NO_ERR;
1348 
1349  ENTER(" ");
1350 
1351  /* Convert user input into a normalized uri
1352  * Note that the normalized uri for internal use can have a password */
1353  norm_file = gnc_uri_normalize_uri ( filename, TRUE );
1354  if (!norm_file)
1355  {
1356  show_session_error (ERR_FILEIO_FILE_NOT_FOUND, filename,
1357  GNC_FILE_DIALOG_SAVE);
1358  return;
1359  }
1360 
1361  newfile = gnc_uri_add_extension (norm_file, GNC_DATAFILE_EXT);
1362  g_free (norm_file);
1363  gnc_uri_get_components (newfile, &protocol, &hostname,
1364  &port, &username, &password, &path);
1365 
1366  /* Save As can't use the generic 'file' protocol. If the user didn't set
1367  * a specific protocol, assume the default 'xml'.
1368  */
1369  if (g_strcmp0 (protocol, "file") == 0)
1370  {
1371  g_free (protocol);
1372  protocol = g_strdup ("xml");
1373  norm_file = gnc_uri_create_uri (protocol, hostname, port,
1374  username, password, path);
1375  g_free (newfile);
1376  newfile = norm_file;
1377  }
1378 
1379  /* Some extra steps for file based uri's only */
1380  if (gnc_uri_is_file_protocol(protocol))
1381  {
1382  /* Remember the directory as the default. */
1383  gchar *default_dir = g_path_get_dirname(path);
1384  gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE, default_dir);
1385  g_free(default_dir);
1386 
1387  /* Prevent user to store file in GnuCash' private configuration
1388  * directory (~/.gnucash by default in linux, but can be overridden)
1389  */
1390  DEBUG("User path: %s, dotgnucash_dir: %s", path, gnc_dotgnucash_dir());
1391  if (g_str_has_prefix(path, gnc_dotgnucash_dir()))
1392  {
1393  show_session_error (ERR_FILEIO_RESERVED_WRITE, newfile, GNC_FILE_DIALOG_SAVE);
1394  return;
1395  }
1396  }
1397 
1398  /* Check to see if the user specified the same file as the current
1399  * file. If so, then just do a simple save, instead of a full save as */
1400  session = gnc_get_current_session ();
1401  oldfile = qof_session_get_url(session);
1402  if (oldfile && (strcmp(oldfile, newfile) == 0))
1403  {
1404  g_free (newfile);
1405  gnc_file_save ();
1406  return;
1407  }
1408 
1409  /* Make sure all of the data from the old file is loaded */
1411 
1412  /* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
1413 
1414  save_in_progress++;
1415 
1416  new_session = qof_session_new ();
1417  qof_session_begin (new_session, newfile, FALSE, TRUE, FALSE);
1418 
1419  io_err = qof_session_get_error (new_session);
1420 
1421  /* If the file exists and would be clobbered, ask the user */
1422  if (ERR_BACKEND_STORE_EXISTS == io_err)
1423  {
1424  const char *format = _("The file %s already exists. "
1425  "Are you sure you want to overwrite it?");
1426 
1427  const char *name;
1428  if ( gnc_uri_is_file_uri ( newfile ) )
1429  name = gnc_uri_get_path ( newfile );
1430  else
1431  name = gnc_uri_normalize_uri ( newfile, FALSE );
1432 
1433  /* if user says cancel, we should break out */
1434  if (!gnc_verify_dialog (NULL, FALSE, format, name ))
1435  {
1436  xaccLogDisable();
1437  qof_session_destroy (new_session);
1438  xaccLogEnable();
1439  g_free (newfile);
1440  save_in_progress--;
1441  return;
1442  }
1443  qof_session_begin (new_session, newfile, FALSE, TRUE, TRUE);
1444  }
1445  /* if file appears to be locked, ask the user ... */
1446  else if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
1447  {
1448  if (FALSE == show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE))
1449  {
1450  /* user told us to ignore locks. So ignore them. */
1451  qof_session_begin (new_session, newfile, TRUE, FALSE, FALSE);
1452  }
1453  }
1454 
1455  /* if the database doesn't exist, ask the user ... */
1456  else if ((ERR_FILEIO_FILE_NOT_FOUND == io_err) ||
1457  (ERR_BACKEND_NO_SUCH_DB == io_err) ||
1458  (ERR_SQL_DB_TOO_OLD == io_err))
1459  {
1460  if (FALSE == show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE))
1461  {
1462  /* user told us to create a new database. Do it. */
1463  qof_session_begin (new_session, newfile, FALSE, TRUE, FALSE);
1464  }
1465  }
1466 
1467  /* check again for session errors (since above dialog may have
1468  * cleared a file lock & moved things forward some more)
1469  * This time, errors will be fatal.
1470  */
1471  io_err = qof_session_get_error (new_session);
1472  if (ERR_BACKEND_NO_ERR != io_err)
1473  {
1474  show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE);
1475  xaccLogDisable();
1476  qof_session_destroy (new_session);
1477  xaccLogEnable();
1478  g_free (newfile);
1479  save_in_progress--;
1480  return;
1481  }
1482 
1483  /* If the new "file" is a database, attempt to store the password
1484  * in a keyring. GnuCash itself will not save it.
1485  */
1486  if ( !gnc_uri_is_file_protocol (protocol))
1487  gnc_keyring_set_password ( protocol, hostname, port,
1488  path, username, password );
1489 
1490  /* Prevent race condition between swapping the contents of the two
1491  * sessions, and actually installing the new session as the current
1492  * one. Any event callbacks that occur in this interval will have
1493  * problems if they check for the current book. */
1495 
1496  /* if we got to here, then we've successfully gotten a new session */
1497  /* close up the old file session (if any) */
1498  qof_session_swap_data (session, new_session);
1499 
1500  /* XXX At this point, we should really mark the data in the new session
1501  * as being 'dirty', since we haven't saved it at all under the new
1502  * session. But I'm lazy...
1503  */
1504 
1505  qof_event_resume();
1506 
1507 
1508  gnc_set_busy_cursor (NULL, TRUE);
1509  gnc_window_show_progress(_("Writing file..."), 0.0);
1510  qof_session_save (new_session, gnc_window_show_progress);
1511  gnc_window_show_progress(NULL, -1.0);
1512  gnc_unset_busy_cursor (NULL);
1513 
1514  io_err = qof_session_get_error( new_session );
1515  if ( ERR_BACKEND_NO_ERR != io_err )
1516  {
1517  /* Well, poop. The save failed, so the new session is invalid and we
1518  * need to restore the old one.
1519  */
1520  show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE);
1522  qof_session_swap_data( new_session, session );
1523  qof_session_destroy( new_session );
1524  new_session = NULL;
1525  qof_event_resume();
1526  }
1527  else
1528  {
1529  /* Yay! Save was successful, we can dump the old session */
1531  gnc_clear_current_session();
1532  gnc_set_current_session( new_session );
1533  qof_event_resume();
1534  session = NULL;
1535 
1536  xaccReopenLog();
1537  gnc_add_history (new_session);
1538  gnc_hook_run(HOOK_BOOK_SAVED, new_session);
1539  }
1540  /* --------------- END CORE SESSION CODE -------------- */
1541 
1542  save_in_progress--;
1543 
1544  g_free (newfile);
1545  LEAVE (" ");
1546 }
1547 
1548 void
1549 gnc_file_quit (void)
1550 {
1551  QofSession *session;
1552 
1553  gnc_set_busy_cursor (NULL, TRUE);
1554  session = gnc_get_current_session ();
1555 
1556  /* disable events; otherwise the mass deletion of accounts and
1557  * transactions during shutdown would cause massive redraws */
1558  qof_event_suspend ();
1559 
1561  gnc_hook_run(HOOK_BOOK_CLOSED, session);
1562  gnc_close_gui_component_by_session (session);
1563  gnc_state_save (session);
1564  gnc_clear_current_session();
1565 
1566  qof_event_resume ();
1567  gnc_unset_busy_cursor (NULL);
1568 }
1569 
1570 void
1571 gnc_file_set_shutdown_callback (GNCShutdownCB cb)
1572 {
1573  shutdown_cb = cb;
1574 }
1575 
1576 gboolean
1577 gnc_file_save_in_progress (void)
1578 {
1579  QofSession *session = gnc_get_current_session();
1580  return (qof_session_save_in_progress(session) || save_in_progress > 0);
1581 }
Functions to load, save and get gui state.
void qof_session_save(QofSession *session, QofPercentageFunc percentage_func)
gboolean qof_session_save_in_progress(const QofSession *session)
Functions to save and retrieve passwords.
gboolean gnc_uri_is_file_protocol(const gchar *protocol)
Definition: gnc-uri-utils.c:58
gboolean gnc_uri_is_file_uri(const gchar *uri)
Definition: gnc-uri-utils.c:71
utility functions for the GnuCash UI
void gnc_state_save(const QofSession *session)
Definition: gnc-state.c:226
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition: qofbackend.h:59
time64 qof_book_get_session_dirty_time(const QofBook *book)
void qof_session_safe_save(QofSession *session, QofPercentageFunc percentage_func)
void xaccLogDisable(void)
Definition: TransLog.c:93
#define DEBUG(format, args...)
Definition: qoflog.h:255
Functions that are supported by all types of windows.
gchar * gnc_uri_get_path(const gchar *uri)
GList * gnc_account_list_name_violations(QofBook *book, const gchar *separator)
Definition: Account.c:196
void qof_book_mark_readonly(QofBook *book)
#define PERR(format, args...)
Definition: qoflog.h:237
#define ENTER(format, args...)
Definition: qoflog.h:261
QofBook * qof_session_get_book(const QofSession *session)
void gnc_history_add_file(const char *newfile)
Account handling public routines.
char * gnc_history_get_last(void)
gchar * gnc_uri_normalize_uri(const gchar *uri, gboolean allow_password)
gchar * gnc_account_name_violations_errmsg(const gchar *separator, GList *invalid_account_names)
Definition: Account.c:159
void qof_session_call_close_hooks(QofSession *session)
Functions providing the file history menu.
QofBackendError qof_session_pop_error(QofSession *session)
void gnc_uri_get_components(const gchar *uri, gchar **protocol, gchar **hostname, gint32 *port, gchar **username, gchar **password, gchar **path)
Definition: gnc-uri-utils.c:82
void gnc_keyring_set_password(const gchar *access_method, const gchar *server, guint32 port, const gchar *service, const gchar *user, const gchar *password)
Definition: gnc-keyring.c:67
QofBackendError qof_session_get_error(QofSession *session)
gchar * gnc_features_test_unknown(QofBook *book)
Definition: gnc-features.c:96
void qof_session_swap_data(QofSession *session_1, QofSession *session_2)
gboolean qof_book_session_not_saved(const QofBook *book)
All type declarations for the whole Gnucash engine.
gchar * gnc_uri_add_extension(const gchar *uri, const gchar *extension)
gboolean gnc_keyring_get_password(GtkWidget *parent, const gchar *access_method, const gchar *server, guint32 port, const gchar *service, gchar **user, gchar **password)
Definition: gnc-keyring.c:146
API for the transaction logger.
void qof_session_begin(QofSession *session, const char *book_id, gboolean ignore_lock, gboolean create, gboolean force)
gboolean qof_book_is_readonly(const QofBook *book)
gchar * gnc_uri_create_uri(const gchar *protocol, const gchar *hostname, gint32 port, const gchar *username, const gchar *password, const gchar *path)
void qof_event_suspend(void)
Suspend all engine events.
void qof_event_resume(void)
void qof_session_ensure_all_data_loaded(QofSession *session)
#define LEAVE(format, args...)
Definition: qoflog.h:271
time64 gnc_time(time64 *tbuf)
get the current local time
const gchar * gnc_dotgnucash_dir(void)
Ensure that the user's configuration directory exists and is minimally populated. ...
Utility functions for convert uri in separate components and back.
GtkWidget * gnc_ui_get_toplevel(void)
gint64 time64
Definition: gnc-date.h:83
File path resolution utility functions.
Commodity handling public routines.
void xaccLogEnable(void)
Definition: TransLog.c:97
const gchar * QofLogModule
Definition: qofid.h:89
const gchar * gnc_get_account_separator_string(void)
Definition: Account.c:129
Utility functions for file access.