GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-autosave.c
1/*
2 * gnc-autosave.c -- Functions related to the auto-save feature.
3 *
4 * Copyright (C) 2007 Christian Stimming <stimming@tuhh.de>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, contact:
18 *
19 * Free Software Foundation Voice: +1-617-542-5942
20 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
21 * Boston, MA 02110-1301, USA gnu@gnu.org
22 */
23
24#include <config.h>
25
26#include "gnc-autosave.h"
27
28#include <glib/gi18n.h>
29#include "gnc-engine.h"
30#include "gnc-session.h"
31#include "gnc-ui.h"
32#include "gnc-file.h"
33#include "gnc-window.h"
34#include "gnc-prefs.h"
35#include "gnc-main-window.h"
36#include "gnc-gui-query.h"
37#include "dialog-utils.h"
38#include <qoflog.h>
39
40#define GNC_PREF_AUTOSAVE_SHOW_EXPLANATION "autosave-show-explanation"
41#define GNC_PREF_AUTOSAVE_INTERVAL "autosave-interval-minutes"
42#define AUTOSAVE_SOURCE_ID "autosave_source_id"
43
44#ifdef G_LOG_DOMAIN
45# undef G_LOG_DOMAIN
46#endif
47#define G_LOG_DOMAIN "gnc.gui.autosave"
48static const QofLogModule log_module = G_LOG_DOMAIN;
49
50static void
51autosave_remove_timer_cb(QofBook *book, gpointer key, gpointer user_data);
52
53/* Here's how autosave works:
54 *
55 * Initially, the book is in state "undirty". Once the book changes
56 * state to "dirty", hence calling
57 * gnc_main_window_autosave_dirty(true), the auto-save timer is added
58 * and started. Now one out of two state changes can occur (well,
59 * three actually), depending on which event occurs first:
60 *
61 * - Either the book changes state to "undirty", hence calling
62 * gnc_main_window_autosave_dirty(false). In this case the auto-save
63 * timer is removed and all returns to the initial state with the book
64 * "undirty".
65 *
66 * - Or the auto-save timer hits its timeout, hence calling
67 * autosave_timeout_cb(). In this case gnc_file_save() is invoked, the
68 * auto-save timer is removed, and all returns to the initial state
69 * with the book "undirty". (As an exceptional addition to this, on
70 * the very first call to autosave_timeout_cb, if the key
71 * autosave_show_explanation is true, an explanation dialog of this
72 * feature is shown to the user, and the key autosave_show_explanation
73 * is set to false to not show this dialog again.)
74 *
75 * - As a third possibility, the book can also change state to
76 * "closing", in which case the autosave_remove_timer_cb is called
77 * that removes the auto-save timer and all returns to the initial
78 * state with the book "undirty".
79 */
80
81static gboolean autosave_confirm(GtkWidget *toplevel)
82{
83 GtkWidget *dialog;
84 guint interval_mins =
85 gnc_prefs_get_float(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTOSAVE_INTERVAL);
86 gboolean switch_off_autosave, show_expl_again, save_now;
87 gint response;
88
89#define YES_THIS_TIME 1
90#define YES_ALWAYS 2
91#define NO_NEVER 3
92#define NO_NOT_THIS_TIME 4
93 /* The autosave timeout has occurred, and we should show the
94 explanation dialog. */
95 dialog =
96 gtk_message_dialog_new(GTK_WINDOW(toplevel),
97 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
98 GTK_MESSAGE_QUESTION,
99 GTK_BUTTONS_NONE,
100 "%s",
101 _("Save file automatically?"));
102
103 // Set the name for this dialog so it can be easily manipulated with css
104 gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-auto-save");
105
106 gtk_message_dialog_format_secondary_text
107 (GTK_MESSAGE_DIALOG(dialog),
108 ngettext("Your data file needs to be saved to your hard disk to save your changes. "
109 "GnuCash has a feature to save the file automatically every %d minute, "
110 "just as if you had pressed the \"Save\" button each time.\n\n"
111 "You can change the time interval or turn off this feature under "
112 "Edit->Preferences->General->Auto-save time interval.\n\n"
113 "Should your file be saved automatically?",
114 "Your data file needs to be saved to your hard disk to save your changes. "
115 "GnuCash has a feature to save the file automatically every %d minutes, "
116 "just as if you had pressed the \"Save\" button each time.\n\n"
117 "You can change the time interval or turn off this feature under "
118 "Edit->Preferences->General->Auto-save time interval.\n\n"
119 "Should your file be saved automatically?",
120 interval_mins),
121 interval_mins);
122 gtk_dialog_add_buttons(GTK_DIALOG(dialog),
123 _("_Yes, this time"), YES_THIS_TIME,
124 _("Yes, _always"), YES_ALWAYS,
125 _("No, n_ever"), NO_NEVER,
126 _("_No, not this time"), NO_NOT_THIS_TIME,
127 NULL);
128 gtk_dialog_set_default_response( GTK_DIALOG(dialog), NO_NOT_THIS_TIME);
129
130 /* Run the modal dialog */
131 response = gtk_dialog_run( GTK_DIALOG( dialog ) );
132 gtk_widget_destroy( dialog );
133
134 /* Evaluate the response */
135 switch (response)
136 {
137 case YES_THIS_TIME:
138 switch_off_autosave = FALSE;
139 show_expl_again = TRUE;
140 save_now = TRUE;
141 break;
142 case YES_ALWAYS:
143 switch_off_autosave = FALSE;
144 show_expl_again = FALSE;
145 save_now = TRUE;
146 break;
147 case NO_NEVER:
148 switch_off_autosave = TRUE;
149 show_expl_again = FALSE;
150 save_now = FALSE;
151 break;
152 default:
153 case NO_NOT_THIS_TIME:
154 switch_off_autosave = FALSE;
155 show_expl_again = TRUE;
156 save_now = FALSE;
157 };
158
159 /* Should we show this explanation again? */
160 gnc_prefs_set_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTOSAVE_SHOW_EXPLANATION, show_expl_again);
161 DEBUG("autosave_timeout_cb: Show explanation again=%s\n",
162 (show_expl_again ? "TRUE" : "FALSE"));
163
164 /* Should we switch off autosave? */
165 if (switch_off_autosave)
166 {
167 gnc_prefs_set_float(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTOSAVE_INTERVAL, 0);
168 DEBUG("autosave_timeout_cb: User chose to disable auto-save.\n");
169 }
170
171 return save_now;
172}
173
174
175static gboolean autosave_timeout_cb(gpointer user_data)
176{
177 QofBook *book = user_data;
178 gboolean show_explanation;
179 gboolean save_now = TRUE;
180 GtkWidget *toplevel;
181
182 DEBUG("autosave_timeout_cb called\n");
183
184 /* Is there already a save in progress? If yes, return FALSE so that
185 the timeout is automatically destroyed and the function will not
186 be called again. */
187 if (gnc_file_save_in_progress() || !gnc_current_session_exist()
188 || qof_book_is_readonly(book))
189 return FALSE;
190
191 /* Store the current toplevel window for later use. */
192 toplevel = GTK_WIDGET (gnc_ui_get_main_window (NULL));
193
194 /* Lookup preference to show an explanatory dialog, if wanted. */
195 show_explanation =
196 gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTOSAVE_SHOW_EXPLANATION);
197 if (show_explanation)
198 {
199 save_now = autosave_confirm(toplevel);
200 }
201
202 if (save_now)
203 {
204 DEBUG("autosave_timeout_cb: Really trigger auto-save now.\n");
205
206 /* Timeout has passed - save the file. */
207 if (GNC_IS_MAIN_WINDOW(toplevel))
208 gnc_main_window_set_progressbar_window( GNC_MAIN_WINDOW( toplevel ) );
209 else
210 DEBUG("autosave_timeout_cb: toplevel is not a GNC_MAIN_WINDOW\n");
211 if (GNC_IS_WINDOW(toplevel))
212 gnc_window_set_progressbar_window( GNC_WINDOW( toplevel ) );
213 else
214 DEBUG("autosave_timeout_cb: toplevel is not a GNC_WINDOW\n");
215
216 gnc_file_save (GTK_WINDOW (toplevel));
217
219
220 /* Return FALSE so that the timeout is automatically destroyed and
221 the function will not be called again. However, at least in my
222 glib-2.12.4 the timer event source still exists after returning
223 FALSE?! */
224 return FALSE;
225 }
226 else
227 {
228 DEBUG("autosave_timeout_cb: No auto-save this time, let the timeout run again.\n");
229 /* Return TRUE so that the timeout is not removed but will be
230 triggered again after the next time interval. */
231 return TRUE;
232 }
233}
234
235static void
236autosave_remove_timer_cb(QofBook *book, gpointer key, gpointer user_data)
237{
238 guint autosave_source_id = GPOINTER_TO_UINT(user_data);
239 gboolean res;
240 /* Remove the timer that would have triggered the next autosave */
241 if (autosave_source_id > 0)
242 {
243 res = g_source_remove (autosave_source_id);
244 DEBUG("Removing auto save timer with id %d, result=%s\n",
245 autosave_source_id, (res ? "TRUE" : "FALSE"));
246
247 /* Set the event source id to zero. */
248 qof_book_set_data_fin(book, AUTOSAVE_SOURCE_ID,
249 GUINT_TO_POINTER(0), autosave_remove_timer_cb);
250 }
251}
252
253void gnc_autosave_remove_timer(QofBook *book)
254{
255 autosave_remove_timer_cb(book, AUTOSAVE_SOURCE_ID,
256 qof_book_get_data(book, AUTOSAVE_SOURCE_ID));
257}
258
259static void gnc_autosave_add_timer(QofBook *book)
260{
261 guint interval_mins =
262 gnc_prefs_get_float(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTOSAVE_INTERVAL);
263
264 /* Interval zero means auto-save is turned off. */
265 if ( interval_mins > 0
266 && ( ! gnc_file_save_in_progress() )
267 && gnc_current_session_exist() )
268 {
269 /* Add a new timer (timeout) that runs until the next autosave
270 timeout. */
271 guint autosave_source_id =
272 g_timeout_add_seconds(interval_mins * 60,
273 autosave_timeout_cb, book);
274 DEBUG("Adding new auto-save timer with id %d\n", autosave_source_id);
275
276 /* Save the event source id for a potential removal, and also
277 set the callback upon book closing */
278 qof_book_set_data_fin(book, AUTOSAVE_SOURCE_ID,
279 GUINT_TO_POINTER(autosave_source_id),
280 autosave_remove_timer_cb);
281 }
282}
283
284void gnc_autosave_dirty_handler (QofBook *book, gboolean dirty)
285{
286 DEBUG("gnc_main_window_autosave_dirty(dirty = %s)\n",
287 (dirty ? "TRUE" : "FALSE"));
288 if (dirty)
289 {
290 if (qof_book_is_readonly(book))
291 {
292 //DEBUG("Book is read-only, ignoring dirty flag");
293 return;
294 }
295
296 /* Book state changed from non-dirty to dirty. */
297 if (!qof_book_shutting_down(book))
298 {
299 /* Start the autosave timer.
300 First stop a potentially running old timer. */
301 gnc_autosave_remove_timer(book);
302 /* Add a new timer (timeout) that runs until the next autosave
303 timeout. */
304 gnc_autosave_add_timer(book);
305 }
306 else
307 {
308 DEBUG("Shutting down book, ignoring dirty book");
309 }
310 }
311 else
312 {
313 /* Book state changed from dirty to non-dirty (probably due to
314 saving). Delete the running autosave timer. */
315 gnc_autosave_remove_timer(book);
316 }
317}
All type declarations for the whole Gnucash engine.
Functions for adding content to a window.
Generic api to store and retrieve preferences.
Functions that are supported by all types of windows.
gboolean qof_book_shutting_down(const QofBook *book)
Is the book shutting down?
Definition qofbook.cpp:447
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition qofbook.cpp:497
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
void gnc_main_window_set_progressbar_window(GncMainWindow *window)
Set the window where all progressbar updates should occur.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
gboolean gnc_prefs_set_float(const gchar *group, const gchar *pref_name, gdouble value)
Store a float value into the preferences backend.
gdouble gnc_prefs_get_float(const gchar *group, const gchar *pref_name)
Get an float value from the preferences backend.
gboolean gnc_prefs_set_bool(const gchar *group, const gchar *pref_name, gboolean value)
Store a boolean value into the preferences backend.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
QofBook reference.
Definition qofbook-p.hpp:47