mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
1627 lines
54 KiB
C
1627 lines
54 KiB
C
/********************************************************************\
|
|
* FileDialog.c -- file-handling utility dialogs for gnucash. *
|
|
* *
|
|
* Copyright (C) 1997 Robin D. Clark *
|
|
* Copyright (C) 1998, 1999, 2000 Linas Vepstas *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, write to the Free Software *
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
|
|
\********************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gi18n.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include "dialog-utils.h"
|
|
#include "assistant-xml-encoding.h"
|
|
#include "gnc-commodity.h"
|
|
#include "gnc-component-manager.h"
|
|
#include "gnc-engine.h"
|
|
#include "Account.h"
|
|
#include "gnc-file.h"
|
|
#include "gnc-features.h"
|
|
#include "gnc-filepath-utils.h"
|
|
#include "gnc-gui-query.h"
|
|
#include "gnc-hooks.h"
|
|
#include "gnc-keyring.h"
|
|
#include "gnc-splash.h"
|
|
#include "gnc-ui.h"
|
|
#include "gnc-ui-util.h"
|
|
#include "gnc-uri-utils.h"
|
|
#include "gnc-window.h"
|
|
#include "gnc-plugin-file-history.h"
|
|
#include "qof.h"
|
|
#include "TransLog.h"
|
|
#include "gnc-session.h"
|
|
#include "gnc-state.h"
|
|
#include "gnc-autosave.h"
|
|
|
|
|
|
/** GLOBALS *********************************************************/
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
static QofLogModule log_module = GNC_MOD_GUI;
|
|
|
|
static GNCShutdownCB shutdown_cb = NULL;
|
|
static gint save_in_progress = 0;
|
|
|
|
|
|
/********************************************************************\
|
|
* gnc_file_dialog *
|
|
* Pops up a file selection dialog (either a "Save As" or an *
|
|
* "Open"), and returns the name of the file the user selected. *
|
|
* (This function does not return until the user selects a file *
|
|
* or presses "Cancel" or the window manager destroy button) *
|
|
* *
|
|
* Args: title - the title of the window *
|
|
* filters - list of GtkFileFilters to use, will be *
|
|
freed automatically *
|
|
* default_dir - start the chooser in this directory *
|
|
* type - what type of dialog (open, save, etc.) *
|
|
* Return: containing the name of the file the user selected *
|
|
\********************************************************************/
|
|
|
|
char *
|
|
gnc_file_dialog (const char * title,
|
|
GList * filters,
|
|
const char * starting_dir,
|
|
GNCFileDialogType type
|
|
)
|
|
{
|
|
GtkWidget *file_box;
|
|
const char *internal_name;
|
|
char *file_name = NULL;
|
|
gchar * okbutton = GTK_STOCK_OPEN;
|
|
const gchar *ok_icon = NULL;
|
|
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
|
gint response;
|
|
|
|
ENTER(" ");
|
|
|
|
switch (type)
|
|
{
|
|
case GNC_FILE_DIALOG_OPEN:
|
|
action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
|
okbutton = GTK_STOCK_OPEN;
|
|
if (title == NULL)
|
|
title = _("Open");
|
|
break;
|
|
case GNC_FILE_DIALOG_IMPORT:
|
|
action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
|
okbutton = _("_Import");
|
|
if (title == NULL)
|
|
title = _("Import");
|
|
break;
|
|
case GNC_FILE_DIALOG_SAVE:
|
|
action = GTK_FILE_CHOOSER_ACTION_SAVE;
|
|
okbutton = GTK_STOCK_SAVE;
|
|
if (title == NULL)
|
|
title = _("Save");
|
|
break;
|
|
case GNC_FILE_DIALOG_EXPORT:
|
|
action = GTK_FILE_CHOOSER_ACTION_SAVE;
|
|
okbutton = _("_Export");
|
|
ok_icon = GTK_STOCK_CONVERT;
|
|
if (title == NULL)
|
|
title = _("Export");
|
|
break;
|
|
|
|
}
|
|
|
|
file_box = gtk_file_chooser_dialog_new(
|
|
title,
|
|
NULL,
|
|
action,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
NULL);
|
|
if (ok_icon)
|
|
gnc_gtk_dialog_add_button(file_box, okbutton, ok_icon, GTK_RESPONSE_ACCEPT);
|
|
else
|
|
gtk_dialog_add_button(GTK_DIALOG(file_box),
|
|
okbutton, GTK_RESPONSE_ACCEPT);
|
|
|
|
if (starting_dir)
|
|
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (file_box),
|
|
starting_dir);
|
|
|
|
gtk_window_set_modal(GTK_WINDOW(file_box), TRUE);
|
|
/*
|
|
gtk_window_set_transient_for(GTK_WINDOW(file_box),
|
|
GTK_WINDOW(gnc_ui_get_toplevel()));
|
|
*/
|
|
|
|
if (filters != NULL)
|
|
{
|
|
GList* filter;
|
|
GtkFileFilter* all_filter = gtk_file_filter_new();
|
|
|
|
for (filter = filters; filter; filter = filter->next)
|
|
{
|
|
g_return_val_if_fail(GTK_IS_FILE_FILTER(filter->data), NULL);
|
|
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_box),
|
|
GTK_FILE_FILTER (filter->data));
|
|
}
|
|
|
|
gtk_file_filter_set_name (all_filter, _("All files"));
|
|
gtk_file_filter_add_pattern (all_filter, "*");
|
|
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_box), all_filter);
|
|
|
|
/* Note: You cannot set a file filter and preselect a file name.
|
|
* The latter wins, and the filter ends up disabled. Since we are
|
|
* only setting the starting directory for the chooser dialog,
|
|
* everything works as expected. */
|
|
gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (file_box),
|
|
GTK_FILE_FILTER (filters->data));
|
|
g_list_free (filters);
|
|
}
|
|
|
|
response = gtk_dialog_run(GTK_DIALOG(file_box));
|
|
|
|
if (response == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
/* look for constructs like postgres://foo */
|
|
internal_name = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER (file_box));
|
|
if (strstr (internal_name, "file://") == internal_name)
|
|
{
|
|
/* nope, a local file name */
|
|
internal_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (file_box));
|
|
}
|
|
file_name = g_strdup(internal_name);
|
|
}
|
|
gtk_widget_destroy(GTK_WIDGET(file_box));
|
|
LEAVE("%s", file_name ? file_name : "(null)");
|
|
return file_name;
|
|
}
|
|
|
|
|
|
gboolean
|
|
show_session_error (QofBackendError io_error,
|
|
const char *newfile,
|
|
GNCFileDialogType type)
|
|
{
|
|
GtkWidget *parent = gnc_ui_get_toplevel();
|
|
GtkWidget *dialog;
|
|
gboolean uh_oh = TRUE;
|
|
const char *fmt, *label;
|
|
gchar *displayname;
|
|
gint response;
|
|
|
|
if (NULL == newfile)
|
|
{
|
|
displayname = g_strdup(_("(null)"));
|
|
}
|
|
else if (! gnc_uri_is_file_uri (newfile)) /* Hide the db password in error messages */
|
|
displayname = gnc_uri_normalize_uri ( newfile, FALSE);
|
|
else
|
|
displayname = g_strdup (newfile);
|
|
|
|
switch (io_error)
|
|
{
|
|
case ERR_BACKEND_NO_ERR:
|
|
uh_oh = FALSE;
|
|
break;
|
|
|
|
case ERR_BACKEND_NO_HANDLER:
|
|
fmt = _("No suitable backend was found for %s.");
|
|
gnc_error_dialog(parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_NO_BACKEND:
|
|
fmt = _("The URL %s is not supported by this version of GnuCash.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_BAD_URL:
|
|
fmt = _("Can't parse the URL %s.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_CANT_CONNECT:
|
|
fmt = _("Can't connect to %s. "
|
|
"The host, username or password were incorrect.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_CONN_LOST:
|
|
fmt = _("Can't connect to %s. "
|
|
"Connection was lost, unable to send data.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_TOO_NEW:
|
|
fmt = _("This file/URL appears to be from a newer version "
|
|
"of GnuCash. You must upgrade your version of GnuCash "
|
|
"to work with this data.");
|
|
gnc_error_dialog (parent, "%s", fmt);
|
|
break;
|
|
|
|
case ERR_BACKEND_NO_SUCH_DB:
|
|
fmt = _("The database %s doesn't seem to exist. "
|
|
"Do you want to create it?");
|
|
if (gnc_verify_dialog (parent, TRUE, fmt, displayname))
|
|
{
|
|
uh_oh = FALSE;
|
|
}
|
|
break;
|
|
|
|
case ERR_BACKEND_LOCKED:
|
|
switch (type)
|
|
{
|
|
case GNC_FILE_DIALOG_OPEN:
|
|
default:
|
|
label = GTK_STOCK_OPEN;
|
|
fmt = _("GnuCash could not obtain the lock for %s. "
|
|
"That database may be in use by another user, "
|
|
"in which case you should not open the database. "
|
|
"Do you want to proceed with opening the database?");
|
|
break;
|
|
|
|
case GNC_FILE_DIALOG_IMPORT:
|
|
label = _("Import");
|
|
fmt = _("GnuCash could not obtain the lock for %s. "
|
|
"That database may be in use by another user, "
|
|
"in which case you should not import the database. "
|
|
"Do you want to proceed with importing the database?");
|
|
break;
|
|
|
|
case GNC_FILE_DIALOG_SAVE:
|
|
label = GTK_STOCK_SAVE;
|
|
fmt = _("GnuCash could not obtain the lock for %s. "
|
|
"That database may be in use by another user, "
|
|
"in which case you should not save the database. "
|
|
"Do you want to proceed with saving the database?");
|
|
break;
|
|
|
|
case GNC_FILE_DIALOG_EXPORT:
|
|
label = _("Export");
|
|
fmt = _("GnuCash could not obtain the lock for %s. "
|
|
"That database may be in use by another user, "
|
|
"in which case you should not export the database. "
|
|
"Do you want to proceed with exporting the database?");
|
|
break;
|
|
}
|
|
|
|
dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION,
|
|
GTK_BUTTONS_NONE,
|
|
fmt,
|
|
displayname);
|
|
gtk_dialog_add_buttons(GTK_DIALOG(dialog),
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
label, GTK_RESPONSE_YES,
|
|
NULL);
|
|
if (parent == NULL)
|
|
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
|
|
response = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
uh_oh = (response != GTK_RESPONSE_YES);
|
|
break;
|
|
|
|
case ERR_BACKEND_READONLY:
|
|
fmt = _("GnuCash could not write to %s. "
|
|
"That database may be on a read-only file system, "
|
|
"or you may not have write permission for the directory.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_DATA_CORRUPT:
|
|
fmt = _("The file/URL %s "
|
|
"does not contain GnuCash data or the data is corrupt.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_SERVER_ERR:
|
|
fmt = _("The server at URL %s "
|
|
"experienced an error or encountered bad or corrupt data.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_PERM:
|
|
fmt = _("You do not have permission to access %s.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_BACKEND_MISC:
|
|
fmt = _("An error occurred while processing %s.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_BAD_READ:
|
|
fmt = _("There was an error reading the file. "
|
|
"Do you want to continue?");
|
|
if (gnc_verify_dialog (parent, TRUE, "%s", fmt))
|
|
{
|
|
uh_oh = FALSE;
|
|
}
|
|
break;
|
|
|
|
case ERR_FILEIO_PARSE_ERROR:
|
|
fmt = _("There was an error parsing the file %s.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_EMPTY:
|
|
fmt = _("The file %s is empty.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_NOT_FOUND:
|
|
if (type == GNC_FILE_DIALOG_SAVE)
|
|
{
|
|
uh_oh = FALSE;
|
|
}
|
|
else
|
|
{
|
|
fmt = _("The file %s could not be found.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
}
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_TOO_OLD:
|
|
fmt = _("This file is from an older version of GnuCash. "
|
|
"Do you want to continue?");
|
|
if (gnc_verify_dialog (parent, TRUE, "%s", fmt))
|
|
{
|
|
uh_oh = FALSE;
|
|
}
|
|
break;
|
|
|
|
case ERR_FILEIO_UNKNOWN_FILE_TYPE:
|
|
fmt = _("The file type of file %s is unknown.");
|
|
gnc_error_dialog(parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_FILEIO_BACKUP_ERROR:
|
|
fmt = _("Could not make a backup of the file %s");
|
|
gnc_error_dialog(parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_FILEIO_WRITE_ERROR:
|
|
fmt = _("Could not write to file %s. Check that you have "
|
|
"permission to write to this file and that "
|
|
"there is sufficient space to create it.");
|
|
gnc_error_dialog(parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_EACCES:
|
|
fmt = _("No read permission to read from file %s.");
|
|
gnc_error_dialog (parent, fmt, displayname);
|
|
break;
|
|
|
|
case ERR_FILEIO_RESERVED_WRITE:
|
|
/* Translators: the first %s is a path in the filesystem,
|
|
* the second %s is PACKAGE_NAME, which by default is "GnuCash"
|
|
*/
|
|
fmt = _("You attempted to save in\n%s\nor a subdirectory thereof. "
|
|
"This is not allowed as %s reserves that directory for internal use.\n\n"
|
|
"Please try again in a different directory.");
|
|
gnc_error_dialog (parent, fmt, gnc_dotgnucash_dir(), PACKAGE_NAME);
|
|
break;
|
|
|
|
case ERR_SQL_DB_TOO_OLD:
|
|
fmt = _("This database is from an older version of GnuCash. "
|
|
"Select OK to upgrade it to the current version, Cancel "
|
|
"to mark it read-only.");
|
|
|
|
response = gnc_ok_cancel_dialog(parent, GTK_RESPONSE_CANCEL, "%s", fmt);
|
|
uh_oh = (response == GTK_RESPONSE_CANCEL);
|
|
break;
|
|
|
|
case ERR_SQL_DB_TOO_NEW:
|
|
fmt = _("This database is from a newer version of GnuCash. "
|
|
"This version can read it, but cannot safely save to it. "
|
|
"It will be marked read-only until you do File>Save As, "
|
|
"but data may be lost in writing to the old version.");
|
|
gnc_warning_dialog (parent, "%s", fmt);
|
|
uh_oh = TRUE;
|
|
break;
|
|
|
|
case ERR_SQL_DB_BUSY:
|
|
fmt = _("The SQL database is in use by other users, "
|
|
"and the upgrade cannot be performed until they logoff. "
|
|
"If there are currently no other users, consult the "
|
|
"documentation to learn how to clear out dangling login "
|
|
"sessions.");
|
|
gnc_error_dialog (parent, "%s", fmt);
|
|
break;
|
|
|
|
case ERR_SQL_BAD_DBI:
|
|
|
|
fmt = _("The library \"libdbi\" installed on your system doesn't correctly "
|
|
"store large numbers. This means GnuCash cannot use SQL databases "
|
|
"correctly. Gnucash will not open or save to SQL databases until this is "
|
|
"fixed by installing a different version of \"libdbi\". Please see "
|
|
"https://bugzilla.gnome.org/show_bug.cgi?id=611936 for more "
|
|
"information.");
|
|
|
|
gnc_error_dialog (parent, "%s", fmt);
|
|
break;
|
|
|
|
case ERR_SQL_DBI_UNTESTABLE:
|
|
|
|
fmt = _("GnuCash could not complete a critical test for the presence of "
|
|
"a bug in the \"libdbi\" library. This may be caused by a "
|
|
"permissions misconfiguration of your SQL database. Please see "
|
|
"https://bugzilla.gnome.org/show_bug.cgi?id=645216 for more "
|
|
"information.");
|
|
|
|
gnc_error_dialog (parent, "%s", fmt);
|
|
break;
|
|
|
|
case ERR_FILEIO_FILE_UPGRADE:
|
|
fmt = _("This file is from an older version of GnuCash and will be "
|
|
"upgraded when saved by this version. You will not be able "
|
|
"to read the saved file from the older version of Gnucash "
|
|
"(it will report an \"error parsing the file\"). If you wish "
|
|
"to preserve the old version, exit without saving.");
|
|
gnc_warning_dialog (parent, "%s", fmt);
|
|
uh_oh = FALSE;
|
|
break;
|
|
|
|
default:
|
|
PERR("FIXME: Unhandled error %d", io_error);
|
|
fmt = _("An unknown I/O error (%d) occurred.");
|
|
gnc_error_dialog (parent, fmt, io_error);
|
|
break;
|
|
}
|
|
|
|
g_free (displayname);
|
|
return uh_oh;
|
|
}
|
|
|
|
static void
|
|
gnc_add_history (QofSession * session)
|
|
{
|
|
const gchar *url;
|
|
char *file;
|
|
|
|
if (!session) return;
|
|
|
|
url = qof_session_get_url ( session );
|
|
if ( !strlen (url) )
|
|
return;
|
|
|
|
if ( gnc_uri_is_file_uri ( url ) )
|
|
file = gnc_uri_get_path ( url );
|
|
else
|
|
file = gnc_uri_normalize_uri ( url, FALSE ); /* Note that the password is not saved in history ! */
|
|
|
|
gnc_history_add_file (file);
|
|
}
|
|
|
|
static void
|
|
gnc_book_opened (void)
|
|
{
|
|
gnc_hook_run(HOOK_BOOK_OPENED, gnc_get_current_session());
|
|
}
|
|
|
|
void
|
|
gnc_file_new (void)
|
|
{
|
|
QofSession *session;
|
|
|
|
/* If user attempts to start a new session before saving results of
|
|
* the last one, prompt them to clean up their act. */
|
|
if (!gnc_file_query_save (TRUE))
|
|
return;
|
|
|
|
if (gnc_current_session_exist())
|
|
{
|
|
session = gnc_get_current_session ();
|
|
|
|
/* close any ongoing file sessions, and free the accounts.
|
|
* disable events so we don't get spammed by redraws. */
|
|
qof_event_suspend ();
|
|
|
|
gnc_hook_run(HOOK_BOOK_CLOSED, session);
|
|
|
|
gnc_close_gui_component_by_session (session);
|
|
gnc_state_save (session);
|
|
gnc_clear_current_session();
|
|
qof_event_resume ();
|
|
}
|
|
|
|
/* start a new book */
|
|
gnc_get_current_session ();
|
|
|
|
gnc_hook_run(HOOK_NEW_BOOK, NULL);
|
|
|
|
gnc_gui_refresh_all ();
|
|
|
|
/* Call this after re-enabling events. */
|
|
gnc_book_opened ();
|
|
}
|
|
|
|
gboolean
|
|
gnc_file_query_save (gboolean can_cancel)
|
|
{
|
|
GtkWidget *parent = gnc_ui_get_toplevel();
|
|
QofBook *current_book;
|
|
|
|
if (!gnc_current_session_exist())
|
|
return TRUE;
|
|
|
|
current_book = qof_session_get_book (gnc_get_current_session ());
|
|
/* Remove any pending auto-save timeouts */
|
|
gnc_autosave_remove_timer(current_book);
|
|
|
|
/* If user wants to mess around before finishing business with
|
|
* the old file, give him a chance to figure out what's up.
|
|
* Pose the question as a "while" loop, so that if user screws
|
|
* up the file-selection dialog, we don't blow him out of the water;
|
|
* instead, give them another chance to say "no" to the verify box.
|
|
*/
|
|
while (qof_book_session_not_saved(current_book))
|
|
{
|
|
GtkWidget *dialog;
|
|
gint response;
|
|
const char *title = _("Save changes to the file?");
|
|
/* This should be the same message as in gnc-main-window.c */
|
|
time64 oldest_change;
|
|
gint minutes;
|
|
|
|
dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION,
|
|
GTK_BUTTONS_NONE,
|
|
"%s", title);
|
|
oldest_change = qof_book_get_session_dirty_time(current_book);
|
|
minutes = (gnc_time (NULL) - oldest_change) / 60 + 1;
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
|
|
ngettext("If you don't save, changes from the past %d minute will be discarded.",
|
|
"If you don't save, changes from the past %d minutes will be discarded.",
|
|
minutes), minutes);
|
|
gtk_dialog_add_button(GTK_DIALOG(dialog),
|
|
_("Continue _Without Saving"), GTK_RESPONSE_OK);
|
|
|
|
if (can_cancel)
|
|
gtk_dialog_add_button(GTK_DIALOG(dialog),
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
|
|
gtk_dialog_add_button(GTK_DIALOG(dialog),
|
|
GTK_STOCK_SAVE, GTK_RESPONSE_YES);
|
|
|
|
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES);
|
|
|
|
response = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
|
|
switch (response)
|
|
{
|
|
case GTK_RESPONSE_YES:
|
|
gnc_file_save ();
|
|
/* Go check the loop condition. */
|
|
break;
|
|
|
|
case GTK_RESPONSE_CANCEL:
|
|
default:
|
|
if (can_cancel)
|
|
return FALSE;
|
|
/* No cancel function available. */
|
|
/* Fall through */
|
|
|
|
case GTK_RESPONSE_OK:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* private utilities for file open; done in two stages */
|
|
|
|
#define RESPONSE_NEW 1
|
|
#define RESPONSE_OPEN 2
|
|
#define RESPONSE_QUIT 3
|
|
#define RESPONSE_READONLY 4
|
|
|
|
static gboolean
|
|
gnc_post_file_open (const char * filename, gboolean is_readonly)
|
|
{
|
|
QofSession *current_session, *new_session;
|
|
QofBook *new_book;
|
|
GList *invalid_account_names;
|
|
gboolean uh_oh = FALSE;
|
|
char * newfile;
|
|
QofBackendError io_err = ERR_BACKEND_NO_ERR;
|
|
|
|
gchar *protocol = NULL;
|
|
gchar *hostname = NULL;
|
|
gchar *username = NULL;
|
|
gchar *password = NULL;
|
|
gchar *path = NULL;
|
|
gint32 port = 0;
|
|
|
|
|
|
ENTER("filename %s", filename);
|
|
RESTART:
|
|
if (!filename || (*filename == '\0')) return FALSE;
|
|
|
|
/* Convert user input into a normalized uri
|
|
* Note that the normalized uri for internal use can have a password */
|
|
newfile = gnc_uri_normalize_uri ( filename, TRUE );
|
|
if (!newfile)
|
|
{
|
|
show_session_error (ERR_FILEIO_FILE_NOT_FOUND, filename,
|
|
GNC_FILE_DIALOG_OPEN);
|
|
return FALSE;
|
|
}
|
|
|
|
gnc_uri_get_components (newfile, &protocol, &hostname,
|
|
&port, &username, &password, &path);
|
|
|
|
/* If the file to open is a database, and no password was given,
|
|
* attempt to look it up in a keyring. If that fails the keyring
|
|
* function will ask the user to enter a password. The user can
|
|
* cancel this dialog, in which case the open file action will be
|
|
* abandoned.
|
|
*/
|
|
if ( !gnc_uri_is_file_protocol (protocol) && !password)
|
|
{
|
|
gboolean have_valid_pw = FALSE;
|
|
have_valid_pw = gnc_keyring_get_password ( NULL, protocol, hostname, port,
|
|
path, &username, &password );
|
|
if (!have_valid_pw)
|
|
return FALSE;
|
|
|
|
/* Got password. Recreate the uri to use internally. */
|
|
g_free ( newfile );
|
|
newfile = gnc_uri_create_uri ( protocol, hostname, port,
|
|
username, password, path);
|
|
}
|
|
|
|
/* For file based uri's, remember the directory as the default. */
|
|
if (gnc_uri_is_file_protocol(protocol))
|
|
{
|
|
gchar *default_dir = g_path_get_dirname(path);
|
|
gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE, default_dir);
|
|
g_free(default_dir);
|
|
}
|
|
|
|
/* disable events while moving over to the new set of accounts;
|
|
* the mass deletion of accounts and transactions during
|
|
* switchover would otherwise cause excessive redraws. */
|
|
qof_event_suspend ();
|
|
|
|
/* Change the mouse to a busy cursor */
|
|
gnc_set_busy_cursor (NULL, TRUE);
|
|
|
|
/* -------------- BEGIN CORE SESSION CODE ------------- */
|
|
/* -- this code is almost identical in FileOpen and FileSaveAs -- */
|
|
if (gnc_current_session_exist())
|
|
{
|
|
current_session = gnc_get_current_session();
|
|
gnc_hook_run(HOOK_BOOK_CLOSED, current_session);
|
|
gnc_close_gui_component_by_session (current_session);
|
|
gnc_state_save (current_session);
|
|
gnc_clear_current_session();
|
|
}
|
|
|
|
/* load the accounts from the users datafile */
|
|
/* but first, check to make sure we've got a session going. */
|
|
new_session = qof_session_new ();
|
|
|
|
// Begin the new session. If we are in read-only mode, ignore the locks.
|
|
qof_session_begin (new_session, newfile, is_readonly, FALSE, FALSE);
|
|
io_err = qof_session_get_error (new_session);
|
|
|
|
if (ERR_BACKEND_BAD_URL == io_err)
|
|
{
|
|
gchar *directory;
|
|
show_session_error (io_err, newfile, GNC_FILE_DIALOG_OPEN);
|
|
io_err = ERR_BACKEND_NO_ERR;
|
|
if (g_file_test (filename, G_FILE_TEST_IS_DIR))
|
|
directory = g_strdup (filename);
|
|
else
|
|
directory = gnc_get_default_directory (GNC_PREFS_GROUP_OPEN_SAVE);
|
|
|
|
filename = gnc_file_dialog (NULL, NULL, directory,
|
|
GNC_FILE_DIALOG_OPEN);
|
|
qof_session_destroy (new_session);
|
|
new_session = NULL;
|
|
g_free (directory);
|
|
goto RESTART;
|
|
}
|
|
/* if file appears to be locked, ask the user ... */
|
|
else if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
|
|
{
|
|
GtkWidget *dialog;
|
|
gchar *displayname = NULL;
|
|
|
|
char *fmt1 = _("GnuCash could not obtain the lock for %s.");
|
|
char *fmt2 = ((ERR_BACKEND_LOCKED == io_err) ?
|
|
_("That database may be in use by another user, "
|
|
"in which case you should not open the database. "
|
|
"What would you like to do?") :
|
|
_("That database may be on a read-only file system, "
|
|
"or you may not have write permission for the directory. "
|
|
"If you proceed you may not be able to save any changes. "
|
|
"What would you like to do?")
|
|
);
|
|
int rc;
|
|
|
|
if (! gnc_uri_is_file_uri (newfile)) /* Hide the db password in error messages */
|
|
displayname = gnc_uri_normalize_uri ( newfile, FALSE);
|
|
else
|
|
displayname = g_strdup (newfile);
|
|
|
|
// Bug#467521: on Mac (and maybe Win?), the dialog will appear below the
|
|
// splash, but is modal, so we can't get rid of the splash... So, get
|
|
// rid of it now.
|
|
gnc_destroy_splash_screen();
|
|
|
|
dialog = gtk_message_dialog_new(NULL,
|
|
0,
|
|
GTK_MESSAGE_WARNING,
|
|
GTK_BUTTONS_NONE,
|
|
fmt1, displayname);
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
|
|
"%s", fmt2);
|
|
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
|
|
|
|
gnc_gtk_dialog_add_button(dialog, _("_Open Read-Only"),
|
|
GTK_STOCK_REVERT_TO_SAVED, RESPONSE_READONLY);
|
|
gnc_gtk_dialog_add_button(dialog, _("_Create New File"),
|
|
GTK_STOCK_NEW, RESPONSE_NEW);
|
|
gnc_gtk_dialog_add_button(dialog, _("Open _Anyway"),
|
|
GTK_STOCK_OPEN, RESPONSE_OPEN);
|
|
if (shutdown_cb)
|
|
gtk_dialog_add_button(GTK_DIALOG(dialog),
|
|
GTK_STOCK_QUIT, RESPONSE_QUIT);
|
|
rc = gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
g_free (displayname);
|
|
|
|
if (rc == GTK_RESPONSE_DELETE_EVENT)
|
|
{
|
|
rc = shutdown_cb ? RESPONSE_QUIT : RESPONSE_NEW;
|
|
}
|
|
switch (rc)
|
|
{
|
|
case RESPONSE_QUIT:
|
|
if (shutdown_cb)
|
|
shutdown_cb(0);
|
|
g_assert(1);
|
|
break;
|
|
case RESPONSE_READONLY:
|
|
is_readonly = TRUE;
|
|
// re-enable the splash screen, file loading and display of
|
|
// reports may take some time
|
|
gnc_show_splash_screen();
|
|
/* user told us to open readonly. We do ignore locks (just as before), but now also force the opening. */
|
|
qof_session_begin (new_session, newfile, is_readonly, FALSE, TRUE);
|
|
break;
|
|
case RESPONSE_OPEN:
|
|
// re-enable the splash screen, file loading and display of
|
|
// reports may take some time
|
|
gnc_show_splash_screen();
|
|
/* user told us to ignore locks. So ignore them. */
|
|
qof_session_begin (new_session, newfile, TRUE, FALSE, FALSE);
|
|
break;
|
|
default:
|
|
/* Can't use the given file, so just create a new
|
|
* database so that the user will get a window that
|
|
* they can click "Exit" on.
|
|
*/
|
|
gnc_file_new ();
|
|
break;
|
|
}
|
|
}
|
|
/* if the database doesn't exist, ask the user ... */
|
|
else if ((ERR_BACKEND_NO_SUCH_DB == io_err))
|
|
{
|
|
if (FALSE == show_session_error (io_err, newfile, GNC_FILE_DIALOG_OPEN))
|
|
{
|
|
/* user told us to create a new database. Do it. We
|
|
* shouldn't have to worry about locking or clobbering,
|
|
* it's supposed to be new. */
|
|
qof_session_begin (new_session, newfile, FALSE, TRUE, FALSE);
|
|
}
|
|
}
|
|
|
|
/* Check for errors again, since above may have cleared the lock.
|
|
* If its still locked, still, doesn't exist, still too old, then
|
|
* don't bother with the message, just die. */
|
|
io_err = qof_session_get_error (new_session);
|
|
if ((ERR_BACKEND_LOCKED == io_err) ||
|
|
(ERR_BACKEND_READONLY == io_err) ||
|
|
(ERR_BACKEND_NO_SUCH_DB == io_err))
|
|
{
|
|
uh_oh = TRUE;
|
|
}
|
|
|
|
else
|
|
{
|
|
uh_oh = show_session_error (io_err, newfile, GNC_FILE_DIALOG_OPEN);
|
|
}
|
|
|
|
if (!uh_oh)
|
|
{
|
|
Account *new_root;
|
|
|
|
/* If the new "file" is a database, attempt to store the password
|
|
* in a keyring. GnuCash itself will not save it.
|
|
*/
|
|
if ( !gnc_uri_is_file_protocol (protocol))
|
|
gnc_keyring_set_password ( protocol, hostname, port,
|
|
path, username, password );
|
|
|
|
xaccLogDisable();
|
|
gnc_window_show_progress(_("Loading user data..."), 0.0);
|
|
qof_session_load (new_session, gnc_window_show_progress);
|
|
gnc_window_show_progress(NULL, -1.0);
|
|
xaccLogEnable();
|
|
|
|
if (is_readonly)
|
|
{
|
|
// If the user chose "open read-only" above, make sure to have this
|
|
// read-only here.
|
|
qof_book_mark_readonly(qof_session_get_book(new_session));
|
|
}
|
|
|
|
/* check for i/o error, put up appropriate error dialog */
|
|
io_err = qof_session_pop_error (new_session);
|
|
|
|
if (io_err == ERR_FILEIO_NO_ENCODING)
|
|
{
|
|
if (gnc_xml_convert_single_file (newfile))
|
|
{
|
|
/* try to load once again */
|
|
gnc_window_show_progress(_("Loading user data..."), 0.0);
|
|
qof_session_load (new_session, gnc_window_show_progress);
|
|
gnc_window_show_progress(NULL, -1.0);
|
|
xaccLogEnable();
|
|
io_err = qof_session_get_error (new_session);
|
|
}
|
|
else
|
|
{
|
|
io_err = ERR_FILEIO_PARSE_ERROR;
|
|
}
|
|
}
|
|
|
|
uh_oh = show_session_error (io_err, newfile, GNC_FILE_DIALOG_OPEN);
|
|
/* Attempt to update the database if it's too old */
|
|
if ( !uh_oh && io_err == ERR_SQL_DB_TOO_OLD )
|
|
{
|
|
gnc_window_show_progress(_("Re-saving user data..."), 0.0);
|
|
qof_session_safe_save(new_session, gnc_window_show_progress);
|
|
io_err = qof_session_get_error(new_session);
|
|
uh_oh = show_session_error(io_err, newfile, GNC_FILE_DIALOG_SAVE);
|
|
}
|
|
/* Database is either too old and couldn't (or user didn't
|
|
* want it to) be updated or it's too new. Mark it as
|
|
* read-only
|
|
*/
|
|
if (uh_oh && (io_err == ERR_SQL_DB_TOO_OLD ||
|
|
io_err == ERR_SQL_DB_TOO_NEW))
|
|
{
|
|
qof_book_mark_readonly(qof_session_get_book(new_session));
|
|
uh_oh = FALSE;
|
|
}
|
|
new_root = gnc_book_get_root_account (qof_session_get_book (new_session));
|
|
if (uh_oh) new_root = NULL;
|
|
|
|
/* Umm, came up empty-handed, but no error:
|
|
* The backend forgot to set an error. So make one up. */
|
|
if (!uh_oh && !new_root)
|
|
{
|
|
uh_oh = show_session_error (ERR_BACKEND_MISC, newfile,
|
|
GNC_FILE_DIALOG_OPEN);
|
|
}
|
|
|
|
/* test for unknown features. */
|
|
if (!uh_oh)
|
|
{
|
|
gchar *msg = gnc_features_test_unknown(qof_session_get_book (new_session));
|
|
|
|
if (msg)
|
|
{
|
|
uh_oh = TRUE;
|
|
|
|
// XXX: should pull out the file name here */
|
|
gnc_error_dialog(gnc_ui_get_toplevel(), msg, "");
|
|
g_free (msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_free (protocol);
|
|
g_free (hostname);
|
|
g_free (username);
|
|
g_free (password);
|
|
g_free (path);
|
|
|
|
gnc_unset_busy_cursor (NULL);
|
|
|
|
/* going down -- abandon ship */
|
|
if (uh_oh)
|
|
{
|
|
xaccLogDisable();
|
|
qof_session_destroy (new_session);
|
|
xaccLogEnable();
|
|
|
|
/* well, no matter what, I think it's a good idea to have a root
|
|
* account around. For example, early in the gnucash startup
|
|
* sequence, the user opens a file; if this open fails for any
|
|
* reason, we don't want to leave them high & dry without a root
|
|
* account, because if the user continues, then bad things will
|
|
* happen. */
|
|
gnc_get_current_session ();
|
|
|
|
g_free (newfile);
|
|
|
|
qof_event_resume ();
|
|
gnc_gui_refresh_all ();
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* if we got to here, then we've successfully gotten a new session */
|
|
/* close up the old file session (if any) */
|
|
gnc_set_current_session(new_session);
|
|
|
|
/* --------------- END CORE SESSION CODE -------------- */
|
|
|
|
/* clean up old stuff, and then we're outta here. */
|
|
gnc_add_history (new_session);
|
|
|
|
g_free (newfile);
|
|
|
|
qof_event_resume ();
|
|
gnc_gui_refresh_all ();
|
|
|
|
/* Call this after re-enabling events. */
|
|
gnc_book_opened ();
|
|
|
|
/* Check for account names that may contain the current separator character
|
|
* and inform the user if there are any */
|
|
new_book = gnc_get_current_book();
|
|
invalid_account_names = gnc_account_list_name_violations ( new_book,
|
|
gnc_get_account_separator_string() );
|
|
if ( invalid_account_names )
|
|
{
|
|
gchar *message = gnc_account_name_violations_errmsg ( gnc_get_account_separator_string(),
|
|
invalid_account_names );
|
|
gnc_warning_dialog(NULL, "%s", message);
|
|
g_free ( message );
|
|
}
|
|
|
|
// Convert imap mappings from account full name to guid strings
|
|
qof_event_suspend();
|
|
gnc_account_imap_convert_bayes (gnc_get_current_book());
|
|
qof_event_resume();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Routine that pops up a file chooser dialog
|
|
*
|
|
* Note: this dialog is used when dbi is not enabled
|
|
* so the paths used in here are always file
|
|
* paths, never db uris.
|
|
*/
|
|
gboolean
|
|
gnc_file_open (void)
|
|
{
|
|
const gchar * newfile;
|
|
gchar *last = NULL;
|
|
gchar *default_dir = NULL;
|
|
gboolean result;
|
|
|
|
if (!gnc_file_query_save (TRUE))
|
|
return FALSE;
|
|
|
|
if ( last && gnc_uri_is_file_uri ( last ) )
|
|
{
|
|
gchar *filepath = gnc_uri_get_path ( last );
|
|
default_dir = g_path_get_dirname( filepath );
|
|
g_free ( filepath );
|
|
}
|
|
else
|
|
default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_OPEN_SAVE);
|
|
|
|
newfile = gnc_file_dialog (_("Open"), NULL, default_dir, GNC_FILE_DIALOG_OPEN);
|
|
g_free ( last );
|
|
g_free ( default_dir );
|
|
|
|
result = gnc_post_file_open ( newfile, /*is_readonly*/ FALSE );
|
|
|
|
/* This dialogue can show up early in the startup process. If the
|
|
* user fails to pick a file (by e.g. hitting the cancel button), we
|
|
* might be left with a null topgroup, which leads to nastiness when
|
|
* user goes to create their very first account. So create one. */
|
|
gnc_get_current_session ();
|
|
|
|
return result;
|
|
}
|
|
|
|
gboolean
|
|
gnc_file_open_file (const char * newfile, gboolean open_readonly)
|
|
{
|
|
if (!newfile) return FALSE;
|
|
|
|
if (!gnc_file_query_save (TRUE))
|
|
return FALSE;
|
|
|
|
return gnc_post_file_open (newfile, open_readonly);
|
|
}
|
|
|
|
/* Note: this dialog will only be used when dbi is not enabled
|
|
* paths used in it always refer to files and are
|
|
* never db uris
|
|
*/
|
|
void
|
|
gnc_file_export (void)
|
|
{
|
|
const char *filename;
|
|
char *default_dir = NULL; /* Default to last open */
|
|
char *last;
|
|
|
|
ENTER(" ");
|
|
|
|
last = gnc_history_get_last();
|
|
if ( last && gnc_uri_is_file_uri ( last ) )
|
|
{
|
|
gchar *filepath = gnc_uri_get_path ( last );
|
|
default_dir = g_path_get_dirname( filepath );
|
|
g_free ( filepath );
|
|
}
|
|
else
|
|
default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_EXPORT);
|
|
|
|
filename = gnc_file_dialog (_("Save"), NULL, default_dir,
|
|
GNC_FILE_DIALOG_SAVE);
|
|
g_free ( last );
|
|
g_free ( default_dir );
|
|
if (!filename) return;
|
|
|
|
gnc_file_do_export( filename );
|
|
|
|
LEAVE (" ");
|
|
}
|
|
|
|
/* Prevent the user from storing or exporting data files into the settings
|
|
* directory.
|
|
*/
|
|
static gboolean
|
|
check_file_path (const char *path)
|
|
{
|
|
/* Remember the directory as the default. */
|
|
gchar *dir = g_path_get_dirname(path);
|
|
const gchar *dotgnucash = gnc_dotgnucash_dir();
|
|
char *dirpath = dir;
|
|
|
|
/* Prevent user from storing file in GnuCash' private configuration
|
|
* directory (~/.gnucash by default in linux, but can be overridden)
|
|
*/
|
|
while (strcmp(dir = g_path_get_dirname(dirpath), dirpath) != 0)
|
|
{
|
|
if (strcmp(dirpath, dotgnucash) == 0)
|
|
{
|
|
g_free (dir);
|
|
g_free (dirpath);
|
|
return TRUE;
|
|
}
|
|
g_free (dirpath);
|
|
dirpath = dir;
|
|
}
|
|
g_free (dirpath);
|
|
g_free(dir);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void
|
|
gnc_file_do_export(const char * filename)
|
|
{
|
|
QofSession *current_session, *new_session;
|
|
gboolean ok;
|
|
QofBackendError io_err = ERR_BACKEND_NO_ERR;
|
|
gchar *norm_file;
|
|
gchar *newfile;
|
|
const gchar *oldfile;
|
|
|
|
gchar *protocol = NULL;
|
|
gchar *hostname = NULL;
|
|
gchar *username = NULL;
|
|
gchar *password = NULL;
|
|
gchar *path = NULL;
|
|
gint32 port = 0;
|
|
|
|
ENTER(" ");
|
|
|
|
/* Convert user input into a normalized uri
|
|
* Note that the normalized uri for internal use can have a password */
|
|
norm_file = gnc_uri_normalize_uri ( filename, TRUE );
|
|
if (!norm_file)
|
|
{
|
|
show_session_error (ERR_FILEIO_FILE_NOT_FOUND, filename,
|
|
GNC_FILE_DIALOG_EXPORT);
|
|
return;
|
|
}
|
|
|
|
newfile = gnc_uri_add_extension (norm_file, GNC_DATAFILE_EXT);
|
|
g_free (norm_file);
|
|
gnc_uri_get_components (newfile, &protocol, &hostname,
|
|
&port, &username, &password, &path);
|
|
|
|
/* Save As can't use the generic 'file' protocol. If the user didn't set
|
|
* a specific protocol, assume the default 'xml'.
|
|
*/
|
|
if (g_strcmp0 (protocol, "file") == 0)
|
|
{
|
|
g_free (protocol);
|
|
protocol = g_strdup ("xml");
|
|
norm_file = gnc_uri_create_uri (protocol, hostname, port,
|
|
username, password, path);
|
|
g_free (newfile);
|
|
newfile = norm_file;
|
|
}
|
|
|
|
/* Some extra steps for file based uri's only */
|
|
if (gnc_uri_is_file_protocol(protocol))
|
|
{
|
|
if (check_file_path (path))
|
|
{
|
|
show_session_error (ERR_FILEIO_RESERVED_WRITE, newfile,
|
|
GNC_FILE_DIALOG_SAVE);
|
|
return;
|
|
}
|
|
gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE,
|
|
g_path_get_dirname(path));
|
|
}
|
|
/* Check to see if the user specified the same file as the current
|
|
* file. If so, prevent the export from happening to avoid killing this file */
|
|
current_session = gnc_get_current_session ();
|
|
oldfile = qof_session_get_url(current_session);
|
|
if (strlen (oldfile) && (strcmp(oldfile, newfile) == 0))
|
|
{
|
|
g_free (newfile);
|
|
show_session_error (ERR_FILEIO_WRITE_ERROR, filename,
|
|
GNC_FILE_DIALOG_EXPORT);
|
|
return;
|
|
}
|
|
|
|
qof_event_suspend();
|
|
|
|
/* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
|
|
|
|
new_session = qof_session_new ();
|
|
qof_session_begin (new_session, newfile, FALSE, TRUE, FALSE);
|
|
|
|
io_err = qof_session_get_error (new_session);
|
|
/* If the file exists and would be clobbered, ask the user */
|
|
if (ERR_BACKEND_STORE_EXISTS == io_err)
|
|
{
|
|
const char *format = _("The file %s already exists. "
|
|
"Are you sure you want to overwrite it?");
|
|
|
|
const char *name;
|
|
if ( gnc_uri_is_file_uri ( newfile ) )
|
|
name = gnc_uri_get_path ( newfile );
|
|
else
|
|
name = gnc_uri_normalize_uri ( newfile, FALSE );
|
|
/* if user says cancel, we should break out */
|
|
if (!gnc_verify_dialog (NULL, FALSE, format, name))
|
|
{
|
|
return;
|
|
}
|
|
qof_session_begin (new_session, newfile, FALSE, TRUE, TRUE);
|
|
}
|
|
/* if file appears to be locked, ask the user ... */
|
|
if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
|
|
{
|
|
if (FALSE == show_session_error (io_err, newfile, GNC_FILE_DIALOG_EXPORT))
|
|
{
|
|
/* user told us to ignore locks. So ignore them. */
|
|
qof_session_begin (new_session, newfile, TRUE, FALSE, FALSE);
|
|
}
|
|
}
|
|
|
|
/* --------------- END CORE SESSION CODE -------------- */
|
|
|
|
/* use the current session to save to file */
|
|
gnc_set_busy_cursor (NULL, TRUE);
|
|
gnc_window_show_progress(_("Exporting file..."), 0.0);
|
|
ok = qof_session_export (new_session, current_session,
|
|
gnc_window_show_progress);
|
|
gnc_window_show_progress(NULL, -1.0);
|
|
gnc_unset_busy_cursor (NULL);
|
|
xaccLogDisable();
|
|
qof_session_destroy (new_session);
|
|
xaccLogEnable();
|
|
qof_event_resume();
|
|
|
|
if (!ok)
|
|
{
|
|
/* %s is the strerror(3) error string of the error that occurred. */
|
|
const char *format = _("There was an error saving the file.\n\n%s");
|
|
|
|
gnc_error_dialog (NULL, format, strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static gboolean been_here_before = FALSE;
|
|
|
|
void
|
|
gnc_file_save (void)
|
|
{
|
|
QofBackendError io_err;
|
|
const char * newfile;
|
|
QofSession *session;
|
|
ENTER (" ");
|
|
|
|
/* hack alert -- Somehow make sure all in-progress edits get committed! */
|
|
|
|
/* If we don't have a filename/path to save to get one. */
|
|
session = gnc_get_current_session ();
|
|
|
|
if (!strlen (qof_session_get_url (session)))
|
|
{
|
|
gnc_file_save_as ();
|
|
return;
|
|
}
|
|
|
|
if (qof_book_is_readonly(qof_session_get_book(session)))
|
|
{
|
|
gint response = gnc_ok_cancel_dialog(gnc_ui_get_toplevel(),
|
|
GTK_RESPONSE_CANCEL,
|
|
_("The database was opened read-only. "
|
|
"Do you want to save it to a different place?"));
|
|
if (response == GTK_RESPONSE_OK)
|
|
{
|
|
gnc_file_save_as ();
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* use the current session to save to file */
|
|
save_in_progress++;
|
|
gnc_set_busy_cursor (NULL, TRUE);
|
|
gnc_window_show_progress(_("Writing file..."), 0.0);
|
|
qof_session_save (session, gnc_window_show_progress);
|
|
gnc_window_show_progress(NULL, -1.0);
|
|
gnc_unset_busy_cursor (NULL);
|
|
save_in_progress--;
|
|
|
|
/* Make sure everything's OK - disk could be full, file could have
|
|
become read-only etc. */
|
|
io_err = qof_session_get_error (session);
|
|
if (ERR_BACKEND_NO_ERR != io_err)
|
|
{
|
|
newfile = qof_session_get_url(session);
|
|
show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE);
|
|
|
|
if (been_here_before) return;
|
|
been_here_before = TRUE;
|
|
gnc_file_save_as (); /* been_here prevents infinite recursion */
|
|
been_here_before = FALSE;
|
|
return;
|
|
}
|
|
|
|
xaccReopenLog();
|
|
gnc_add_history (session);
|
|
gnc_hook_run(HOOK_BOOK_SAVED, session);
|
|
LEAVE (" ");
|
|
}
|
|
|
|
/* Note: this dialog will only be used when dbi is not enabled
|
|
* paths used in it always refer to files and are
|
|
* never db uris. See gnc_file_do_save_as for that.
|
|
*/
|
|
void
|
|
gnc_file_save_as (void)
|
|
{
|
|
const gchar *filename;
|
|
gchar *default_dir = NULL; /* Default to last open */
|
|
gchar *last;
|
|
|
|
ENTER(" ");
|
|
|
|
last = gnc_history_get_last();
|
|
if ( last && gnc_uri_is_file_uri ( last ) )
|
|
{
|
|
gchar *filepath = gnc_uri_get_path ( last );
|
|
default_dir = g_path_get_dirname( filepath );
|
|
g_free ( filepath );
|
|
}
|
|
else
|
|
default_dir = gnc_get_default_directory(GNC_PREFS_GROUP_OPEN_SAVE);
|
|
|
|
filename = gnc_file_dialog (_("Save"), NULL, default_dir,
|
|
GNC_FILE_DIALOG_SAVE);
|
|
g_free ( last );
|
|
g_free ( default_dir );
|
|
if (!filename) return;
|
|
|
|
gnc_file_do_save_as( filename );
|
|
|
|
LEAVE (" ");
|
|
}
|
|
|
|
void
|
|
gnc_file_do_save_as (const char* filename)
|
|
{
|
|
QofSession *new_session;
|
|
QofSession *session;
|
|
gchar *norm_file;
|
|
gchar *newfile;
|
|
const gchar *oldfile;
|
|
|
|
gchar *protocol = NULL;
|
|
gchar *hostname = NULL;
|
|
gchar *username = NULL;
|
|
gchar *password = NULL;
|
|
gchar *path = NULL;
|
|
gint32 port = 0;
|
|
|
|
|
|
QofBackendError io_err = ERR_BACKEND_NO_ERR;
|
|
|
|
ENTER(" ");
|
|
|
|
/* Convert user input into a normalized uri
|
|
* Note that the normalized uri for internal use can have a password */
|
|
norm_file = gnc_uri_normalize_uri ( filename, TRUE );
|
|
if (!norm_file)
|
|
{
|
|
show_session_error (ERR_FILEIO_FILE_NOT_FOUND, filename,
|
|
GNC_FILE_DIALOG_SAVE);
|
|
return;
|
|
}
|
|
|
|
newfile = gnc_uri_add_extension (norm_file, GNC_DATAFILE_EXT);
|
|
g_free (norm_file);
|
|
gnc_uri_get_components (newfile, &protocol, &hostname,
|
|
&port, &username, &password, &path);
|
|
|
|
/* Save As can't use the generic 'file' protocol. If the user didn't set
|
|
* a specific protocol, assume the default 'xml'.
|
|
*/
|
|
if (g_strcmp0 (protocol, "file") == 0)
|
|
{
|
|
g_free (protocol);
|
|
protocol = g_strdup ("xml");
|
|
norm_file = gnc_uri_create_uri (protocol, hostname, port,
|
|
username, password, path);
|
|
g_free (newfile);
|
|
newfile = norm_file;
|
|
}
|
|
|
|
/* Some extra steps for file based uri's only */
|
|
if (gnc_uri_is_file_protocol(protocol))
|
|
{
|
|
if (check_file_path (path))
|
|
{
|
|
show_session_error (ERR_FILEIO_RESERVED_WRITE, newfile,
|
|
GNC_FILE_DIALOG_SAVE);
|
|
return;
|
|
}
|
|
gnc_set_default_directory (GNC_PREFS_GROUP_OPEN_SAVE,
|
|
g_path_get_dirname (path));
|
|
}
|
|
|
|
/* Check to see if the user specified the same file as the current
|
|
* file. If so, then just do a simple save, instead of a full save as */
|
|
session = gnc_get_current_session ();
|
|
oldfile = qof_session_get_url(session);
|
|
if (strlen (oldfile) && (strcmp(oldfile, newfile) == 0))
|
|
{
|
|
g_free (newfile);
|
|
gnc_file_save ();
|
|
return;
|
|
}
|
|
|
|
/* Make sure all of the data from the old file is loaded */
|
|
qof_session_ensure_all_data_loaded(session);
|
|
|
|
/* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
|
|
|
|
save_in_progress++;
|
|
|
|
new_session = qof_session_new ();
|
|
qof_session_begin (new_session, newfile, FALSE, TRUE, FALSE);
|
|
|
|
io_err = qof_session_get_error (new_session);
|
|
|
|
/* If the file exists and would be clobbered, ask the user */
|
|
if (ERR_BACKEND_STORE_EXISTS == io_err)
|
|
{
|
|
const char *format = _("The file %s already exists. "
|
|
"Are you sure you want to overwrite it?");
|
|
|
|
const char *name;
|
|
if ( gnc_uri_is_file_uri ( newfile ) )
|
|
name = gnc_uri_get_path ( newfile );
|
|
else
|
|
name = gnc_uri_normalize_uri ( newfile, FALSE );
|
|
|
|
/* if user says cancel, we should break out */
|
|
if (!gnc_verify_dialog (NULL, FALSE, format, name ))
|
|
{
|
|
xaccLogDisable();
|
|
qof_session_destroy (new_session);
|
|
xaccLogEnable();
|
|
g_free (newfile);
|
|
save_in_progress--;
|
|
return;
|
|
}
|
|
qof_session_begin (new_session, newfile, FALSE, TRUE, TRUE);
|
|
}
|
|
/* if file appears to be locked, ask the user ... */
|
|
else if (ERR_BACKEND_LOCKED == io_err || ERR_BACKEND_READONLY == io_err)
|
|
{
|
|
if (FALSE == show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE))
|
|
{
|
|
/* user told us to ignore locks. So ignore them. */
|
|
qof_session_begin (new_session, newfile, TRUE, FALSE, FALSE);
|
|
}
|
|
}
|
|
|
|
/* if the database doesn't exist, ask the user ... */
|
|
else if ((ERR_FILEIO_FILE_NOT_FOUND == io_err) ||
|
|
(ERR_BACKEND_NO_SUCH_DB == io_err) ||
|
|
(ERR_SQL_DB_TOO_OLD == io_err))
|
|
{
|
|
if (FALSE == show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE))
|
|
{
|
|
/* user told us to create a new database. Do it. */
|
|
qof_session_begin (new_session, newfile, FALSE, TRUE, FALSE);
|
|
}
|
|
}
|
|
|
|
/* check again for session errors (since above dialog may have
|
|
* cleared a file lock & moved things forward some more)
|
|
* This time, errors will be fatal.
|
|
*/
|
|
io_err = qof_session_get_error (new_session);
|
|
if (ERR_BACKEND_NO_ERR != io_err)
|
|
{
|
|
show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE);
|
|
xaccLogDisable();
|
|
qof_session_destroy (new_session);
|
|
xaccLogEnable();
|
|
g_free (newfile);
|
|
save_in_progress--;
|
|
return;
|
|
}
|
|
|
|
/* If the new "file" is a database, attempt to store the password
|
|
* in a keyring. GnuCash itself will not save it.
|
|
*/
|
|
if ( !gnc_uri_is_file_protocol (protocol))
|
|
gnc_keyring_set_password ( protocol, hostname, port,
|
|
path, username, password );
|
|
|
|
/* Prevent race condition between swapping the contents of the two
|
|
* sessions, and actually installing the new session as the current
|
|
* one. Any event callbacks that occur in this interval will have
|
|
* problems if they check for the current book. */
|
|
qof_event_suspend();
|
|
|
|
/* if we got to here, then we've successfully gotten a new session */
|
|
/* close up the old file session (if any) */
|
|
qof_session_swap_data (session, new_session);
|
|
|
|
/* XXX At this point, we should really mark the data in the new session
|
|
* as being 'dirty', since we haven't saved it at all under the new
|
|
* session. But I'm lazy...
|
|
*/
|
|
|
|
qof_event_resume();
|
|
|
|
|
|
gnc_set_busy_cursor (NULL, TRUE);
|
|
gnc_window_show_progress(_("Writing file..."), 0.0);
|
|
qof_session_save (new_session, gnc_window_show_progress);
|
|
gnc_window_show_progress(NULL, -1.0);
|
|
gnc_unset_busy_cursor (NULL);
|
|
|
|
io_err = qof_session_get_error( new_session );
|
|
if ( ERR_BACKEND_NO_ERR != io_err )
|
|
{
|
|
/* Well, poop. The save failed, so the new session is invalid and we
|
|
* need to restore the old one.
|
|
*/
|
|
show_session_error (io_err, newfile, GNC_FILE_DIALOG_SAVE);
|
|
qof_event_suspend();
|
|
qof_session_swap_data( new_session, session );
|
|
qof_session_destroy( new_session );
|
|
new_session = NULL;
|
|
qof_event_resume();
|
|
}
|
|
else
|
|
{
|
|
/* Yay! Save was successful, we can dump the old session */
|
|
qof_event_suspend();
|
|
gnc_clear_current_session();
|
|
gnc_set_current_session( new_session );
|
|
qof_event_resume();
|
|
session = NULL;
|
|
|
|
xaccReopenLog();
|
|
gnc_add_history (new_session);
|
|
gnc_hook_run(HOOK_BOOK_SAVED, new_session);
|
|
}
|
|
/* --------------- END CORE SESSION CODE -------------- */
|
|
|
|
save_in_progress--;
|
|
|
|
g_free (newfile);
|
|
LEAVE (" ");
|
|
}
|
|
|
|
void
|
|
gnc_file_revert (void)
|
|
{
|
|
QofSession *session;
|
|
const gchar *fileurl, *filename, *tmp;
|
|
const gchar *title = _("Reverting will discard all unsaved changes to %s. Are you sure you want to proceed ?");
|
|
|
|
if (!gnc_main_window_all_finish_pending())
|
|
return;
|
|
|
|
session = gnc_get_current_session();
|
|
fileurl = qof_session_get_url(session);
|
|
if (!strlen (fileurl))
|
|
fileurl = _("<unknown>");
|
|
if ((tmp = strrchr(fileurl, '/')) != NULL)
|
|
filename = tmp + 1;
|
|
else
|
|
filename = fileurl;
|
|
|
|
if (!gnc_verify_dialog (NULL, FALSE, title, filename))
|
|
return;
|
|
|
|
qof_book_mark_session_saved (qof_session_get_book (session));
|
|
gnc_file_open_file (fileurl, qof_book_is_readonly(gnc_get_current_book()));}
|
|
|
|
void
|
|
gnc_file_quit (void)
|
|
{
|
|
QofSession *session;
|
|
|
|
gnc_set_busy_cursor (NULL, TRUE);
|
|
session = gnc_get_current_session ();
|
|
|
|
/* disable events; otherwise the mass deletion of accounts and
|
|
* transactions during shutdown would cause massive redraws */
|
|
qof_event_suspend ();
|
|
|
|
gnc_hook_run(HOOK_BOOK_CLOSED, session);
|
|
gnc_close_gui_component_by_session (session);
|
|
gnc_state_save (session);
|
|
gnc_clear_current_session();
|
|
|
|
qof_event_resume ();
|
|
gnc_unset_busy_cursor (NULL);
|
|
}
|
|
|
|
void
|
|
gnc_file_set_shutdown_callback (GNCShutdownCB cb)
|
|
{
|
|
shutdown_cb = cb;
|
|
}
|
|
|
|
gboolean
|
|
gnc_file_save_in_progress (void)
|
|
{
|
|
QofSession *session = gnc_get_current_session();
|
|
return (qof_session_save_in_progress(session) || save_in_progress > 0);
|
|
}
|