diff -uN gnumeric.orig/src/auto-save.c gnumeric/src/auto-save.c --- gnumeric.orig/src/auto-save.c Wed Dec 31 19:00:00 1969 +++ gnumeric/src/auto-save.c Sat Feb 23 00:36:06 2002 @@ -0,0 +1,291 @@ +/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * auto-save.c: Auto-save and recovery code, done right. + * + * Author: + * C. Scott Ananian + * + * (C) 2002 C. Scott Ananian + */ +#include +#include "gnumeric.h" +#include "auto-save.h" +#include "workbook.h" +#include "workbook-control-gui.h" +#include "workbook-view.h" +#include "command-context.h" +#include "xml-io.h" +#include "gui-util.h" + +#include +#include +#include + +/* these are the interesting constants. not terribly interesting. =) */ +#define GNUMERIC_AUTOSAVE_PREFIX "GNM-" +#define GNUMERIC_AUTOSAVE_SUFFIX "-XXXXXX" +#define AUTOSAVE_MINUTES 1/*for testing, in final patch this will be 5*/ + +/* local prototypes */ +static int cb_autosave(gpointer arg); +static void do_auto_save_now(Workbook *wb); + +/* display a non-modal notification of an auto-save error. */ +static void +autosave_error_notice(const gchar *msg) +{ + GtkWidget *dialog; + /* gtk dialog stuff */ + gnumeric_notice_nonmodal(NULL, &dialog, GTK_MESSAGE_WARNING, msg); + /* ta-da! */ +} + +/* ask the user if they want to open the given recovery file. */ +static void +maybe_open(WorkbookView *wbv, WorkbookControl *wbc, + gchar *filename, gchar *suggestion) +{ + WorkbookControlGUI *wbcg = WORKBOOK_CONTROL_GUI(wbc); + GtkWidget *dialog; + int response; + /* gtk dialog stuff */ + dialog = gtk_message_dialog_new + (wbcg_toplevel(wbcg), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + _("Gnumeric detected an auto-recovery file for\n" + "\'%s\'. This may belong to another copy of\n" + "gnumeric which is running on this machine\n" + "(in which case you probably should choose\n" + "\'Later' in case this other copy crashes),\n" + "or it may be from a crashed gnumeric (in \n" + "which case it has stuff in it you probably\n" + "want back!). Do you wish to recover this file\n" + "now?"), suggestion); + gtk_dialog_add_button(GTK_DIALOG(dialog),_("Ask me later"), 0); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), 0); + /* note that gnumeric_dialog_run will take care of making this + * dialog transient and destroying the window after the response. */ + switch (gnumeric_dialog_run (wbcg, GTK_DIALOG(dialog))) { + case GTK_RESPONSE_YES: + /* open this file, set its autosave filename to filename, + * mark it dirty, set its filename. */ + if (wb_view_open_custom(wbv, wbc, &wbc, + gnumeric_xml_get_opener(), + filename, TRUE)) { + Workbook *wb = wb_control_workbook(wbc); + workbook_set_dirty(wb, TRUE); + wb->last_autosave_filename = g_strdup(filename); + workbook_set_filename(wb, suggestion); + } + break; + case GTK_RESPONSE_NO: + /* delete this autosave file */ + unlink(filename); + break; + default: + /* leave it alone */ + break; + } + +} + +/* check to see if there are any recovery files, and offer to recover + * any which are found. */ +void +auto_save_check_recover(WorkbookView *wbv, WorkbookControl *wbc) +{ + GDir *tempdir; + const gchar *basename; + gchar *fullname, *formerly, *prefix; + + /* look for crashfiles in the default temp dir. */ + tempdir = g_dir_open(g_get_tmp_dir(), 0, NULL); + if (tempdir==NULL) + return; /* can't open temp dir/can't find crash files */ + + /* add the username to the prefix, to make sure we don't try to + * recover someone else's files! */ + prefix = g_strconcat(GNUMERIC_AUTOSAVE_PREFIX, g_get_user_name(), "-", + NULL); + + while (NULL != (basename = g_dir_read_name(tempdir))) { + /* does basename match prefix? */ + if (strncmp(prefix, basename, + strlen(prefix)) != 0) + continue; /* nope, not a candidate. */ + /* does basename match suffix? note that last six characters + * of suffix template are always X's. */ + if (strlen(basename) < + strlen(prefix)+strlen(GNUMERIC_AUTOSAVE_SUFFIX)) + continue; /* basename's not long enough. */ + /* check all but the last six characters of suffix template */ + if (strncmp(basename + + strlen(basename)-strlen(GNUMERIC_AUTOSAVE_SUFFIX), + GNUMERIC_AUTOSAVE_SUFFIX, + strlen(GNUMERIC_AUTOSAVE_SUFFIX)-6) != 0) + continue; /* nope, doesn't match suffix template */ + /* o-KAY! this is a autosave file candidate! */ + /* let's create the full path for this guy, and also + * re-create the former name of the worksheet. */ + formerly = g_strndup(basename+strlen(prefix), + strlen(basename)- + strlen(prefix)- + strlen(GNUMERIC_AUTOSAVE_SUFFIX)); + + fullname = g_build_filename(g_get_tmp_dir(), basename, NULL); + /* a final check, now that we have the fullname */ + /* XXX would like to check that it's also readable, writable, + * and perhaps also owned by the current user. */ + if (g_file_test(fullname, G_FILE_TEST_IS_REGULAR)) + /* ta-da! ask the user if we should open this one. */ + maybe_open(wbv, wbc, fullname, formerly); + /* free up memory for the next go-round. */ + g_free(formerly); + g_free(fullname); + } + g_free(prefix); + g_dir_close(tempdir); +} + +/* called after save or load to reset to autosave system for this + * worksheet. */ +void +auto_save_reset(Workbook *wb) +{ + /* get rid of last autosave file & remove timer */ + auto_save_destroy(wb); + /* reinstate timer. */ + wb->autosave_timer_id = + g_timeout_add(AUTOSAVE_MINUTES*60*1000, cb_autosave, wb); + +} + +/* called when a worksheet is closed w/ the users okay to throw away + * unsaved contents. */ +void +auto_save_destroy(Workbook *wb) +{ + /* remove the timer */ + if (wb->autosave_timer_id != 0) + g_source_remove (wb->autosave_timer_id); + wb->autosave_timer_id = 0; + /* remove any out-of-date autosave files */ + if (wb->last_autosave_filename!=NULL) { + unlink(wb->last_autosave_filename); + g_free(wb->last_autosave_filename); + } + wb->last_autosave_filename=NULL; +} + +/* XXX: should count keystrokes or input events to know when this should + * be called. emacs also decreases the frequency on very large files + * (on the grounds that it's more intrusive; we might time the + * do_autosave_now method to determine this) and auto-saves whenever + * there's been a sufficiently-long idle time (how do we do this + * in GTK? maybe an event filter?). emacs also auto-saves all files + * at once, hmm. maybe the timer is not a workbook property after all. + * should we disable auto-save after it has failed once? (re-enable on + * next save, as per emacs) + * + * possible parameters: TIME=MAX(5min, 60*(save time, in sec)) + * events = 300 (buttons and keypress only) -- but fall back to + * time-based if the save takes longer than 3s. + * + * XXX: NOT LOADING non-compressed XML correctly (importing as text), + * and NOT SAVING TO FD in compressed form. For the moment we are + * working around this by explicitly setting the right file opener + * during the recovery process. But we shouldn't need to do this. + */ + +/** Auto-save the given workbook and reset the timer. */ +void +auto_save_now(Workbook *wb) +{ + /* reset the timer, since this is a non-timed auto-save. */ + if (wb->autosave_timer_id != 0) + g_source_remove (wb->autosave_timer_id); + wb->autosave_timer_id = g_timeout_add(AUTOSAVE_MINUTES*60*1000, + cb_autosave, wb); + /* okay, now do the autosave */ + do_auto_save_now(wb); +} +/** Timer callback to do the auto-save. */ +static int +cb_autosave(gpointer arg) +{ + Workbook *wb = WORKBOOK(arg); + do_auto_save_now(wb); + return TRUE; /* keep the timer running. */ +} +/** This is the actual function which does the save. */ +static void +do_auto_save_now(Workbook *wb) +{ + WorkbookView *wbv=NULL; + WorkbookControl *wbc=NULL; + const gchar *suggested; + gchar *template, *newname; + IOContext *io_context; + GError *error = NULL; + int fd; + /* if no changes, no need to auto-save. */ + if (!workbook_is_dirty(wb)) return; + /* pick any view of the workbook; it doesn't matter since this + * is just used for crash-recovery. */ + WORKBOOK_FOREACH_VIEW(wb, v, wbv=v;); + if (wbv==NULL) return; /* no view for this workbook!? */ + /* pick any control of the view; it still doesn't matter. */ + WORKBOOK_VIEW_FOREACH_CONTROL(wbv, c, wbc=c;); + if (wbc==NULL) return; /* no control for this workbook!? */ + /* create template from human-readable description 'suggested' */ + suggested = wb->filename; /* XXX is this a good description? */ + if (suggested==NULL) + suggested="(unknown)"; + template = g_strconcat(GNUMERIC_AUTOSAVE_PREFIX, + g_get_user_name(), "-", suggested, + GNUMERIC_AUTOSAVE_SUFFIX, NULL); + /* make the template safe by removing any troublesome characters + * (this is weird for non-roman scripts and diacritics. maybe we + * should remove *troublesome* characters instead?) */ + g_strcanon(template, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" /* maybe some other portably-safe chars? */, + '-'); + /* open a file matching this template in the tmp directory */ + fd = g_file_open_tmp(template, &newname, &error); g_free(template); + if (error!=NULL) { + /* can't autosave. put up status message. */ + autosave_error_notice(error->message); + g_error_free(error); + /* xxx? should disable autosave? */ + return; + } + /* save to fd. Note we must use fd rather than filename to prevent + * races and potential security holes. */ + io_context = gnumeric_io_context_new(COMMAND_CONTEXT(wbc)); + gnumeric_xml_write_workbook_to_fd(NULL, io_context, wbv, fd); + close(fd); + if (gnumeric_io_error_occurred(io_context)) { + ErrorInfo *ei; + /* error during save. keep oldname, unlink newname. */ + unlink(newname); + g_free(newname); + /* put up status message. */ + ei = gnumeric_io_error_pop(io_context); + autosave_error_notice(error_info_peek_message(ei)); + error_info_free(ei); + } else { + /* successful save */ + if (wb->last_autosave_filename!=NULL) { + unlink(wb->last_autosave_filename); + g_free(wb->last_autosave_filename); + } + wb->last_autosave_filename = newname; + } + g_object_unref (G_OBJECT (io_context)); + /* done. */ +} diff -uN gnumeric.orig/src/auto-save.h gnumeric/src/auto-save.h --- gnumeric.orig/src/auto-save.h Wed Dec 31 19:00:00 1969 +++ gnumeric/src/auto-save.h Sat Feb 23 00:28:13 2002 @@ -0,0 +1,24 @@ +#ifndef GNUMERIC_AUTO_SAVE_H +#define GNUMERIC_AUTO_SAVE_H + +#include "workbook.h" +#include "workbook-control.h" +#include "workbook-view.h" + +/* Looks for crash files left by a previous run of gnumeric; + * opens a dialog and then calls wb_view_open() on any that are found. */ +void auto_save_check_recover(WorkbookView *wbv, WorkbookControl *wbc); + +/* Resets/starts the autosave timer when workbook becomes "clean" */ +void auto_save_reset(Workbook *wb); + +/* This function is called to force an autosave. Recommended before + * you do something exceedingly hairy, or when the number of unsaved + * changes to the workbook gets large (TODO: we don't track unsaved + * changes currently). */ +void auto_save_now(Workbook *wb); + +/* Destroys the autosave timer and removes the last autosave file. */ +void auto_save_destroy(Workbook *wb); + +#endif /* GNUMERIC_AUTOSAVE_H */