GnuCash  5.6-150-g038405b370+
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"
48 static const QofLogModule log_module = G_LOG_DOMAIN;
49 
50 static void
51 autosave_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 
81 static 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 
175 static 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 
235 static void
236 autosave_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 
253 void 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 
259 static 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 
284 void 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 }
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
Functions that are supported by all types of windows.
Functions for adding content to a window.
gboolean gnc_prefs_set_bool(const gchar *group, const gchar *pref_name, gboolean value)
Store a boolean value into the preferences backend.
Definition: gnc-prefs.c:278
void qof_book_set_data_fin(QofBook *book, const gchar *key, gpointer data, QofBookFinalCB)
Same as qof_book_set_data(), except that the callback will be called when the book is destroyed...
gboolean gnc_prefs_set_float(const gchar *group, const gchar *pref_name, gdouble value)
Store a float value into the preferences backend.
Definition: gnc-prefs.c:309
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition: qofbook.cpp:497
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
gboolean qof_book_shutting_down(const QofBook *book)
Is the book shutting down?
Definition: qofbook.cpp:447
gpointer qof_book_get_data(const QofBook *book, const gchar *key)
Retrieves arbitrary pointers to structs stored by qof_book_set_data.
void gnc_main_window_set_progressbar_window(GncMainWindow *window)
Set the window where all progressbar updates should occur.
gdouble gnc_prefs_get_float(const gchar *group, const gchar *pref_name)
Get an float value from the preferences backend.