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