/* $Id$ */
/*-
 * Copyright (c) 2005-206 Benedikt Meurer <benny@xfce.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gdk/gdk.h>

#include <thunarx/thunarx-private.h>
#include <thunarx/thunarx-provider-factory.h>
#include <thunarx/thunarx-provider-module.h>
#include <thunarx/thunarx-provider-plugin.h>
#include <thunarx/thunarx-alias.h>



/* "provider cache" cleanup interval (in ms) */
#define THUNARX_PROVIDER_FACTORY_INTERVAL (45 * 1000)



static void     thunarx_provider_factory_class_init     (ThunarxProviderFactoryClass *klass);
static void     thunarx_provider_factory_finalize       (GObject                     *object);
static void     thunarx_provider_factory_add            (ThunarxProviderFactory      *factory,
                                                         ThunarxProviderModule       *module);
static GList   *thunarx_provider_factory_load_modules   (ThunarxProviderFactory      *factory);
static gboolean thunarx_provider_factory_timer          (gpointer                     user_data);
static void     thunarx_provider_factory_timer_destroy  (gpointer                     user_data);



typedef struct
{
  GObject *provider;  /* cached provider reference or %NULL */
  GType    type;      /* provider GType */
} ThunarxProviderInfo;

struct _ThunarxProviderFactoryClass
{
  GObjectClass __parent__;
};

struct _ThunarxProviderFactory
{
  GObject __parent__;

  ThunarxProviderInfo *infos;     /* provider types and cached provider references */
  gint                 n_infos;   /* number of items in the infos array */

  gint                 timer_id;  /* GSource timer to cleanup cached providers */
};



static GObjectClass *thunarx_provider_factory_parent_class;
static GList        *thunarx_provider_modules = NULL;       /* list of active provider modules */



GType
thunarx_provider_factory_get_type (void)
{
  static GType type = G_TYPE_INVALID;

  if (G_UNLIKELY (type == G_TYPE_INVALID))
    {
      static const GTypeInfo info =
      {
        sizeof (ThunarxProviderFactoryClass),
        NULL,
        NULL,
        (GClassInitFunc) thunarx_provider_factory_class_init,
        NULL,
        NULL,
        sizeof (ThunarxProviderFactory),
        0,
        NULL,
        NULL,
      };

      type = g_type_register_static (G_TYPE_OBJECT, I_("ThunarxProviderFactory"), &info, 0);
    }

  return type;
}



static void
thunarx_provider_factory_class_init (ThunarxProviderFactoryClass *klass)
{
  GObjectClass *gobject_class;

  /* determine the parent type class */
  thunarx_provider_factory_parent_class = g_type_class_peek_parent (klass);

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->finalize = thunarx_provider_factory_finalize;
}



static void
thunarx_provider_factory_finalize (GObject *object)
{
  ThunarxProviderFactory *factory = THUNARX_PROVIDER_FACTORY (object);
  gint                    n;

  /* stop the "provider cache" cleanup timer */
  if (G_LIKELY (factory->timer_id > 0))
    g_source_remove (factory->timer_id);

  /* release provider infos */
  for (n = 0; n < factory->n_infos; ++n)
    if (factory->infos[n].provider != NULL)
      g_object_unref (factory->infos[n].provider);
  g_free (factory->infos);

  (*G_OBJECT_CLASS (thunarx_provider_factory_parent_class)->finalize) (object);
}



static void
thunarx_provider_factory_add (ThunarxProviderFactory *factory,
                              ThunarxProviderModule  *module)
{
  const GType *types;
  gint         n_types;

  /* determines the types provided by the module */
  thunarx_provider_module_list_types (module, &types, &n_types);

  /* add the types provided by the extension */
  factory->infos = g_renew (ThunarxProviderInfo, factory->infos, factory->n_infos + n_types);
  for (; n_types-- > 0; ++types)
    {
      factory->infos[factory->n_infos].provider = NULL;
      factory->infos[factory->n_infos].type = *types;
      ++factory->n_infos;
    }
}



static GList*
thunarx_provider_factory_load_modules (ThunarxProviderFactory *factory)
{
  ThunarxProviderModule *module;
  const gchar           *name;
  GList                 *modules = NULL;
  GList                 *lp;
  GDir                  *dp;

  dp = g_dir_open (THUNARX_DIRECTORY, 0, NULL);
  if (G_LIKELY (dp != NULL))
    {
      /* determine the types for all existing plugins */
      for (;;)
        {
          /* read the next entry from the directory */
          name = g_dir_read_name (dp);
          if (G_UNLIKELY (name == NULL))
            break;

          /* check if this is a valid plugin file */
          if (g_str_has_suffix (name, "." G_MODULE_SUFFIX))
            {
              /* check if we already have that module */
              for (lp = thunarx_provider_modules; lp != NULL; lp = lp->next)
                if (g_str_equal (G_TYPE_MODULE (lp->data)->name, name))
                  break;

              /* use or allocate a new module for the file */
              if (G_UNLIKELY (lp != NULL))
                {
                  /* just use the existing module */
                  module = THUNARX_PROVIDER_MODULE (lp->data);
                }
              else
                {
                  /* allocate the new module and add it to our list */
                  module = thunarx_provider_module_new (name);
                  thunarx_provider_modules = g_list_prepend (thunarx_provider_modules, module);
                }

              /* try to load the module */
              if (g_type_module_use (G_TYPE_MODULE (module)))
                {
                  /* add the types provided by the module */
                  thunarx_provider_factory_add (factory, module);

                  /* add the module to our list */
                  modules = g_list_prepend (modules, module);
                }
            }
        }

      g_dir_close (dp);
    }

  return modules;
}



static gboolean
thunarx_provider_factory_timer (gpointer user_data)
{
  ThunarxProviderFactory *factory = THUNARX_PROVIDER_FACTORY (user_data);
  ThunarxProviderInfo    *info;
  gint                    n;

  GDK_THREADS_ENTER ();

  /* drop all providers for which only we keep a reference */
  for (n = factory->n_infos; --n >= 0; )
    {
      info = factory->infos + n;
      if (info->provider != NULL && info->provider->ref_count == 1)
        {
          g_object_unref (info->provider);
          info->provider = NULL;
        }
    }

  GDK_THREADS_LEAVE ();

  return TRUE;
}



static void
thunarx_provider_factory_timer_destroy (gpointer user_data)
{
  THUNARX_PROVIDER_FACTORY (user_data)->timer_id = 0;
}



/**
 * thunarx_provider_factory_get_default:
 *
 * Returns a reference to the default #ThunarxProviderFactory
 * instance.
 *
 * The caller is responsible to free the returned object
 * using g_object_unref() when no longer needed.
 *
 * Return value: a reference to the default
 *               #ThunarxProviderFactory instance.
 **/
ThunarxProviderFactory*
thunarx_provider_factory_get_default (void)
{
  static ThunarxProviderFactory *factory = NULL;

  /* allocate the default factory instance on-demand */
  if (G_UNLIKELY (factory == NULL))
    {
      factory = g_object_new (THUNARX_TYPE_PROVIDER_FACTORY, NULL);
      g_object_add_weak_pointer (G_OBJECT (factory), (gpointer) &factory);
    }
  else
    {
      /* take a reference on the default factory for the caller */
      g_object_ref (G_OBJECT (factory));
    }

  return factory;
}



/**
 * thunarx_provider_factory_list_providers:
 * @factory : a #ThunarxProviderFactory instance.
 * @type    : the provider #GType.
 *
 * Returns all providers of the given @type.
 *
 * The caller is responsible to release the returned
 * list of providers using code like this:
 * <informalexample><programlisting>
 * g_list_foreach (list, (GFunc) g_object_unref, NULL);
 * g_list_free (list);
 * </programlisting></informalexample>
 *
 * Return value: the of providers for @type.
 **/
GList*
thunarx_provider_factory_list_providers (ThunarxProviderFactory *factory,
                                         GType                   type)
{
  ThunarxProviderInfo *info;
  GList               *providers = NULL;
  GList               *modules = NULL;
  GList               *lp;
  gint                 n;

  /* check if the cleanup timer is running (and thereby the factory is initialized) */
  if (G_UNLIKELY (factory->timer_id == 0))
    {
      /* load all available modules (and thereby initialize the factory) */
      modules = thunarx_provider_factory_load_modules (factory);

      /* start the "provider cache" cleanup timer */
      factory->timer_id = g_timeout_add_full (G_PRIORITY_LOW, THUNARX_PROVIDER_FACTORY_INTERVAL,
                                              thunarx_provider_factory_timer, factory,
                                              thunarx_provider_factory_timer_destroy);
    }

  /* determine all available providers for the type */
  for (info = factory->infos, n = factory->n_infos; --n >= 0; ++info)
    if (G_LIKELY (g_type_is_a (info->type, type)))
      {
        /* allocate the provider on-demand */
        if (G_UNLIKELY (info->provider == NULL))
          {
            info->provider = g_object_new (info->type, NULL);
            if (G_UNLIKELY (info->provider == NULL))
              continue;
          }

        /* take a reference for the caller */
        g_object_ref (info->provider);

        /* add the provider to the result list */
        providers = g_list_append (providers, info->provider);
      }

  /* check if we were initialized by this method invocation */
  if (G_UNLIKELY (modules != NULL))
    {
      /* unload all non-persistent modules */
      for (lp = modules; lp != NULL; lp = lp->next)
        if (!thunarx_provider_plugin_get_resident (lp->data))
          g_type_module_unuse (G_TYPE_MODULE (lp->data));
    }

  return providers;
}



#define __THUNARX_PROVIDER_FACTORY_C__
#include <thunarx/thunarx-aliasdef.c>
