/*

Copyright (C) 2000 - 2006 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <dirent.h>

#include <libnd_misc.h>
#include <libnd_prefs.h>
#include <libnd_protocol.h>
#include <libnd_protocol_registry.h>
#include <libnd_protocol_plugin.h>

static GList *proto_plugins = NULL;  /* list<LND_ProtoPlugin*> */

static LND_ProtoPlugin *proto_plugin_new(char *filename);
static void       proto_plugin_scan(const char *dirname);
static gboolean   proto_plugin_hook_up(LND_ProtoPlugin *p);
static void       proto_plugin_load(void);
static int        proto_plugin_cmp(LND_ProtoPlugin *p1, char *filename);

static const char      *
proto_plugin_dummy_name(void)
{
  return "Unnamed Plugin.";
}


static const char      *
proto_plugin_dummy_description(void)
{
  return "No description available.";
}


static const char      *
proto_plugin_dummy_author(void)
{
  return "No author(s) specified.";
}

static const char      *
proto_plugin_dummy_version(void)
{
  return "No version specified.";
}


static int
proto_plugin_dummy_op(void)
{
  return 0;
}

static LND_Protocol *
proto_plugin_dummy_init(void)
{
  return NULL;
}


static LND_ProtoPlugin *
proto_plugin_new(char *filename)
{
  LND_ProtoPlugin *plugin;

  plugin = (LND_ProtoPlugin*) g_new0(LND_ProtoPlugin, 1);
  D_ASSERT_PTR(plugin);
  if (!plugin)
    return NULL;

  if (filename)
    plugin->filename = g_strdup(filename);

  plugin->name        = proto_plugin_dummy_name;
  plugin->description = proto_plugin_dummy_description;
  plugin->author      = proto_plugin_dummy_author;
  plugin->version     = proto_plugin_dummy_version;
  plugin->init        = proto_plugin_dummy_init;
  plugin->cleanup     = proto_plugin_dummy_op;

  return (plugin);
}


static void
proto_plugin_free(LND_ProtoPlugin *plugin)
{
  if (!plugin)
    return;

  lt_dlclose(plugin->lt);
  g_free(plugin->filename);
  g_free(plugin);
}


static int    
proto_plugin_cmp(LND_ProtoPlugin *p1, char *filename)
{
  if (!p1 || !filename)
    return (-1);

  return (strcmp(p1->filename, filename));
}


static void
proto_plugin_scan(const char *dir)
{
  DIR             *dirp;
  struct dirent   *de;
  char            *dot;
  struct stat      st;
  char             path[MAXPATHLEN];

  D(("Scanning '%s' for protocols\n", dir));

  if (! (dirp = opendir(dir)))
    return;

  for (de = readdir(dirp); de; de = readdir(dirp))
    {
      g_snprintf(path, MAXPATHLEN, "%s/%s", dir, de->d_name);
      
      if (stat(path, &st) != 0)
	{
	  D(("Could not stat %s\n", path));
	  continue;
	}

      if (! S_ISREG(st.st_mode))
	{
	  D(("Skipping %s, not a regular file.\n", path));
	  continue;
	}

      /* Chop off any file extension, if found. libltdl takes
       * care of that.
       */
      if ( (dot = strrchr(path, '.')))
	*dot = '\0';

      if (!g_list_find_custom(proto_plugins, path,
			      (GCompareFunc)proto_plugin_cmp))
	{
	  proto_plugins = g_list_append(proto_plugins,
					proto_plugin_new(path));
	}
    }

  closedir(dirp);
}


static gboolean
proto_plugin_hook_up(LND_ProtoPlugin *plugin)
{
  LND_Protocol *proto;
  lt_ptr_t     sym;

  if ((sym = lt_dlsym(plugin->lt, "name")) != NULL)
    plugin->name = sym;
  if ((sym = lt_dlsym(plugin->lt, "description")) != NULL)
    plugin->description = sym;
  if ((sym = lt_dlsym(plugin->lt, "author")) != NULL)
    plugin->author = sym;
  if ((sym = lt_dlsym(plugin->lt, "version")) != NULL)
    plugin->version = sym;

  if ((sym = lt_dlsym(plugin->lt, "init")) != NULL)
    plugin->init = sym;
  if ((sym = lt_dlsym(plugin->lt, "cleanup")) != NULL)
    plugin->cleanup = sym;

  /* Call the init routine the plugin provides. If this
   * routine does not return a protocol, the plugin is broken
   * and we abort.
   */
  if (! (proto = plugin->init()))
    return FALSE;
  
  /* register the plugin: */
  proto->plugin = plugin;
  libnd_proto_registry_register(proto);
  
  return TRUE;
}


static gint
proto_plugin_strcmp(gconstpointer a, gconstpointer b)
{
  LND_ProtoPlugin *p1 = (LND_ProtoPlugin *) a;
  LND_ProtoPlugin *p2 = (LND_ProtoPlugin *) b;

  return strcmp(p1->name(), p2->name());
}

static void       
proto_plugin_load(void)
{
  GList             *l, *l2;
  LND_ProtoPlugin   *plugin;

  for (l = g_list_first(proto_plugins); l; l = g_list_next(l))
    {
      plugin = (LND_ProtoPlugin *) l->data;

      if (! (plugin->lt = lt_dlopenext(plugin->filename)) ||
	  ! proto_plugin_hook_up(plugin))
	{
	  l->data = NULL;
	  proto_plugin_free(plugin);
	}
    }
  
  /* We may have deleted some plugins above that failed to initialize.
   * Clear out those list items in a separate iteration.
   */
  for (l = g_list_first(proto_plugins); l; )
    {
      if (! l->data)
	{
	  l2 = l;
	  l = g_list_next(l);
	  proto_plugins = g_list_remove_link(proto_plugins, l2);
	  g_list_free_1(l2);
	}
      else
	{
	  l = g_list_next(l);
	}
    }

  /* Now sort the list of plugins alphanumerically,
   * so that we automatically get sorted menus etc.
   */
  proto_plugins = g_list_sort(proto_plugins, proto_plugin_strcmp);
}


void 
libnd_proto_plugin_init(void)
{
  if (lt_dlinit() != 0)
    {
      fprintf(stderr, "Could not initialize libltdl -- exiting.\n");
      exit(-1);
    }

  proto_plugin_scan(libnd_prefs_get_proto_dir_user());
  proto_plugin_scan(libnd_prefs_get_proto_dir_global());
  proto_plugin_load();
}


const char   *
libnd_proto_plugin_get_description(LND_ProtoPlugin *plugin)
{
  if (!plugin)
    return NULL;

  return plugin->description();
}


const char   *
libnd_proto_plugin_get_author(LND_ProtoPlugin *plugin)
{
  if (!plugin)
    return NULL;

  return plugin->author();
}

const char   *
libnd_proto_plugin_get_version(LND_ProtoPlugin *plugin)
{
  if (!plugin)
    return NULL;

  return plugin->version();
}

const char   *
libnd_proto_plugin_get_name(LND_ProtoPlugin *plugin)
{
  if (!plugin)
    return NULL;

  return plugin->name();
}

void           
libnd_proto_plugin_foreach(LND_ProtoPluginFunc callback,
			   void *user_data)
{
  GList *l;

  if (!callback)
    return;
  
  for (l = g_list_first(proto_plugins); l; l = g_list_next(l))
    callback((LND_ProtoPlugin *)l->data, user_data);
}


syntax highlighted by Code2HTML, v. 0.9.1