/*
   Kickshaw - A Menu Editor for Openbox

   Copyright (c) 2010–2025        Marcus Schätzle

   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 Kickshaw. If not, see http://www.gnu.org/licenses/.
*/

#include <gtk/gtk.h>

#include <stdlib.h> // Sometimes this might have to be included explicitly.
#include <string.h> // Sometimes this might have to be included explicitly.

#include "declarations_definitions_and_enumerations.h"
#include "undo_redo_autosave.h"

static void show_errmsg_and_restore_position_index (const gchar  *action, 
                                                          GError *error);
static void update_pos_inside_undo_stack_pointer (void);
static void write_autosave_callback (      GObject      *source_object, 
                                           GAsyncResult *result, 
                                     const gchar        *autosave_file_path);
static gboolean write_row_information (GtkTreeModel  *model, 
                                       GtkTreePath   *path, 
                                       GtkTreeIter   *iter, 
                                       GOutputStream *compressed_undo_file_output_stream);

/*

    Shows a message related to an error that occurred during the creation or writing 
    of a new undo stack item and resets the position index, if applicable.

*/

static void show_errmsg_and_restore_position_index (const gchar  *action, 
                                                          GError *error)
{
    const gchar *err_msg_markup;

    if (STREQ (action, "creation")) {
        err_msg_markup = _("<b>Could not create undo stack item</b> <tt>%1$i</tt> <b>inside tmp folder</b> <tt>%2$s</tt> <b>!\nError:</b> %3$s");
    }
    else if (STREQ (action, "writing")) {
        err_msg_markup = _("<b>An error occurred during the writing process of the new undo stack item</b> "
                           "<tt>%1$i</tt> <b>inside tmp folder</b> <tt>%2$s</tt> <b>!\nError:</b> %3$s");
    }
    else { // deletion
        err_msg_markup = _("<b>Could not remove incomplete undo stack item</b> "
                           "<tt>%1$i</tt> <b>inside tmp folder</b> <tt>%2$s</tt> <b>!\nError:</b> %3$s");
    }

    g_autofree gchar *err_msg = g_strdup_printf (err_msg_markup, 
                                                 (!STREQ (action, "deletion")) ? ks.pos_inside_undo_stack-- : ks.pos_inside_undo_stack, 
                                                 ks.tmp_path, error->message);

    show_errmsg (err_msg);
}

/* 

    Writes everything relevant needed to restore the data and appearance of a single tree view row, including:
    - Tree store fields (of the image related fields, only the file path is used, 
      since there might have occurred changes to the image file in the time between the storage of the data 
      and its retrieval).
    - Existence of a label
    - Path depth
    - Selection status
    - Expansion status

*/

static gboolean write_row_information (GtkTreeModel  *model,
                                       GtkTreePath   *path, 
                                       GtkTreeIter   *iter, 
                                       GOutputStream *compressed_undo_file_output_stream)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
    g_autoptr(GString) row_info = g_string_new (NULL);
    g_autoptr(GError) error = NULL;

    gchar *undo_txt_fields[NUMBER_OF_TXT_FIELDS];

    for (guint8 tree_store_loop = 0; tree_store_loop < NUMBER_OF_TXT_FIELDS; ++tree_store_loop) {
        /*
            Reading of the tree store data has to start from TS_ICON_PATH,
            since TS_ICON_IMG, TS_ICON_IMG_STATUS, and TS_ICON_MODIFICATION_TIME aren't used.
        */
        gtk_tree_model_get (model, iter, tree_store_loop + TS_ICON_PATH, &undo_txt_fields[tree_store_loop], -1);
        g_string_append_printf (row_info, "%s%c", 
                                (undo_txt_fields[tree_store_loop]) ? undo_txt_fields[tree_store_loop] : "\0", 
                                31); // 31 = unit separator
    }

    g_string_append_printf (row_info, "%s%c%i%c%s%c%s%c", 
                            (streq_any (undo_txt_fields[TYPE_TXT], "menu", "pipe menu", "item", "separator", NULL) && 
                             undo_txt_fields[MENU_ELEMENT_TXT]) ? "TRUE" : "FALSE", // Existence of a label
                            31, // 31 = unit separator
                            gtk_tree_path_get_depth (path), // Path depth
                            31, // 31 = unit separator
                            (gtk_tree_selection_iter_is_selected (selection, iter)) ? "TRUE" : "FALSE", // Selection status
                            31, // 31 = unit separator
                            // Expansion status
                            (gtk_tree_view_row_expanded (GTK_TREE_VIEW (ks.treeview), path)) ? "TRUE" : "FALSE",
                            30); // 30 = record/row separator

    if (G_UNLIKELY (!(g_output_stream_write (compressed_undo_file_output_stream, 
                                             row_info->str, strlen (row_info->str), NULL, &error)))) {
        show_errmsg_and_restore_position_index ("writing", error);
    }

    // Cleanup
    free_elements_of_static_string_array (undo_txt_fields, NUMBER_OF_TXT_FIELDS, FALSE);

    return (error != NULL); // TRUE = stop iterating.
}

/* 

    Pushes tree view data on the undo stack, 
    i.e. writes this data into a compressed file inside the tmp folder.

*/

void push_new_item_on_undo_stack (const gboolean after_save)
{
    g_autoptr(GError) error = NULL;

    /*
        Applying a change starting from a position earlier than the latest undo file creates a new "time line".
        This means that all later undo files starting from the current position have to be removed.
    */
    if (ks.pos_inside_undo_stack < ks.number_of_undo_stack_items) {
        for (gint stack_file_cnt = ks.pos_inside_undo_stack + 1; stack_file_cnt <= ks.number_of_undo_stack_items; ++stack_file_cnt) {
            g_autofree gchar *later_undo_file_str = g_strdup_printf ("%s/%i", ks.tmp_path, stack_file_cnt);
            g_autoptr(GFile) later_undo_file = g_file_new_for_path (later_undo_file_str);

            if (G_UNLIKELY (!(g_file_delete (later_undo_file, NULL, &error)))) {
                g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not remove undo stack item</b> <tt>%1$i</tt> "
                                                               "<b>inside tmp folder</b> <tt>%2$s</tt> <b>!\nError:</b> %3$s\n\n"
                                                               "--- Note ---\n\n"
                                                               "All undo stack items after the current position are supposed "
                                                               "to be deleted when a change is applied prior to the latest change, "
                                                               "i.e. when it is applied from a position within the undo stack.\n"
                                                               "A new undo stack item '<tt>%4$i</tt>' for this change won't be saved "
                                                               "so it will not be possible to recall this change by invoking an "
                                                               "undo or redo action."), 
                                                             stack_file_cnt, ks.tmp_path, error->message, ks.pos_inside_undo_stack + 1);

                show_errmsg (err_msg);

                return;
            }
        }

        ks.number_of_undo_stack_items = ks.pos_inside_undo_stack;
    }

    if (!after_save) {
        ks.pos_inside_undo_stack = ks.number_of_undo_stack_items + 1;
    }

    g_autofree gchar *compressed_undo_file_str = g_strdup_printf ("%s/%i", ks.tmp_path, ks.pos_inside_undo_stack);
    g_autoptr(GFile) compressed_undo_file = g_file_new_for_path (compressed_undo_file_str);

    g_autoptr(GOutputStream) undo_file_output_stream;

    if (!after_save) {
        if (G_UNLIKELY (!((undo_file_output_stream = G_OUTPUT_STREAM (g_file_create (compressed_undo_file, G_FILE_CREATE_NONE, 
                                                                                     FALSE, &error)))))) {
            show_errmsg_and_restore_position_index ("creation", error);

            return;
        }
    }
    else {
        if (G_UNLIKELY (!((undo_file_output_stream = G_OUTPUT_STREAM (g_file_replace (compressed_undo_file, NULL, FALSE, 
                                                                                      G_FILE_CREATE_NONE, NULL, &error)))))) {
            g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not remove undo stack item</b> <tt>%1$i</tt> "
                                                           "<b>inside tmp folder</b> <tt>%2$s</tt> <b>!\nError:</b> %3$s"), 
                                                         ks.pos_inside_undo_stack, ks.tmp_path, error->message);

            show_errmsg (err_msg);

            return;
        }
    }

    g_autoptr(GConverter) compressor = G_CONVERTER (g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, -1));
    GOutputStream *compressed_undo_file_output_stream = g_converter_output_stream_new (undo_file_output_stream, compressor);

    g_autofree gchar *filename_header = g_strdup_printf ("%s%c", (ks.filename) ? (ks.filename) : "\0", 31); // 31 = unit separator

    if (G_UNLIKELY (!(g_output_stream_write (compressed_undo_file_output_stream, filename_header, strlen (filename_header), NULL, &error)))) {
        show_errmsg_and_restore_position_index ("writing", error);
    }

    g_autoptr(GString) global_data = g_string_new (NULL);

    for (guint8 edit_booleans_cnt = 0; edit_booleans_cnt < NUMBER_OF_LOADING_PROCESS_EDIT_TYPES; ++edit_booleans_cnt) {
        g_string_append_printf (global_data, "%s%c", 
                                (ks.loading_process_edit_types[edit_booleans_cnt]) ? "TRUE" : "FALSE", 
                                (edit_booleans_cnt < NUMBER_OF_LOADING_PROCESS_EDIT_TYPES - 1) ? '|' : 31); // 31 = unit separator
    }
    // 31 = unit separator, 30 = record/row separator
    g_string_append_printf (global_data, "%s%c%i%c", (ks.change_done) ? "TRUE" : "FALSE", 31, ks.last_save_pos, 30);

    if (G_UNLIKELY (!(g_output_stream_write (compressed_undo_file_output_stream, global_data->str, strlen (global_data->str), NULL, &error)))) {
        show_errmsg_and_restore_position_index ("writing", error);
    }

    if (G_LIKELY (!error)) {
        gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) write_row_information, compressed_undo_file_output_stream);

        // The position index has been reset by the callback function write_row_information (), meaning that there has been an error.
        if (G_UNLIKELY (ks.pos_inside_undo_stack == ks.number_of_undo_stack_items && 
                        !(g_file_delete (compressed_undo_file, NULL, &error)))) {
            show_errmsg_and_restore_position_index ("deletion", error);
        }
    }

    // There has been an error, do not increase ks.number_of_undo_stack_items.
    if (G_UNLIKELY (!after_save && ks.pos_inside_undo_stack == ks.number_of_undo_stack_items)) {
        return;
    }

    ks.number_of_undo_stack_items = ks.pos_inside_undo_stack;

    update_pos_inside_undo_stack_pointer ();

    // Cleanup. This has to be unrefed here, or the new undo stack file can't be copied as new autosave file.
    g_clear_object (&compressed_undo_file_output_stream);

    write_autosave (compressed_undo_file);
}

/* 

    Undos the last or redos the next change from the current position inside the undo stack. Also restores an autosave.

*/

void undo_redo_restore (const gchar *action)
{
    // new_pos_inside_undo_stack is not used in case action = "restore".
    const gint new_pos_inside_undo_stack = (STREQ (action, "undo")) ? ks.pos_inside_undo_stack - 1 : ks.pos_inside_undo_stack + 1;
    g_autofree gchar *compressed_undo_file_str = (!(STREQ (action, "restore"))) ? 
                                                 g_strdup_printf ("%s/%i", ks.tmp_path, new_pos_inside_undo_stack) : 
                                                 g_build_filename (ks.home_dir, ".kickshaw_autosave", NULL);
    g_autoptr(GFile) compressed_undo_file = g_file_new_for_path (compressed_undo_file_str);
    g_autoptr(GFileInputStream) undo_file_input_stream;
    g_autoptr(GError) error = NULL;

    if (G_UNLIKELY (!((undo_file_input_stream = g_file_read (compressed_undo_file, NULL, &error))))) {
        g_autofree gchar *err_msg;

        if (!(STREQ (action, "restore"))) {
            err_msg = g_strdup_printf (_("<b>Could not open undo stack item</b> <tt>%1$i</tt> "
                                         "<b>inside tmp folder</b> <tt>%2$s</tt> <b>for reading!\n"
                                         "Error:</b> %3$s"), 
                                       new_pos_inside_undo_stack, ks.tmp_path, error->message);
        }
        else {
            err_msg = g_strdup_printf (_("<b>Could not open autosave file</b> <tt>%s</tt> <b>!\nError:</b> %s"), 
                                       compressed_undo_file_str, error->message);
        }

        show_errmsg (err_msg);

        return;
    }

    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));

    g_signal_handler_block (selection, ks.handler_id_row_selected);


    // --- Clear global data. ---


#if GLIB_CHECK_VERSION(2,64,0)
    g_clear_slist (&ks.menu_ids, (GDestroyNotify) g_free);
#else
    g_slist_free_full (ks.menu_ids, (GDestroyNotify) g_free);
    ks.menu_ids = NULL;
#endif

    if (ks.rows_with_icons) {
        stop_timeout ();
    }

    gtk_tree_store_clear (ks.treestore);

    ks.statusbar_msg_shown = FALSE;


    // --- Restore tree store from undo file. ---


    g_autoptr(GConverter) decompressor = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW));
    g_autoptr(GInputStream) decom_undo_file_input_stream = g_converter_input_stream_new (G_INPUT_STREAM (undo_file_input_stream), 
                                                                                         decompressor);

    g_autoptr(GString) record = g_string_new (NULL);
    gchar buffer[2];

    // The fields that are stored inside an undo file.
    enum { UN_ICON_PATH, UN_MENU_ELEMENT, UN_TYPE, UN_VALUE, UN_MENU_ID, UN_EXECUTE, UN_ELEMENT_VISIBILITY, 
           UN_MENU_ITEM_OR_SEP_WITH_LABEL, UN_PATH_DEPTH, UN_SELECTED, UN_EXPANDED };

    g_autofree GtkTreeIter *iters = g_malloc (sizeof (GtkTreeIter));

#if GLIB_CHECK_VERSION(2,56,0)
    g_autoslist(GtkTreePath) expanded_paths = NULL;
    g_autoslist(GtkTreePath) selected_paths = NULL;
#else
    GSList *expanded_paths = NULL;
    GSList *selected_paths = NULL;
#endif

    guint max_path_depth = 1; // Default

    // x1E (dec. 30) = record/row separator
    while (g_input_stream_read (decom_undo_file_input_stream, buffer, 1, NULL, &error) > 0 && !(STREQ (buffer, "\x1E"))) { 
        g_string_append (record, buffer);
    }

    // Global data

    if (STREQ (action, "restore")) {
        g_auto(GStrv) tokens = g_strsplit (record->str, "\x1F", -1); // x1F (dec. 31) = unit separator
        if (*(tokens[0])) {
            set_filename_and_window_title (g_strdup (tokens[0]));
        }
        else {
            if (!ks.settings.use_header_bar) {
                gtk_window_set_title (GTK_WINDOW (ks.window), _("Unsaved Menu"));
            }
            else {
                gtk_label_set_label (GTK_LABEL (ks.basename_label), _("Unsaved Menu"));
                gtk_widget_hide (ks.subtitle_label);
            }
        }

        g_auto(GStrv) loading_process_edit_tokens = g_strsplit (tokens[1], "|", -1);

        for (guint8 edit_booleans_cnt = 0; edit_booleans_cnt < NUMBER_OF_LOADING_PROCESS_EDIT_TYPES; ++edit_booleans_cnt) {
            ks.loading_process_edit_types[edit_booleans_cnt] = (STREQ (loading_process_edit_tokens[edit_booleans_cnt], "TRUE"));
        }

        ks.change_done = (STREQ (tokens[2], "TRUE"));
        ks.last_save_pos = atoi (tokens[3]);
    }

    g_string_assign (record, "");

    // Menu data

    while (g_input_stream_read (decom_undo_file_input_stream, buffer, 1, NULL, &error) > 0) {
        if (!(STREQ (buffer, "\x1E"))) { // x1E (dec. 30) = record/row separator
            g_string_append (record, buffer);
        }
        else { 
            g_auto(GStrv) tokens = g_strsplit (record->str, "\x1F", -1); // x1F (dec. 31) = unit separator
            const guint path_depth = atoi (tokens[UN_PATH_DEPTH]);
            const guint iter_idx = path_depth - 1;

            if (path_depth > max_path_depth) {
                max_path_depth = path_depth;
                iters = g_realloc (iters, sizeof (GtkTreeIter) * max_path_depth);
            }

            gtk_tree_store_insert (ks.treestore, &iters[iter_idx], (path_depth == 1) ? NULL : &iters[iter_idx - 1], -1);

            for (guint8 tokens_cnt = 0; tokens_cnt <= UN_ELEMENT_VISIBILITY; ++tokens_cnt) {
                gtk_tree_store_set (ks.treestore, &iters[iter_idx], tokens_cnt + TS_ICON_PATH, 
                                    (!STREQ (tokens[tokens_cnt], "\0")) ? tokens[tokens_cnt] : 
                                     /*
                                        Menus, items, and separators may have empty labels or no labels at all.
                                        Menu IDs may consist of empty strings.
                                     */
                                     (((tokens_cnt == UN_MENU_ELEMENT && STREQ (tokens[UN_MENU_ITEM_OR_SEP_WITH_LABEL], "TRUE")) ||
                                      tokens_cnt == UN_MENU_ID) ? 
                                      "\0" : NULL), 
                                      -1);
            }

            if (streq_any (tokens[UN_TYPE], "menu", "pipe menu", NULL)) {
                ks.menu_ids = g_slist_prepend (ks.menu_ids, g_strdup (tokens[UN_MENU_ID]));
            }

            if (*tokens[UN_ICON_PATH]) {
                g_autoptr(GFile) icon_path_file = g_file_new_for_path (tokens[UN_ICON_PATH]);

                if (G_LIKELY (g_file_query_exists (icon_path_file, NULL))) {
                    // This is a separate if-statement, because after a succesful call of set_icon() the 
                    // else-statement below should not be called.
                    if (G_UNLIKELY (!(set_icon (&iters[iter_idx], tokens[UN_ICON_PATH], FALSE, TRUE)))) {
                        g_autofree gchar *time_stamp = get_modification_time_for_icon (tokens[UN_ICON_PATH]);

                        gtk_tree_store_set (ks.treestore, &iters[iter_idx],
                                            TS_ICON_IMG, ks.invalid_icon_imgs[INVALID_FILE_ICON], 
                                            TS_ICON_IMG_STATUS, INVALID_FILE, 
                                            TS_ICON_MODIFICATION_TIME, time_stamp, 
                                            -1);
                    }
                }
                else {
                    gtk_tree_store_set (ks.treestore, &iters[iter_idx],
                                        TS_ICON_IMG, ks.invalid_icon_imgs[INVALID_PATH_ICON], 
                                        TS_ICON_IMG_STATUS, INVALID_PATH, 
                                        TS_ICON_MODIFICATION_TIME, NULL, 
                                        -1);
                }
            }

            g_autoptr(GtkTreePath) path = gtk_tree_model_get_path (ks.ts_model, &iters[iter_idx]);

            if (STREQ (tokens[UN_EXPANDED], "TRUE")) {
                expanded_paths = g_slist_prepend (expanded_paths, gtk_tree_path_copy (path));
            }

            if (STREQ (tokens[UN_SELECTED], "TRUE")) {
                selected_paths = g_slist_prepend (selected_paths, gtk_tree_path_copy (path));
            }

            g_string_assign (record, "");
        }
    }

    if (G_LIKELY (!error)) {
        GSList *expanded_paths_loop, *selected_paths_loop;

        expanded_paths = g_slist_reverse (expanded_paths); // Expand from top to bottom

        FOREACH_IN_LIST (expanded_paths, expanded_paths_loop) {
            gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), expanded_paths_loop->data, FALSE);
        }

        FOREACH_IN_LIST (selected_paths, selected_paths_loop) {
            gtk_tree_selection_select_path (selection, selected_paths_loop->data);
        }
    }

    g_signal_handler_unblock (selection, ks.handler_id_row_selected);

#if !(GLIB_CHECK_VERSION(2,56,0))
    // Cleanup
    g_slist_free_full (expanded_paths, (GDestroyNotify) gtk_tree_path_free);
    g_slist_free_full (selected_paths, (GDestroyNotify) gtk_tree_path_free);
#endif

    if (G_UNLIKELY (error)) {
        g_autofree gchar *err_msg;

        if (!STREQ (action, "restore")) {
            err_msg = g_strdup_printf (_("<b>Could not read data from undo stack item</b> <tt>%1$i</tt> "
                                         "<b>inside tmp folder</b> <tt>%2$s</tt> <b>!\n"
                                         "Error:</b> %3$s"), 
                                       new_pos_inside_undo_stack, ks.tmp_path, error->message);
        }
        else {
            err_msg = g_strdup_printf (_("<b>Could not read data from autosave file</b> <tt>%s</tt> <b>!\nError:</b> %s"), 
                                       compressed_undo_file_str, error->message);
        }

        show_errmsg (err_msg);

        return;
    }

    // If this was a restore of an autosave, setting ks.pos_inside_undo_stack is done later when the first undo item is pushed on the stack.
    if (!(STREQ (action, "restore"))) {
        ks.pos_inside_undo_stack = new_pos_inside_undo_stack;
        update_pos_inside_undo_stack_pointer ();
        write_autosave (compressed_undo_file);
    }

    // If this was a restore of an autosave for a new menu that had not been saved yet, the ability to save (not save as) should be disabled.
    if (STREQ (action, "restore") && !ks.filename) {
        ks.restored_autosave_that_hasnt_been_saved_yet = TRUE;
    }

    gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) add_icon_occurrence_to_list, NULL);
    if (ks.rows_with_icons) {
        ks.timeout_id = g_timeout_add_seconds (1, (GSourceFunc) check_for_external_file_and_settings_changes, "timeout");
    }

    if (!(STREQ (action, "restore"))) {
        ks.change_done = TRUE;
    }
    row_selected ();
}

/*

    Creates a symbolic link to the undo stack file that indicates the current position inside the stack.
    Meant for use after a restart or an irregular termination of the program.

*/

static void update_pos_inside_undo_stack_pointer (void)
{
    g_autofree gchar *pos_inside_undo_stack_str = g_strdup_printf ("%i", ks.pos_inside_undo_stack);
    g_autofree gchar *pos_inside_undo_stack_path = g_build_filename (ks.tmp_path, pos_inside_undo_stack_str, NULL);
    g_autofree gchar *symlink_pos_inside_undo_stack_path = g_build_filename (ks.tmp_path, "pos_inside_undo_stack_symlink", NULL);
    g_autoptr(GFile) symlink_pos_inside_undo_stack = g_file_new_for_path (symlink_pos_inside_undo_stack_path);

    g_autoptr(GError) error = NULL;

    if (g_file_query_exists (symlink_pos_inside_undo_stack, NULL)) {
        g_file_delete (symlink_pos_inside_undo_stack, NULL, &error);
    }

    if (G_UNLIKELY (error)) {
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not delete symbolic link to undo stack file</b> <tt>%s</tt> <b>!\n"
                                                       "Error:</b> %s"), 
                                                     pos_inside_undo_stack_path, error->message);

        show_errmsg (err_msg);

        return;
    }

    if (G_UNLIKELY (!(g_file_make_symbolic_link (symlink_pos_inside_undo_stack, pos_inside_undo_stack_path, NULL, &error)))) {
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not create symbolic link to undo stack file</b> <tt>%s</tt> <b>!\n"
                                                       "Error:</b> %s"), 
                                                     pos_inside_undo_stack_path, error->message);

        show_errmsg (err_msg);
    }
}

/*

    Creates an autosave file by copying the latest undo stack file to the home folder.

*/

void write_autosave (GFile *compressed_undo_file)
{
    g_autofree gchar *autosave_file_path = g_build_filename (ks.home_dir, ".kickshaw_autosave", NULL);
    g_autoptr(GFile) autosave_file = g_file_new_for_path (autosave_file_path);

    g_file_copy_async (compressed_undo_file, autosave_file, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, 
                       (GAsyncReadyCallback) write_autosave_callback, autosave_file_path);
}

/*

    Called after copying of the latest undo stack item as autosave is finished to check for errors.

*/

static void write_autosave_callback (      GObject      *source_object, 
                                           GAsyncResult *result, 
                                     const gchar        *autosave_file_path)
{
    g_autoptr(GError) error = NULL;

    g_file_copy_finish ((GFile *) source_object, result, &error);

    if (G_UNLIKELY (error)) {
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not create the autosave file</b> <tt>%s</tt> <b>!\nError:</b> %s"), 
                                                       autosave_file_path, error->message);

        show_errmsg (err_msg);
    }
}
