/*
 * SPDX-FileCopyrightText: 2003-2026 Sébastien Helleu <flashcode@flashtux.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

/* Save/restore session data of WeeChat core */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>

#include "weechat.h"
#include "core-upgrade.h"
#include "core-dir.h"
#include "core-hashtable.h"
#include "core-hook.h"
#include "core-infolist.h"
#include "core-secure-buffer.h"
#include "core-string.h"
#include "core-util.h"
#include "../gui/gui-buffer.h"
#include "../gui/gui-chat.h"
#include "../gui/gui-color.h"
#include "../gui/gui-history.h"
#include "../gui/gui-hotlist.h"
#include "../gui/gui-layout.h"
#include "../gui/gui-line.h"
#include "../gui/gui-nicklist.h"
#include "../gui/gui-window.h"
#include "../plugins/plugin.h"


struct t_gui_buffer *upgrade_current_buffer = NULL;
struct t_gui_buffer *upgrade_set_current_buffer = NULL;
int upgrade_set_current_window = 0;
int hotlist_reset = 0;
struct t_gui_layout *upgrade_layout = NULL;


/*
 * Save history in WeeChat upgrade file (from last to first, to restore it in
 * good order).
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
upgrade_weechat_save_history (struct t_upgrade_file *upgrade_file,
                              struct t_gui_history *last_history)
{
    struct t_infolist *ptr_infolist;
    struct t_infolist_item *ptr_item;
    struct t_gui_history *ptr_history;
    int rc;

    if (!last_history)
        return 1;

    ptr_infolist = infolist_new (NULL);
    if (!ptr_infolist)
        return 0;

    ptr_history = last_history;
    while (ptr_history)
    {
        ptr_item = infolist_new_item (ptr_infolist);
        if (!ptr_item)
        {
            infolist_free (ptr_infolist);
            return 0;
        }

        if (!infolist_new_var_string (ptr_item, "text", ptr_history->text))
        {
            infolist_free (ptr_infolist);
            return 0;
        }

        ptr_history = ptr_history->prev_history;
    }

    rc = upgrade_file_write_object (upgrade_file,
                                    UPGRADE_WEECHAT_TYPE_HISTORY,
                                    ptr_infolist);
    infolist_free (ptr_infolist);
    if (!rc)
        return 0;

    return 1;
}

/*
 * Save buffers in WeeChat upgrade file.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
upgrade_weechat_save_buffers (struct t_upgrade_file *upgrade_file)
{
    struct t_infolist *ptr_infolist;
    struct t_gui_buffer *ptr_buffer;
    struct t_gui_line *ptr_line;
    int rc;

    for (ptr_buffer = gui_buffers; ptr_buffer;
         ptr_buffer = ptr_buffer->next_buffer)
    {
        /* Save buffer. */
        ptr_infolist = infolist_new (NULL);
        if (!ptr_infolist)
            return 0;
        if (!gui_buffer_add_to_infolist (ptr_infolist, ptr_buffer))
        {
            infolist_free (ptr_infolist);
            return 0;
        }
        rc = upgrade_file_write_object (upgrade_file,
                                        UPGRADE_WEECHAT_TYPE_BUFFER,
                                        ptr_infolist);
        infolist_free (ptr_infolist);
        if (!rc)
            return 0;

        /* Save nicklist. */
        if (ptr_buffer->nicklist)
        {
            ptr_infolist = infolist_new (NULL);
            if (!ptr_infolist)
                return 0;
            if (!gui_nicklist_add_to_infolist (ptr_infolist, ptr_buffer, NULL))
            {
                infolist_free (ptr_infolist);
                return 0;
            }
            rc = upgrade_file_write_object (upgrade_file,
                                            UPGRADE_WEECHAT_TYPE_NICKLIST,
                                            ptr_infolist);
            infolist_free (ptr_infolist);
            if (!rc)
                return 0;
        }

        /* Save buffer lines. */
        for (ptr_line = ptr_buffer->own_lines->first_line; ptr_line;
             ptr_line = ptr_line->next_line)
        {
            ptr_infolist = infolist_new (NULL);
            if (!ptr_infolist)
                return 0;
            if (!gui_line_add_to_infolist (ptr_infolist,
                                           ptr_buffer->own_lines,
                                           ptr_line))
            {
                infolist_free (ptr_infolist);
                return 0;
            }
            rc = upgrade_file_write_object (upgrade_file,
                                            UPGRADE_WEECHAT_TYPE_BUFFER_LINE,
                                            ptr_infolist);
            infolist_free (ptr_infolist);
            if (!rc)
                return 0;
        }

        /* Save command/text history of buffer. */
        if (ptr_buffer->history)
        {
            rc = upgrade_weechat_save_history (upgrade_file,
                                               ptr_buffer->last_history);
            if (!rc)
                return 0;
        }
    }

    return 1;
}

/*
 * Save miscellaneous info in WeeChat upgrade file.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
upgrade_weechat_save_misc (struct t_upgrade_file *upgrade_file)
{
    struct t_infolist *ptr_infolist;
    struct t_infolist_item *ptr_item;
    int rc;

    ptr_infolist = infolist_new (NULL);
    if (!ptr_infolist)
        return 0;

    ptr_item = infolist_new_item (ptr_infolist);
    if (!ptr_item)
    {
        infolist_free (ptr_infolist);
        return 0;
    }
    if (!infolist_new_var_time (ptr_item, "start_time", weechat_first_start_time))
    {
        infolist_free (ptr_infolist);
        return 0;
    }
    if (!infolist_new_var_integer (ptr_item, "upgrade_count", weechat_upgrade_count))
    {
        infolist_free (ptr_infolist);
        return 0;
    }
    if (!infolist_new_var_integer (ptr_item, "current_window_number", gui_current_window->number))
    {
        infolist_free (ptr_infolist);
        return 0;
    }

    rc = upgrade_file_write_object (upgrade_file,
                                    UPGRADE_WEECHAT_TYPE_MISC,
                                    ptr_infolist);
    infolist_free (ptr_infolist);

    return rc;
}

/*
 * Save hotlist in WeeChat upgrade file.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
upgrade_weechat_save_hotlist (struct t_upgrade_file *upgrade_file)
{
    struct t_infolist *ptr_infolist;
    struct t_gui_hotlist *ptr_hotlist;
    int rc;

    for (ptr_hotlist = gui_hotlist; ptr_hotlist;
         ptr_hotlist = ptr_hotlist->next_hotlist)
    {
        ptr_infolist = infolist_new (NULL);
        if (!ptr_infolist)
            return 0;
        if (!gui_hotlist_add_to_infolist (ptr_infolist, ptr_hotlist))
        {
            infolist_free (ptr_infolist);
            return 0;
        }
        rc = upgrade_file_write_object (upgrade_file,
                                        UPGRADE_WEECHAT_TYPE_HOTLIST,
                                        ptr_infolist);
        infolist_free (ptr_infolist);
        if (!rc)
            return 0;
    }

    return 1;
}

/*
 * Save tree with layout for windows in WeeChat upgrade file.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
upgrade_weechat_save_layout_window_tree (struct t_upgrade_file *upgrade_file,
                                         struct t_gui_layout_window *layout_window)
{
    struct t_infolist *ptr_infolist;
    int rc;

    ptr_infolist = infolist_new (NULL);
    if (!ptr_infolist)
        return 0;

    if (!gui_layout_window_add_to_infolist (ptr_infolist, layout_window))
    {
        infolist_free (ptr_infolist);
        return 0;
    }

    rc = upgrade_file_write_object (upgrade_file,
                                    UPGRADE_WEECHAT_TYPE_LAYOUT_WINDOW,
                                    ptr_infolist);

    infolist_free (ptr_infolist);
    if (!rc)
        return 0;

    if (layout_window->child1)
    {
        if (!upgrade_weechat_save_layout_window_tree (upgrade_file,
                                                      layout_window->child1))
            return 0;
    }

    if (layout_window->child2)
    {
        if (!upgrade_weechat_save_layout_window_tree (upgrade_file,
                                                      layout_window->child2))
            return 0;
    }

    return 1;
}

/*
 * Save layout for windows in WeeChat upgrade file.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
upgrade_weechat_save_layout_window (struct t_upgrade_file *upgrade_file)
{
    struct t_gui_layout *ptr_layout;
    int rc;

    rc = 0;

    /* Get current layout for windows. */
    ptr_layout = gui_layout_alloc (GUI_LAYOUT_UPGRADE);

    if (ptr_layout)
    {
        gui_layout_window_store (ptr_layout);

        /* Save tree with layout of windows. */
        rc = upgrade_weechat_save_layout_window_tree (upgrade_file, ptr_layout->layout_windows);

        gui_layout_free (ptr_layout);
    }

    return rc;
}

/*
 * Save WeeChat upgrade file.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
upgrade_weechat_save (void)
{
    int rc;
    struct t_upgrade_file *upgrade_file;

    upgrade_file = upgrade_file_new (WEECHAT_UPGRADE_FILENAME,
                                     NULL, NULL, NULL);
    if (!upgrade_file)
        return 0;

    rc = 1;
    rc &= upgrade_weechat_save_history (upgrade_file, last_gui_history);
    rc &= upgrade_weechat_save_buffers (upgrade_file);
    rc &= upgrade_weechat_save_misc (upgrade_file);
    rc &= upgrade_weechat_save_hotlist (upgrade_file);
    rc &= upgrade_weechat_save_layout_window (upgrade_file);

    upgrade_file_close (upgrade_file);

    return rc;
}

/*
 * Read a buffer from infolist.
 */

void
upgrade_weechat_read_buffer (struct t_infolist *infolist)
{
    struct t_gui_buffer *ptr_buffer;
    struct t_infolist_var *ptr_var_id;
    const char *key, *var_name, *name, *plugin_name, *ptr_id;
    const char *str;
    char option_name[64], *option_key, *option_var;
    int index, main_buffer;
    long long id;

    /* "id" is new in WeeChat 4.3.0 */
    id = -1;
    ptr_var_id = infolist_search_var (infolist, "id");
    if (ptr_var_id)
    {
        switch (ptr_var_id->type)
        {
            case INFOLIST_LONGLONG: /* WeeChat ≥ 4.11.0 */
                id = infolist_longlong (infolist, "id");
                break;
            case INFOLIST_STRING: /* WeeChat < 4.11.0 */
                ptr_id = infolist_string (infolist, "id");
                if (ptr_id)
                {
                    if (!util_parse_longlong (ptr_id, 10, &id))
                        id = -1;
                }
                break;
            default:
                break;
        }
    }
    if (id < 0)
        id = gui_buffer_generate_id ();

    plugin_name = infolist_string (infolist, "plugin_name");
    name = infolist_string (infolist, "name");
    gui_layout_buffer_add (upgrade_layout,
                           plugin_name, name,
                           infolist_integer (infolist, "number"));
    main_buffer = gui_buffer_is_main (plugin_name, name);
    if (main_buffer)
    {
        /* Use WeeChat main buffer. */
        upgrade_current_buffer = gui_buffers;
        hashtable_remove (gui_buffer_by_id, &(upgrade_current_buffer->id));
        upgrade_current_buffer->id = id;
        hashtable_set (gui_buffer_by_id, &id, upgrade_current_buffer);
    }
    else
    {
        /* Create buffer if it's not main buffer. */
        upgrade_current_buffer = gui_buffer_new_props_with_id (
            id,
            NULL,  /* plugin */
            name,
            NULL,  /* properties */
            NULL, NULL, NULL,  /* input callback */
            NULL, NULL, NULL);  /* close callback */
    }
    if (!upgrade_current_buffer)
        return;

    if (upgrade_current_buffer->id > gui_buffer_last_id_assigned)
        gui_buffer_last_id_assigned = upgrade_current_buffer->id;

    ptr_buffer = upgrade_current_buffer;

    if (infolist_integer (infolist, "current_buffer"))
        upgrade_set_current_buffer = ptr_buffer;

    /* Name for upgrade */
    free (ptr_buffer->plugin_name_for_upgrade);
    ptr_buffer->plugin_name_for_upgrade =
        strdup (infolist_string (infolist, "plugin_name"));

    /* Full name */
    gui_buffer_build_full_name (ptr_buffer);

    /* Old full name */
    free (ptr_buffer->old_full_name);
    str = infolist_string (infolist, "old_full_name");
    ptr_buffer->old_full_name = (str) ? strdup (str) : NULL;

    /* Short name */
    free (ptr_buffer->short_name);
    str = infolist_string (infolist, "short_name");
    ptr_buffer->short_name = strdup ((str) ? str : name);

    /* Buffer type */
    ptr_buffer->type = infolist_integer (infolist, "type");

    /* Notify level */
    ptr_buffer->notify = infolist_integer (infolist, "notify");

    /* "hidden" is new in WeeChat 1.0 */
    if (infolist_search_var (infolist, "hidden"))
        ptr_buffer->hidden = infolist_integer (infolist, "hidden");
    else
        ptr_buffer->hidden = 0;

    /* Day change */
    if (infolist_search_var (infolist, "day_change"))
        ptr_buffer->day_change = infolist_integer (infolist, "day_change");
    else
        ptr_buffer->day_change = 1;

    /* "clear" is new in WeeChat 1.0 */
    if (infolist_search_var (infolist, "clear"))
        ptr_buffer->clear = infolist_integer (infolist, "clear");
    else
        ptr_buffer->clear = (ptr_buffer->type == GUI_BUFFER_TYPE_FREE) ? 0 : 1;

    /* "filter" is new in WeeChat 1.0 */
    if (infolist_search_var (infolist, "filter"))
        ptr_buffer->filter = infolist_integer (infolist, "filter");
    else
        ptr_buffer->filter = 1;

    /* Nicklist */
    ptr_buffer->nicklist_case_sensitive =
        infolist_integer (infolist, "nicklist_case_sensitive");
    ptr_buffer->nicklist_display_groups =
        infolist_integer (infolist, "nicklist_display_groups");

    /* Title (not for main buffer, because there's the latest version) */
    if (!main_buffer)
    {
        free (ptr_buffer->title);
        str = infolist_string (infolist, "title");
        ptr_buffer->title = (str) ? strdup (str) : NULL;
    }

    /* Modes */
    gui_buffer_set_modes (ptr_buffer, infolist_string (infolist, "modes"));

    /* First line not read */
    ptr_buffer->lines->first_line_not_read =
        infolist_integer (infolist, "first_line_not_read");

    /*
     * Note: "lines_last_id_assigned" is not read: it is rebuilt as each line
     * of the buffer is restored (see upgrade_weechat_read_buffer_line).
     */

    /* Time for each line */
    ptr_buffer->time_for_each_line =
        infolist_integer (infolist, "time_for_each_line");

    /* "prefix_for_each_line" is new in WeeChat 4.11.0 */
    if (infolist_search_var (infolist, "prefix_for_each_line"))
    {
        ptr_buffer->prefix_for_each_line =
            infolist_integer (infolist, "prefix_for_each_line");
    }
    else
    {
        ptr_buffer->prefix_for_each_line =
            (ptr_buffer->type == GUI_BUFFER_TYPE_FREE) ? 0 : 1;
    }

    /* Input */
    gui_buffer_set_input_prompt (
        ptr_buffer, infolist_string (infolist, "input_prompt"));
    ptr_buffer->input = infolist_integer (infolist, "input");
    ptr_buffer->input_get_any_user_data =
        infolist_integer (infolist, "input_get_any_user_data");
    ptr_buffer->input_get_unknown_commands =
        infolist_integer (infolist, "input_get_unknown_commands");
    ptr_buffer->input_get_empty =
        infolist_integer (infolist, "input_get_empty");
    ptr_buffer->input_multiline =
        infolist_integer (infolist, "input_multiline");

    if (infolist_integer (infolist, "input_buffer_alloc") > 0)
    {
        ptr_buffer->input_buffer =
            malloc (infolist_integer (infolist, "input_buffer_alloc"));
        if (ptr_buffer->input_buffer)
        {
            ptr_buffer->input_buffer_size =
                infolist_integer (infolist, "input_buffer_size");
            ptr_buffer->input_buffer_length =
                infolist_integer (infolist, "input_buffer_length");
            ptr_buffer->input_buffer_pos =
                infolist_integer (infolist, "input_buffer_pos");
            ptr_buffer->input_buffer_1st_display =
                infolist_integer (infolist, "input_buffer_1st_display");
            if (infolist_string (infolist, "input_buffer"))
                strcpy (ptr_buffer->input_buffer,
                        infolist_string (infolist, "input_buffer"));
            else
                ptr_buffer->input_buffer[0] = '\0';
        }
    }

    /* Text search is disabled after upgrade. */

    /* Highlight options */
    gui_buffer_set_highlight_words (
        ptr_buffer, infolist_string (infolist, "highlight_words"));
    gui_buffer_set_highlight_disable_regex (
        ptr_buffer, infolist_string (infolist, "highlight_disable_regex"));
    gui_buffer_set_highlight_regex (
        ptr_buffer, infolist_string (infolist, "highlight_regex"));
    if (infolist_search_var (infolist,
                             "highlight_tags_restrict"))
    {
        /* WeeChat >= 0.4.3 */
        gui_buffer_set_highlight_tags_restrict (
            ptr_buffer, infolist_string (infolist, "highlight_tags_restrict"));
        gui_buffer_set_highlight_tags (
            ptr_buffer, infolist_string (infolist, "highlight_tags"));
    }
    else
    {
        /* WeeChat <= 0.4.2 */
        gui_buffer_set_highlight_tags_restrict (
            ptr_buffer, infolist_string (infolist, "highlight_tags"));
    }

    /* Hotlist max level nicks */
    gui_buffer_set_hotlist_max_level_nicks (
        ptr_buffer, infolist_string (infolist, "hotlist_max_level_nicks"));

    /* Local keys */
    index = 0;
    while (1)
    {
        snprintf (option_name, sizeof (option_name), "key_%05d", index);
        key = infolist_string (infolist, option_name);
        if (!key)
            break;
        if (string_asprintf (&option_key, "key_bind_%s", key) >= 0)
        {
            snprintf (option_name, sizeof (option_name),
                      "key_command_%05d", index);
            gui_buffer_set (ptr_buffer, option_key,
                            infolist_string (infolist, option_name));
            free (option_key);
        }
        index++;
    }

    /* Local variables */
    index = 0;
    while (1)
    {
        snprintf (option_name, sizeof (option_name),
                  "localvar_name_%05d", index);
        var_name = infolist_string (infolist, option_name);
        if (!var_name)
            break;
        if (string_asprintf (&option_var, "localvar_set_%s", var_name) >= 0)
        {
            snprintf (option_name, sizeof (option_name),
                      "localvar_value_%05d", index);
            gui_buffer_set (ptr_buffer, option_var,
                            infolist_string (infolist, option_name));
            free (option_var);
        }
        index++;
    }
}

/*
 * Read a buffer line from infolist.
 */

void
upgrade_weechat_read_buffer_line (struct t_infolist *infolist)
{
    struct t_gui_line *new_line;

    if (!upgrade_current_buffer)
        return;

    switch (upgrade_current_buffer->type)
    {
        case GUI_BUFFER_TYPE_FORMATTED:
        {
            struct t_infolist_var *ptr_var_id;
            int date_usec, date_usec_printed, highlight, last_read_line;
            int old_format;
            long long id;
            time_t date, date_printed;
            const char *tags, *prefix, *message, *str_time;

            /*
             * Read the fields in the same order they were written by
             * gui_line_add_to_infolist(), so that the infolist lookup
             * cursor (see infolist_item_search_var() in core-infolist.c)
             * can advance forward through the item's variables instead of
             * wrapping around to search backwards for every other field;
             * this also makes the read order deterministic, since C does
             * not guarantee the evaluation order of function arguments.
             */

            /*
             * "id" is a "long long" since WeeChat 4.11.0 (it is the date of
             * print, with microseconds precision); before it was an integer
             * (a counter in the buffer) and the date of print was saved in
             * "date_printed"/"date_usec_printed": in this case the id is
             * rebuilt from these two variables.
             *
             * Note: with old upgrade files, all the lines displayed by a
             * single call to gui_chat_printf_datetime_tags() share the same
             * date of print, so the conversion below can return the same id
             * for several lines; gui_line_new_with_id() increases the id when
             * needed, to keep the ids unique and sorted in the buffer.
             */
            ptr_var_id = infolist_search_var (infolist, "id");
            old_format = (!ptr_var_id
                          || (ptr_var_id->type != INFOLIST_LONGLONG));
            id = (old_format) ? -1 : infolist_longlong (infolist, "id");
            date = infolist_time (infolist, "date");
            date_usec = infolist_integer (infolist, "date_usec");
            date_printed = infolist_time (infolist, "date_printed");
            date_usec_printed = infolist_integer (infolist, "date_usec_printed");
            if (old_format)
            {
                id = ((long long)date_printed * 1000000LL)
                    + (long long)date_usec_printed;
            }
            str_time = infolist_string (infolist, "str_time");
            tags = infolist_string (infolist, "tags");
            highlight = infolist_integer (infolist, "highlight");
            prefix = infolist_string (infolist, "prefix");
            message = infolist_string (infolist, "message");
            last_read_line = infolist_integer (infolist, "last_read_line");

            /*
             * Pass the saved highlight state directly (known_highlight)
             * instead of letting gui_line_new_with_id() run its regex-based
             * highlight detection: the result would be overwritten by the
             * saved value right below anyway, so computing it would be
             * wasted work (and, before this fix, left str_time colorized
             * for the wrong highlight state, since it was never
             * recomputed after the overwrite); for the same reason, pass
             * the saved time string directly (known_str_time) instead of
             * letting gui_line_new_with_id() recompute/recolorize it.
             */
            new_line = gui_line_new_with_id (
                upgrade_current_buffer,
                id,
                -1,
                date, date_usec,
                tags, prefix, message,
                highlight, str_time);
            if (new_line)
            {
                gui_line_add (new_line, 0);
                if (last_read_line)
                    upgrade_current_buffer->lines->last_read_line = new_line;
            }
            break;
        }
        case GUI_BUFFER_TYPE_FREE:
        {
            int y, date_usec;
            time_t date;
            const char *tags, *message;

            /* Read the fields in written order (see comment above). */
            y = infolist_integer (infolist, "y");
            date = infolist_time (infolist, "date");
            date_usec = infolist_integer (infolist, "date_usec");
            tags = infolist_string (infolist, "tags");
            message = infolist_string (infolist, "message");

            /* The id of a line is its number ("y") on buffers with free content. */
            new_line = gui_line_new (
                upgrade_current_buffer,
                y,
                date, date_usec,
                tags,
                NULL,
                message,
                -1, NULL);
            if (new_line)
                gui_line_add_y (new_line);
            break;
        }
        case GUI_BUFFER_NUM_TYPES:
            break;
    }
}

/*
 * Read a nicklist from infolist.
 */

void
upgrade_weechat_read_nicklist (struct t_infolist *infolist)
{
    struct t_gui_nick_group *ptr_group;
    struct t_infolist_var *ptr_var_id;
    const char *type, *name, *group_name, *ptr_id;
    long long id;

    if (!upgrade_current_buffer)
        return;

    upgrade_current_buffer->nicklist = 1;
    ptr_group = NULL;
    type = infolist_string (infolist, "type");
    if (!type)
        return;

    /* "id" is new in WeeChat 4.3.0 */
    id = -1;
    ptr_var_id = infolist_search_var (infolist, "id");
    if (ptr_var_id)
    {
        switch (ptr_var_id->type)
        {
            case INFOLIST_LONGLONG: /* WeeChat ≥ 4.11.0 */
                id = infolist_longlong (infolist, "id");
                break;
            case INFOLIST_STRING: /* WeeChat < 4.11.0 */
                ptr_id = infolist_string (infolist, "id");
                if (ptr_id)
                {
                    if (!util_parse_longlong (ptr_id, 10, &id))
                        id = -1;
                }
                break;
            default:
                break;
        }
    }
    if (id < 0)
        id = gui_nicklist_generate_id (upgrade_current_buffer);

    if (strcmp (type, "group") == 0)
    {
        name = infolist_string (infolist, "name");
        if (name && (strcmp (name, "root") != 0))
        {
            group_name = infolist_string (infolist, "parent_name");
            if (group_name)
            {
                ptr_group = gui_nicklist_search_group (
                    upgrade_current_buffer, NULL, group_name);
            }
            gui_nicklist_add_group_with_id (
                upgrade_current_buffer,
                id,
                ptr_group,
                name,
                infolist_string (infolist, "color"),
                infolist_integer (infolist, "visible"));
        }
    }
    else if (strcmp (type, "nick") == 0)
    {
        group_name = infolist_string (infolist, "group_name");
        if (group_name)
        {
            ptr_group = gui_nicklist_search_group (
                upgrade_current_buffer, NULL, group_name);
        }
        gui_nicklist_add_nick_with_id (
            upgrade_current_buffer,
            id,
            ptr_group,
            infolist_string (infolist, "name"),
            infolist_string (infolist, "color"),
            infolist_string (infolist, "prefix"),
            infolist_string (infolist, "prefix_color"),
            infolist_integer (infolist, "visible"));
    }
}

/*
 * Read hotlist from infolist.
 */

void
upgrade_weechat_read_hotlist (struct t_infolist *infolist)
{
    const char *plugin_name, *buffer_name;
    char option_name[64];
    struct t_gui_buffer *ptr_buffer;
    struct t_gui_hotlist *new_hotlist;
    struct timeval creation_time;
    void *buf;
    int i, size;

    if (!hotlist_reset)
    {
        gui_hotlist_clear (GUI_HOTLIST_MASK_MAX);
        hotlist_reset = 1;
    }
    plugin_name = infolist_string (infolist, "plugin_name");
    buffer_name = infolist_string (infolist, "buffer_name");
    if (plugin_name && buffer_name)
    {
        ptr_buffer = gui_buffer_search (plugin_name, buffer_name);
        if (ptr_buffer)
        {
            buf = infolist_buffer (infolist, "creation_time", &size);
            if (buf)
            {
                memcpy (&creation_time, buf, size);
                new_hotlist = gui_hotlist_add (
                    ptr_buffer,
                    infolist_integer (infolist, "priority"),
                    &creation_time,
                    1);  /* check_conditions */
                if (new_hotlist)
                {
                    for (i = 0; i < GUI_HOTLIST_NUM_PRIORITIES; i++)
                    {
                        snprintf (option_name, sizeof (option_name),
                                  "count_%02d", i);
                        new_hotlist->count[i] = infolist_integer (infolist,
                                                                  option_name);
                    }
                }
            }
        }
    }
}

/*
 * Read WeeChat upgrade file.
 */

int
upgrade_weechat_read_cb (const void *pointer, void *data,
                         struct t_upgrade_file *upgrade_file,
                         int object_id,
                         struct t_infolist *infolist)
{
    /* Make C compiler happy. */
    (void) pointer;
    (void) data;
    (void) upgrade_file;

    gui_buffer_last_id_assigned = -1;

    infolist_reset_item_cursor (infolist);
    while (infolist_next (infolist))
    {
        switch (object_id)
        {
            case UPGRADE_WEECHAT_TYPE_HISTORY:
                if (upgrade_current_buffer)
                {
                    gui_history_buffer_add (upgrade_current_buffer,
                                            infolist_string (infolist, "text"));
                }
                else
                {
                    gui_history_global_add (infolist_string (infolist, "text"));
                }
                break;
            case UPGRADE_WEECHAT_TYPE_BUFFER:
                upgrade_weechat_read_buffer (infolist);
                break;
            case UPGRADE_WEECHAT_TYPE_BUFFER_LINE:
                upgrade_weechat_read_buffer_line (infolist);
                break;
            case UPGRADE_WEECHAT_TYPE_NICKLIST:
                upgrade_weechat_read_nicklist (infolist);
                break;
            case UPGRADE_WEECHAT_TYPE_MISC:
                weechat_first_start_time = infolist_time (infolist, "start_time");
                weechat_upgrade_count = infolist_integer (infolist, "upgrade_count");
                upgrade_set_current_window = infolist_integer (infolist, "current_window_number");
                break;
            case UPGRADE_WEECHAT_TYPE_HOTLIST:
                upgrade_weechat_read_hotlist (infolist);
                break;
            case UPGRADE_WEECHAT_TYPE_LAYOUT_WINDOW:
                gui_layout_window_add (&upgrade_layout->layout_windows,
                                       infolist_integer (infolist, "internal_id"),
                                       gui_layout_window_search_by_id (upgrade_layout->layout_windows,
                                                                       infolist_integer (infolist, "parent_id")),
                                       infolist_integer (infolist, "split_pct"),
                                       infolist_integer (infolist, "split_horiz"),
                                       infolist_string (infolist, "plugin_name"),
                                       infolist_string (infolist, "buffer_name"));
                break;
        }
    }

    return WEECHAT_RC_OK;
}

/*
 * Load WeeChat upgrade file.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
upgrade_weechat_load (void)
{
    int rc;
    struct t_upgrade_file *upgrade_file;

    upgrade_layout = gui_layout_alloc (GUI_LAYOUT_UPGRADE);

    upgrade_file = upgrade_file_new (WEECHAT_UPGRADE_FILENAME,
                                     &upgrade_weechat_read_cb, NULL, NULL);
    if (!upgrade_file)
        return 0;

    rc = upgrade_file_read (upgrade_file);

    upgrade_file_close (upgrade_file);

    if (!hotlist_reset)
        gui_hotlist_clear (GUI_HOTLIST_MASK_MAX);

    gui_color_buffer_assign ();
    gui_color_buffer_display ();

    gui_buffer_user_set_callbacks ();

    secure_buffer_assign ();
    secure_buffer_display ();

    if (upgrade_layout->layout_buffers)
        gui_layout_buffer_apply (upgrade_layout);
    if (upgrade_layout->layout_windows)
        gui_layout_window_apply (upgrade_layout, -1);

    gui_layout_free (upgrade_layout);
    upgrade_layout = NULL;

    if (upgrade_set_current_window > 0)
        gui_window_switch_by_number (upgrade_set_current_window);

    if (upgrade_set_current_buffer)
    {
        gui_window_switch_to_buffer (gui_current_window,
                                     upgrade_set_current_buffer, 0);
    }

    gui_layout_buffer_get_number_all (gui_layout_current);

    return rc;
}

/*
 * Remove a .upgrade file (callback called for each .upgrade file in WeeChat
 * home directory).
 */

void
upgrade_weechat_remove_file_cb (void *data, const char *filename)
{
    /* Make C compiler happy. */
    (void) data;

    if (string_match (filename, "*.upgrade", 1))
    {
        if (weechat_debug_core >= 2)
            gui_chat_printf (NULL, _("debug: removing file: %s"), filename);
        unlink (filename);
    }
}

/*
 * Remove *.upgrade files after upgrade and send signal "weechat_upgrade_done".
 */

void
upgrade_weechat_end (void)
{
    struct timeval tv_now;
    long long time_diff;

    /* Remove .upgrade files. */
    dir_exec_on_files (weechat_data_dir,
                       0, 0,
                       &upgrade_weechat_remove_file_cb, NULL);

    /* Display message for end of /upgrade with duration. */
    gettimeofday (&tv_now, NULL);
    time_diff = util_timeval_diff (&weechat_current_start_timeval, &tv_now);
    gui_chat_printf (NULL,
                     /* TRANSLATORS: %.02fs is a float number + "s" ("seconds"). */
                     _("Upgrade done (%.02fs)"),
                     ((float)time_diff) / 1000000);

    /* Upgrading has ended. */
    weechat_upgrading = 0;

    /* Send signal for end of /upgrade. */
    (void) hook_signal_send ("upgrade_ended", WEECHAT_HOOK_SIGNAL_STRING, NULL);
}
