/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
 *
 * gimppluginmanager-restore.c
 *
 * 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 3 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpconfig/gimpconfig.h"

#include "plug-in-types.h"

#include "config/gimpcoreconfig.h"

#include "core/gimp.h"
#include "core/gimp-utils.h"

#include "pdb/gimppdb.h"
#include "pdb/gimppdbcontext.h"

#include "gimpinterpreterdb.h"
#include "gimpplugindef.h"
#include "gimppluginmanager.h"
#define __YES_I_NEED_GIMP_PLUG_IN_MANAGER_CALL__
#include "gimppluginmanager-call.h"
#include "gimppluginmanager-file.h"
#include "gimppluginmanager-help-domain.h"
#include "gimppluginmanager-restore.h"
#include "gimppluginprocedure.h"
#include "plug-in-rc.h"

#include "gimp-intl.h"


static void    gimp_plug_in_manager_search            (GimpPlugInManager    *manager,
                                                       GimpInitStatusFunc    status_callback);
static void    gimp_plug_in_manager_search_directory  (GimpPlugInManager    *manager,
                                                       GFile                *directory);
static GFile * gimp_plug_in_manager_get_pluginrc      (GimpPlugInManager    *manager);
static void    gimp_plug_in_manager_read_pluginrc     (GimpPlugInManager    *manager,
                                                       GFile                *file,
                                                       GimpInitStatusFunc    status_callback);
static void    gimp_plug_in_manager_query_new         (GimpPlugInManager    *manager,
                                                       GimpContext          *context,
                                                       GimpInitStatusFunc    status_callback);
static void    gimp_plug_in_manager_init_plug_ins     (GimpPlugInManager    *manager,
                                                       GimpContext          *context,
                                                       GimpInitStatusFunc    status_callback);
static void    gimp_plug_in_manager_run_extensions    (GimpPlugInManager    *manager,
                                                       GimpContext          *context,
                                                       GimpInitStatusFunc    status_callback);
static void    gimp_plug_in_manager_add_from_file     (GimpPlugInManager    *manager,
                                                       GFile                *file,
                                                       guint64               mtime);
static void    gimp_plug_in_manager_add_from_rc       (GimpPlugInManager    *manager,
                                                       GimpPlugInDef        *plug_in_def);
static void    gimp_plug_in_manager_add_to_db         (GimpPlugInManager    *manager,
                                                       GimpContext          *context,
                                                       GimpPlugInProcedure  *proc);
static void    gimp_plug_in_manager_sort_file_procs   (GimpPlugInManager    *manager);
static gint    gimp_plug_in_manager_file_proc_compare (gconstpointer         a,
                                                       gconstpointer         b,
                                                       gpointer              data);



void
gimp_plug_in_manager_restore (GimpPlugInManager  *manager,
                              GimpContext        *context,
                              GimpInitStatusFunc  status_callback)
{
  Gimp   *gimp;
  GFile  *pluginrc;
  GSList *list;
  GError *error = NULL;

  g_return_if_fail (GIMP_IS_PLUG_IN_MANAGER (manager));
  g_return_if_fail (GIMP_IS_CONTEXT (context));
  g_return_if_fail (status_callback != NULL);

  gimp = manager->gimp;

  /* need a GimpPDBContext for calling gimp_plug_in_manager_run_foo() */
  context = gimp_pdb_context_new (gimp, context, TRUE);

  /* search for binaries in the plug-in directory path */
  gimp_plug_in_manager_search (manager, status_callback);

  /* read the pluginrc file for cached data */
  pluginrc = gimp_plug_in_manager_get_pluginrc (manager);

  gimp_plug_in_manager_read_pluginrc (manager, pluginrc, status_callback);

  /* query any plug-ins that changed since we last wrote out pluginrc */
  gimp_plug_in_manager_query_new (manager, context, status_callback);

  /* initialize the plug-ins */
  gimp_plug_in_manager_init_plug_ins (manager, context, status_callback);

  /* add the procedures to manager->plug_in_procedures */
  for (list = manager->plug_in_defs; list; list = list->next)
    {
      GimpPlugInDef *plug_in_def = list->data;
      GSList        *list2;

      for (list2 = plug_in_def->procedures; list2; list2 = list2->next)
        {
          gimp_plug_in_manager_add_procedure (manager, list2->data);
        }
    }

  /* write the pluginrc file if necessary */
  if (manager->write_pluginrc)
    {
      if (gimp->be_verbose)
        g_print ("Writing '%s'\n", gimp_file_get_utf8_name (pluginrc));

      if (! plug_in_rc_write (manager->plug_in_defs, pluginrc, &error))
        {
          gimp_message_literal (gimp,
                                NULL, GIMP_MESSAGE_ERROR, error->message);
          g_clear_error (&error);
        }

      manager->write_pluginrc = FALSE;
    }

  g_object_unref (pluginrc);

  /* create help domain lists */
  for (list = manager->plug_in_defs; list; list = list->next)
    {
      GimpPlugInDef *plug_in_def = list->data;

      if (plug_in_def->help_domain_name)
        gimp_plug_in_manager_add_help_domain (manager,
                                              plug_in_def->file,
                                              plug_in_def->help_domain_name,
                                              plug_in_def->help_domain_uri);
    }

  /* we're done with the plug-in-defs */
  g_slist_free_full (manager->plug_in_defs, (GDestroyNotify) g_object_unref);
  manager->plug_in_defs = NULL;

  /* add the plug-in procs to the procedure database */
  for (list = manager->plug_in_procedures; list; list = list->next)
    {
      gimp_plug_in_manager_add_to_db (manager, context, list->data);
    }

  /* sort the load, save and export procedures, make the raw handler list */
  gimp_plug_in_manager_sort_file_procs (manager);

  gimp_plug_in_manager_run_extensions (manager, context, status_callback);

  g_object_unref (context);
}


/* search for binaries in the plug-in directory path */
static void
gimp_plug_in_manager_search (GimpPlugInManager  *manager,
                             GimpInitStatusFunc  status_callback)
{
  const gchar *path_str;
  GList       *path;
  GList       *list;

#ifdef G_OS_WIN32
  const gchar *pathext = g_getenv ("PATHEXT");

  /*  On Windows, we need to add the known file extensions in PATHEXT. */
  if (pathext)
    {
      gchar *exts;

      exts = gimp_interpreter_db_get_extensions (manager->interpreter_db);

      if (exts)
        {
          gchar *value;

          value = g_strconcat (pathext, G_SEARCHPATH_SEPARATOR_S, exts, NULL);

          g_setenv ("PATHEXT", value, TRUE);

          g_free (value);
          g_free (exts);
        }
    }
#endif /* G_OS_WIN32 */

  status_callback (_("Loading extension plug-ins"), "", 0.0);
  g_object_get (manager->gimp->extension_manager,
                "plug-in-paths", &path,
                NULL);
  for (list = path; list; list = g_list_next (list))
    {
      if (gimp_file_is_executable (list->data))
        {
          guint64 mtime;
          GFileInfo *info;

          info = g_file_query_info (list->data, G_FILE_ATTRIBUTE_TIME_MODIFIED,
                                    G_FILE_QUERY_INFO_NONE, NULL, NULL);
          mtime = g_file_info_get_attribute_uint64 (info,
                                                    G_FILE_ATTRIBUTE_TIME_MODIFIED);
          gimp_plug_in_manager_add_from_file (manager, list->data, mtime);
          g_object_unref (info);
        }
    }

  status_callback (_("Searching plug-ins"), "", 0.0);

  /* Give automatic tests a chance to use plug-ins from the build
   * dir
   */
  path_str = g_getenv ("GIMP_TESTING_PLUGINDIRS");
  if (! path_str)
    path_str = manager->gimp->config->plug_in_path;

  path = gimp_config_path_expand_to_files (path_str, NULL);

  for (list = path; list; list = g_list_next (list))
    {
      gimp_plug_in_manager_search_directory (manager, list->data);
    }

  g_list_free_full (path, (GDestroyNotify) g_object_unref);
}

static void
gimp_plug_in_manager_search_directory (GimpPlugInManager *manager,
                                       GFile             *directory)
{
  GFileEnumerator *enumerator;

  enumerator = g_file_enumerate_children (directory,
                                          G_FILE_ATTRIBUTE_STANDARD_NAME ","
                                          G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
                                          G_FILE_ATTRIBUTE_TIME_MODIFIED,
                                          G_FILE_QUERY_INFO_NONE,
                                          NULL, NULL);
  if (enumerator)
    {
      GFileInfo *info;

      while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)))
        {
          GFile *child;

          if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN))
            {
              g_object_unref (info);
              continue;
            }

          child = g_file_enumerator_get_child (enumerator, info);

          if (g_file_query_file_type (child,
                                      G_FILE_QUERY_INFO_NONE,
                                      NULL) == G_FILE_TYPE_DIRECTORY)
            {
              /* Search in subdirectory the first executable file with
               * the same name as the directory (except extension).
               * We don't search recursively, but only at a single
               * level and assume that there can be only 1 plugin
               * inside a directory.
               */
              GFileEnumerator *enumerator2;

              enumerator2 = g_file_enumerate_children (child,
                                                       G_FILE_ATTRIBUTE_STANDARD_NAME ","
                                                       G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
                                                       G_FILE_ATTRIBUTE_TIME_MODIFIED,
                                                       G_FILE_QUERY_INFO_NONE,
                                                       NULL, NULL);
              if (enumerator2)
                {
                  GFileInfo *info2;

                  while ((info2 = g_file_enumerator_next_file (enumerator2, NULL, NULL)))
                    {
                      GFile *child2;
                      gchar *file_name;
                      char  *ext;

                      if (g_file_info_get_attribute_boolean (info2, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN))
                        {
                          g_object_unref (info2);
                          continue;
                        }

                      child2 = g_file_enumerator_get_child (enumerator2, info2);
                      file_name = g_strdup (g_file_info_get_name (info2));

                      ext = strrchr (file_name, '.');
                      if (ext)
                        *ext = '\0';

                      if (g_strcmp0 (file_name, g_file_info_get_name (info)) == 0 &&
                          gimp_file_is_executable (child2))
                        {
                          guint64 mtime;

                          mtime = g_file_info_get_attribute_uint64 (info2,
                                                                    G_FILE_ATTRIBUTE_TIME_MODIFIED);

                          gimp_plug_in_manager_add_from_file (manager, child2, mtime);

                          g_free (file_name);
                          g_object_unref (child2);
                          g_object_unref (info2);
                          break;
                        }

                      g_free (file_name);
                      g_object_unref (child2);
                      g_object_unref (info2);
                    }

                  g_object_unref (enumerator2);
                }
            }
          else if (gimp_file_is_executable (child))
            {
              if (g_getenv ("GIMP_TESTING_PLUGINDIRS"))
                {
                  guint64 mtime;

                  mtime = g_file_info_get_attribute_uint64 (info,
                                                            G_FILE_ATTRIBUTE_TIME_MODIFIED);

                  gimp_plug_in_manager_add_from_file (manager, child, mtime);
                }
              else
                {
                  g_printerr (_("Skipping potential plug-in '%s': "
                                "plug-ins must be installed in subdirectories.\n"),
                              g_file_peek_path (child));
                }
            }
          else
            {
              g_printerr (_("Skipping unknown file '%s' in plug-in directory.\n"),
                          g_file_peek_path (child));
            }

          g_object_unref (child);
          g_object_unref (info);
        }

      g_object_unref (enumerator);
    }
}

static GFile *
gimp_plug_in_manager_get_pluginrc (GimpPlugInManager *manager)
{
  Gimp  *gimp = manager->gimp;
  GFile *pluginrc;

  if (gimp->config->plug_in_rc_path)
    {
      gchar *path = gimp_config_path_expand (gimp->config->plug_in_rc_path,
                                             TRUE, NULL);

      if (g_path_is_absolute (path))
        pluginrc = g_file_new_for_path (path);
      else
        pluginrc = gimp_directory_file (path, NULL);

      g_free (path);
    }
  else
    {
      pluginrc = gimp_directory_file ("pluginrc", NULL);
    }

  return pluginrc;
}

/* read the pluginrc file for cached data */
static void
gimp_plug_in_manager_read_pluginrc (GimpPlugInManager  *manager,
                                    GFile              *pluginrc,
                                    GimpInitStatusFunc  status_callback)
{
  GSList *rc_defs;
  GError *error = NULL;

  status_callback (_("Resource configuration"),
                   gimp_file_get_utf8_name (pluginrc), 0.0);

  if (manager->gimp->be_verbose)
    g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (pluginrc));

  rc_defs = plug_in_rc_parse (manager->gimp, pluginrc, &error);

  if (rc_defs)
    {
      GSList *list;

      for (list = rc_defs; list; list = g_slist_next (list))
        gimp_plug_in_manager_add_from_rc (manager, list->data); /* consumes list->data */

      g_slist_free (rc_defs);
    }
  else if (error)
    {
      if (error->code != GIMP_CONFIG_ERROR_OPEN_ENOENT)
        gimp_message_literal (manager->gimp, NULL, GIMP_MESSAGE_ERROR,
                              error->message);

      g_clear_error (&error);
    }
}

/* query any plug-ins that changed since we last wrote out pluginrc */
static void
gimp_plug_in_manager_query_new (GimpPlugInManager  *manager,
                                GimpContext        *context,
                                GimpInitStatusFunc  status_callback)
{
  GSList *list;
  gint    n_plugins;

  status_callback (_("Querying new Plug-ins"), "", 0.0);

  for (list = manager->plug_in_defs, n_plugins = 0; list; list = list->next)
    {
      GimpPlugInDef *plug_in_def = list->data;

      if (manager->gimp->query_all)
        gimp_plug_in_def_set_needs_query (plug_in_def, TRUE);

      if (plug_in_def->needs_query)
        n_plugins++;
    }

  if (n_plugins)
    {
      gint nth;

      manager->write_pluginrc = TRUE;

      for (list = manager->plug_in_defs, nth = 0; list; list = list->next)
        {
          GimpPlugInDef *plug_in_def = list->data;

          if (plug_in_def->needs_query)
            {
              gchar *basename;

              basename =
                g_path_get_basename (gimp_file_get_utf8_name (plug_in_def->file));
              status_callback (NULL, basename,
                               (gdouble) nth++ / (gdouble) n_plugins);
              g_free (basename);

              if (manager->gimp->be_verbose)
                g_print ("Querying plug-in: '%s'\n",
                         gimp_file_get_utf8_name (plug_in_def->file));

              gimp_plug_in_manager_call_query (manager, context, plug_in_def);
            }
        }
    }

  status_callback (NULL, "", 1.0);
}

/* initialize the plug-ins */
static void
gimp_plug_in_manager_init_plug_ins (GimpPlugInManager  *manager,
                                    GimpContext        *context,
                                    GimpInitStatusFunc  status_callback)
{
  GSList *list;
  gint    n_plugins;

  status_callback (_("Initializing Plug-ins"), "", 0.0);

  for (list = manager->plug_in_defs, n_plugins = 0; list; list = list->next)
    {
      GimpPlugInDef *plug_in_def = list->data;

      if (plug_in_def->has_init)
        n_plugins++;
    }

  if (n_plugins)
    {
      gint nth;

      for (list = manager->plug_in_defs, nth = 0; list; list = list->next)
        {
          GimpPlugInDef *plug_in_def = list->data;

          if (plug_in_def->has_init)
            {
              gchar *basename;

              basename =
                g_path_get_basename (gimp_file_get_utf8_name (plug_in_def->file));
              status_callback (NULL, basename,
                               (gdouble) nth++ / (gdouble) n_plugins);
              g_free (basename);

              if (manager->gimp->be_verbose)
                g_print ("Initializing plug-in: '%s'\n",
                         gimp_file_get_utf8_name (plug_in_def->file));

              gimp_plug_in_manager_call_init (manager, context, plug_in_def);
            }
        }
    }

  status_callback (NULL, "", 1.0);
}

/* run automatically started extensions */
static void
gimp_plug_in_manager_run_extensions (GimpPlugInManager  *manager,
                                     GimpContext        *context,
                                     GimpInitStatusFunc  status_callback)
{
  Gimp   *gimp = manager->gimp;
  GSList *list;
  GList  *extensions = NULL;
  gint    n_extensions;

  /* build list of automatically started extensions */
  for (list = manager->plug_in_procedures; list; list = list->next)
    {
      GimpPlugInProcedure *proc = list->data;

      if (proc->file                                                       &&
          GIMP_PROCEDURE (proc)->proc_type == GIMP_PDB_PROC_TYPE_PERSISTENT &&
          GIMP_PROCEDURE (proc)->num_args  == 0)
        {
          extensions = g_list_prepend (extensions, proc);
        }
    }

  extensions   = g_list_reverse (extensions);
  n_extensions = g_list_length (extensions);

  /* run the available extensions */
  if (extensions)
    {
      GList *list;
      gint   nth;

      status_callback (_("Starting Extensions"), "", 0.0);

      for (list = extensions, nth = 0; list; list = g_list_next (list), nth++)
        {
          GimpPlugInProcedure *proc = list->data;
          GimpValueArray      *args;
          GError              *error = NULL;

          if (gimp->be_verbose)
            g_print ("Starting extension: '%s'\n", gimp_object_get_name (proc));

          status_callback (NULL, gimp_object_get_name (proc),
                           (gdouble) nth / (gdouble) n_extensions);

          args = gimp_value_array_new (0);

          gimp_procedure_execute_async (GIMP_PROCEDURE (proc),
                                        gimp, context, NULL,
                                        args, NULL, &error);

          gimp_value_array_unref (args);

          if (error)
            {
              gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR,
                                    error->message);
              g_clear_error (&error);
            }
        }

      g_list_free (extensions);

      status_callback (NULL, "", 1.0);
    }
}

/**
 * gimp_plug_in_manager_ignore_plugin_basename:
 * @basename: Basename to test with
 *
 * Checks the environment variable
 * GIMP_TESTING_PLUGINDIRS_BASENAME_IGNORES for file basenames.
 *
 * Returns: %TRUE if @basename was in GIMP_TESTING_PLUGINDIRS_BASENAME_IGNORES
 **/
static gboolean
gimp_plug_in_manager_ignore_plugin_basename (const gchar *plugin_basename)
{
  const gchar *ignore_basenames_string;
  GList       *ignore_basenames;
  GList       *iter;
  gboolean     ignore = FALSE;

  ignore_basenames_string = g_getenv ("GIMP_TESTING_PLUGINDIRS_BASENAME_IGNORES");
  ignore_basenames        = gimp_path_parse (ignore_basenames_string,
                                             256 /*max_paths*/,
                                             FALSE /*check*/,
                                             NULL /*check_failed*/);

  for (iter = ignore_basenames; iter; iter = g_list_next (iter))
    {
      const gchar *ignore_basename = iter->data;

      if (g_ascii_strcasecmp (ignore_basename, plugin_basename) == 0)
        {
          ignore = TRUE;
          break;
        }
    }

  gimp_path_free (ignore_basenames);

  return ignore;
}

static void
gimp_plug_in_manager_add_from_file (GimpPlugInManager *manager,
                                    GFile             *file,
                                    guint64            mtime)
{
  GimpPlugInDef *plug_in_def;
  GSList        *list;
  gchar         *filename;
  gchar         *basename;

  filename = g_file_get_path (file);
  basename = g_path_get_basename (filename);
  g_free (filename);

  /* When we scan build dirs for plug-ins, there might be some
   * executable files that are not plug-ins that we want to ignore,
   * for example back with autotools, there used to be a file
   * plug-ins/common/mkgen.pl if GIMP_TESTING_PLUGINDIRS=plug-ins/common
   * Such files should be listed in GIMP_TESTING_PLUGINDIRS_BASENAME_IGNORES
   */
  if (gimp_plug_in_manager_ignore_plugin_basename (basename))
    {
      g_free (basename);
      return;
    }

  for (list = manager->plug_in_defs; list; list = list->next)
    {
      gchar *path;
      gchar *plug_in_name;

      plug_in_def = list->data;

      path = g_file_get_path (plug_in_def->file);
      plug_in_name = g_path_get_basename (path);
      g_free (path);

      if (g_ascii_strcasecmp (basename, plug_in_name) == 0)
        {
          g_printerr ("Skipping duplicate plug-in: '%s'\n",
                      gimp_file_get_utf8_name (file));

          g_free (plug_in_name);
          g_free (basename);

          return;
        }

      g_free (plug_in_name);
    }

  g_free (basename);

  plug_in_def = gimp_plug_in_def_new (file);

  gimp_plug_in_def_set_mtime (plug_in_def, mtime);
  gimp_plug_in_def_set_needs_query (plug_in_def, TRUE);

  manager->plug_in_defs = g_slist_prepend (manager->plug_in_defs, plug_in_def);
}

static void
gimp_plug_in_manager_add_from_rc (GimpPlugInManager *manager,
                                  GimpPlugInDef     *plug_in_def)
{
  GSList *list;
  gchar  *path1;
  gchar  *basename1;

  g_return_if_fail (GIMP_IS_PLUG_IN_MANAGER (manager));
  g_return_if_fail (plug_in_def != NULL);
  g_return_if_fail (plug_in_def->file != NULL);

  path1 = g_file_get_path (plug_in_def->file);

  if (! g_path_is_absolute (path1))
    {
      g_warning ("plug_ins_def_add_from_rc: filename not absolute (skipping)");
      g_object_unref (plug_in_def);
      g_free (path1);
      return;
    }

  basename1 = g_path_get_basename (path1);

  /*  Check if the entry mentioned in pluginrc matches an executable
   *  found in the plug_in_path.
   */
  for (list = manager->plug_in_defs; list; list = list->next)
    {
      GimpPlugInDef *ondisk_plug_in_def = list->data;
      gchar         *path2;
      gchar         *basename2;

      path2 = g_file_get_path (ondisk_plug_in_def->file);

      basename2 = g_path_get_basename (path2);

      g_free (path2);

      if (! strcmp (basename1, basename2))
        {
          if (g_file_equal (plug_in_def->file,
                            ondisk_plug_in_def->file) &&
              (plug_in_def->mtime == ondisk_plug_in_def->mtime))
            {
              /* Use pluginrc entry, deleting on-disk entry */
              list->data = plug_in_def;
              g_object_unref (ondisk_plug_in_def);
            }
          else
            {
              /* Use on-disk entry, deleting pluginrc entry */
              g_object_unref (plug_in_def);
            }

          g_free (basename2);
          g_free (basename1);
          g_free (path1);

          return;
        }

      g_free (basename2);
    }

  g_free (basename1);
  g_free (path1);

  manager->write_pluginrc = TRUE;

  if (manager->gimp->be_verbose)
    {
      g_printerr ("pluginrc lists '%s', but it wasn't found\n",
                  gimp_file_get_utf8_name (plug_in_def->file));
    }

  g_object_unref (plug_in_def);
}


static void
gimp_plug_in_manager_add_to_db (GimpPlugInManager   *manager,
                                GimpContext         *context,
                                GimpPlugInProcedure *proc)
{
  gimp_pdb_register_procedure (manager->gimp->pdb, GIMP_PROCEDURE (proc));

  if (proc->file_proc)
    {
      if (proc->image_types)
        {
          gimp_plug_in_procedure_set_file_proc (proc,
                                                proc->extensions,
                                                proc->meta_extensions,
                                                proc->prefixes,
                                                NULL);

          gimp_plug_in_manager_add_save_procedure (manager, proc);
        }
      else
        {
          gimp_plug_in_procedure_set_file_proc (proc,
                                                proc->extensions,
                                                proc->meta_extensions,
                                                proc->prefixes,
                                                proc->magics);

          gimp_plug_in_manager_add_load_procedure (manager, proc);
        }
    }
  else if (proc->batch_interpreter)
    {
      gimp_plug_in_manager_add_batch_procedure (manager, proc);
    }
}

static void
gimp_plug_in_manager_sort_file_procs (GimpPlugInManager *manager)
{
  GimpCoreConfig *config         = manager->gimp->config;
  GFile          *config_plug_in = NULL;
  GFile          *raw_plug_in    = NULL;
  GSList         *list;

  manager->load_procs =
    g_slist_sort_with_data (manager->load_procs,
                            gimp_plug_in_manager_file_proc_compare,
                            GINT_TO_POINTER (FALSE));
  manager->save_procs =
    g_slist_sort_with_data (manager->save_procs,
                            gimp_plug_in_manager_file_proc_compare,
                            GINT_TO_POINTER (FALSE));
  manager->export_procs =
    g_slist_sort_with_data (manager->export_procs,
                            gimp_plug_in_manager_file_proc_compare,
                            GINT_TO_POINTER (FALSE));

  g_clear_pointer (&manager->display_load_procs,   g_slist_free);
  g_clear_pointer (&manager->display_save_procs,   g_slist_free);
  g_clear_pointer (&manager->display_export_procs, g_slist_free);

  manager->display_load_procs   = g_slist_copy (manager->load_procs);
  manager->display_save_procs   = g_slist_copy (manager->save_procs);
  manager->display_export_procs = g_slist_copy (manager->export_procs);

  manager->display_load_procs =
    g_slist_sort_with_data (manager->display_load_procs,
                            gimp_plug_in_manager_file_proc_compare,
                            GINT_TO_POINTER (TRUE));
  manager->display_save_procs =
    g_slist_sort_with_data (manager->display_save_procs,
                            gimp_plug_in_manager_file_proc_compare,
                            GINT_TO_POINTER (TRUE));
  manager->display_export_procs =
    g_slist_sort_with_data (manager->display_export_procs,
                            gimp_plug_in_manager_file_proc_compare,
                            GINT_TO_POINTER (TRUE));

  g_clear_pointer (&manager->raw_load_procs,         g_slist_free);
  g_clear_pointer (&manager->display_raw_load_procs, g_slist_free);

  if (config->import_raw_plug_in)
    {
      /* remember the configured raw loader, unless it's the placeholder */
      if (! strstr (config->import_raw_plug_in, "file-raw-placeholder"))
        config_plug_in =
          gimp_file_new_for_config_path (config->import_raw_plug_in,
                                         NULL);
    }

  /* make the list of raw loaders, and remember the one configured in
   * config if found
   */
  for (list = manager->load_procs; list; list = g_slist_next (list))
    {
      GimpPlugInProcedure *file_proc = list->data;

      if (file_proc->handles_raw)
        {
          GFile *file;

          manager->raw_load_procs = g_slist_prepend (manager->raw_load_procs,
                                                     file_proc);

          file = gimp_plug_in_procedure_get_file (file_proc);

          if (! raw_plug_in  &&
              config_plug_in &&
              g_file_equal (config_plug_in, file))
            {
              raw_plug_in = file;
            }
        }
    }

  manager->raw_load_procs         = g_slist_reverse (manager->raw_load_procs);
  manager->display_raw_load_procs = g_slist_copy (manager->raw_load_procs);
  manager->display_raw_load_procs =
    g_slist_sort_with_data (manager->display_raw_load_procs,
                            gimp_plug_in_manager_file_proc_compare,
                            GINT_TO_POINTER (TRUE));

  if (config_plug_in)
    g_object_unref (config_plug_in);

  /* if no raw loader was configured, or the configured raw loader
   * wasn't found, default to the first loader that is not the
   * placeholder, if any
   */
  if (! raw_plug_in && manager->raw_load_procs)
    {
      gchar *path;

      for (list = manager->raw_load_procs; list; list = g_slist_next (list))
        {
          GimpPlugInProcedure *file_proc = list->data;

          raw_plug_in = gimp_plug_in_procedure_get_file (file_proc);

          path = gimp_file_get_config_path (raw_plug_in, NULL);

          if (! strstr (path, "file-raw-placeholder"))
            break;

          g_free (path);
          path = NULL;

          raw_plug_in = NULL;
        }

      if (! raw_plug_in)
        {
          raw_plug_in =
            gimp_plug_in_procedure_get_file (manager->raw_load_procs->data);

          path = gimp_file_get_config_path (raw_plug_in, NULL);
        }

      g_object_set (config,
                    "import-raw-plug-in", path,
                    NULL);

      g_free (path);
    }

  list = manager->load_procs;
  while (list)
    {
      GimpPlugInProcedure *file_proc = list->data;

      list = g_slist_next (list);

      /* finally, remove all raw loaders except the configured one from
       * the list of load_procs
       */
      if (file_proc->handles_raw &&
          ! g_file_equal (gimp_plug_in_procedure_get_file (file_proc),
                          raw_plug_in))
        {
          manager->load_procs =
            g_slist_remove (manager->load_procs, file_proc);
          manager->display_load_procs =
            g_slist_remove (manager->display_load_procs, file_proc);
        }
      /* Remove generic (non-image) loaders from the display loader
       * list. */
     if (file_proc->generic_file_proc)
          manager->display_load_procs =
            g_slist_remove (manager->display_load_procs, file_proc);
    }
}

static gint
gimp_plug_in_manager_file_proc_compare (gconstpointer a,
                                        gconstpointer b,
                                        gpointer      data)
{
  GimpPlugInProcedure *proc_a  = GIMP_PLUG_IN_PROCEDURE (a);
  GimpPlugInProcedure *proc_b  = GIMP_PLUG_IN_PROCEDURE (b);
  gboolean             display = GPOINTER_TO_INT (data);
  const gchar         *label_a;
  const gchar         *label_b;

  if (g_str_has_prefix (gimp_file_get_utf8_name (proc_a->file),
                                                 "gimp-xcf"))
    {
      if (! g_str_has_prefix (gimp_file_get_utf8_name (proc_b->file),
                              "gimp-xcf"))
        {
          return -1;
        }
    }
  else if (g_str_has_prefix (gimp_file_get_utf8_name (proc_b->file),
                             "gimp-xcf"))
    {
      return 1;
    }

  if (! display && proc_a->priority != proc_b->priority)
    return proc_a->priority - proc_b->priority;

  label_a = gimp_procedure_get_label (GIMP_PROCEDURE (proc_a));
  label_b = gimp_procedure_get_label (GIMP_PROCEDURE (proc_b));

  if (label_a)
    {
      if (label_b)
        {
          gint comp = g_utf8_collate (label_a, label_b);

          if (comp)
            return comp;
        }
      else
        {
          return -1;
        }
    }
  else if (label_b)
    {
      return 1;
    }

  return strcmp (gimp_object_get_name (proc_a), gimp_object_get_name (proc_b));
}
