/* 
 * Copyright (c) 2002, 2003, 2004 Jean-Yves Lefort <jylefort@brutele.be>
 *
 * 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#ifdef WITH_LOCAL_METADATA
#include <tag_c.h>
#endif
#include "streamtuner.h"

/*** cpp *********************************************************************/

#define POINTER_TO_STRING(ptr)	((ptr) ? (ptr) : "")

/*** type definitions ********************************************************/

typedef struct
{
  STStream	stream;

  char		*pathname;	/* full pathname, in the fs locale */
  char		*filename;	/* filename only, converted to UTF-8 */
  char		*title;
  char		*artist;
  char		*album;
  char		*year;
  char		*genre;
  char		*comment;
  char		*duration;
  int		bitrate;
  int		samplerate;
  int		channels;
} LocalStream;

enum {
  FIELD_PATHNAME,
  FIELD_FILENAME,
  FIELD_TITLE,
  FIELD_ARTIST,
  FIELD_ALBUM,
  FIELD_YEAR,
  FIELD_GENRE,
  FIELD_COMMENT,
  FIELD_DURATION,
  FIELD_BITRATE,
  FIELD_SAMPLERATE,
  FIELD_CHANNELS,
  FIELD_AUDIO		/* meta field (bitrate, samplerate and channels) */
};

/*** variables ***************************************************************/

static STPlugin *local_plugin = NULL;
static STHandler *local_handler = NULL;

/*** function declarations ***************************************************/

static LocalStream *stream_new_cb (gpointer data);
static void stream_field_get_cb (LocalStream *stream,
				 STHandlerField *field,
				 GValue *value,
				 gpointer data);
static void stream_field_set_cb (LocalStream *stream,
				 STHandlerField *field,
				 const GValue *value,
				 gpointer data);
static void stream_stock_field_get_cb (LocalStream *stream,
				       STHandlerStockField stock_field,
				       GValue *value,
				       gpointer data);
static gboolean stream_modify_cb (LocalStream *stream,
				  GSList *fields,
				  GSList *values,
				  gpointer data,
				  GError **err);
static gboolean stream_delete_cb (LocalStream *stream,
				  gpointer data,
				  GError **err);
static void stream_free_cb (LocalStream *stream, gpointer data);

static gboolean stream_rename (LocalStream *stream,
			       const GValue *new_filename,
			       GError **err);

static gboolean stream_tune_in_multiple_cb (GSList *streams,
					    gpointer data,
					    GError **err);
static gboolean stream_browse_cb (LocalStream *stream,
				  gpointer data,
				  GError **err);

static gboolean reload_cb (STCategory *category,
			   GNode **categories,
			   GList **streams,
			   gpointer data,
			   GError **err);

static gboolean reload_categories (const char *music_dir,
				   GNode *root,
				   GError **err);
static gboolean reload_streams (const char *music_dir,
				STCategory *category,
				GList **streams,
				GError **err);

#ifdef WITH_LOCAL_METADATA
static void metadata_read (LocalStream *stream);
static gboolean metadata_write (LocalStream *stream,
				GSList *fields,
				GSList *values,
				GError **err);
#endif /* WITH_LOCAL_METADATA */

static void init_handler (void);
static gboolean check_api_version (GError **err);

/*** implementation **********************************************************/

static LocalStream *
stream_new_cb (gpointer data)
{
  return g_new0(LocalStream, 1);
}

static void
stream_field_get_cb (LocalStream *stream,
		     STHandlerField *field,
		     GValue *value,
		     gpointer data)
{
  switch (field->id)
    {
    case FIELD_PATHNAME:
      g_value_set_string(value, stream->pathname);
      break;

    case FIELD_FILENAME:
      g_value_set_string(value, stream->filename);
      break;

    case FIELD_TITLE:
      g_value_set_string(value, stream->title);
      break;

    case FIELD_ARTIST:
      g_value_set_string(value, stream->artist);
      break;

    case FIELD_ALBUM:
      g_value_set_string(value, stream->album);
      break;
      
    case FIELD_YEAR:
      g_value_set_string(value, stream->year);
      break;

    case FIELD_GENRE:
      g_value_set_string(value, stream->genre);
      break;

    case FIELD_COMMENT:
      g_value_set_string(value, stream->comment);
      break;

    case FIELD_DURATION:
      g_value_set_string(value, stream->duration);
      break;

    case FIELD_BITRATE:
      g_value_set_int(value, stream->bitrate);
      break;

    case FIELD_SAMPLERATE:
      g_value_set_int(value, stream->samplerate);
      break;

    case FIELD_CHANNELS:
      g_value_set_int(value, stream->channels);
      break;

    case FIELD_AUDIO:
      g_value_take_string(value, st_format_audio_properties(stream->bitrate,
							    stream->samplerate,
							    stream->channels));
      break;

    default:
      g_assert_not_reached();
    }
}

static void
stream_field_set_cb (LocalStream *stream,
		     STHandlerField *field,
		     const GValue *value,
		     gpointer data)
{
  switch (field->id)
    {
    case FIELD_PATHNAME:
      stream->pathname = g_value_dup_string(value);
      break;

    case FIELD_FILENAME:
      stream->filename = g_value_dup_string(value);
      break;

    case FIELD_TITLE:
      stream->title = g_value_dup_string(value);
      break;

    case FIELD_ARTIST:
      stream->artist = g_value_dup_string(value);
      break;

    case FIELD_ALBUM:
      stream->album = g_value_dup_string(value);
      break;
      
    case FIELD_YEAR:
      stream->year = g_value_dup_string(value);
      break;

    case FIELD_GENRE:
      stream->genre = g_value_dup_string(value);
      break;

    case FIELD_COMMENT:
      stream->comment = g_value_dup_string(value);
      break;

    case FIELD_DURATION:
      stream->duration = g_value_dup_string(value);
      break;

    case FIELD_BITRATE:
      stream->bitrate = g_value_get_int(value);
      break;

    case FIELD_SAMPLERATE:
      stream->samplerate = g_value_get_int(value);
      break;

    case FIELD_CHANNELS:
      stream->channels = g_value_get_int(value);
      break;

    default:
      g_assert_not_reached();
    }
}

static void
stream_stock_field_get_cb (LocalStream *stream,
			   STHandlerStockField stock_field,
			   GValue *value,
			   gpointer data)
{
  switch (stock_field)
    {
    case ST_HANDLER_STOCK_FIELD_NAME:
      {
	char *name;

	if (stream->artist && stream->title)
	  name = g_strdup_printf("%s - %s", stream->artist, stream->title);
	else if (stream->title)
	  name = g_strdup(stream->title);
	else
	  name = g_strdup(stream->filename);

	g_value_set_string(value, name);
	g_free(name);

	break;
      }

    case ST_HANDLER_STOCK_FIELD_GENRE:
      g_value_set_string(value, stream->genre);
      break;

    case ST_HANDLER_STOCK_FIELD_DESCRIPTION:
      g_value_set_string(value, stream->comment);
      break;

    case ST_HANDLER_STOCK_FIELD_HOMEPAGE:
      /* nop */
      break;

    case ST_HANDLER_STOCK_FIELD_URI_LIST:
      {
	char *uri;
	GError *err = NULL;

	uri = g_filename_to_uri(stream->pathname, NULL, &err);
	if (uri)
	  {
	    GValueArray *value_array;
	    GValue uri_value = { 0, };
	    
	    value_array = g_value_array_new(1);

	    g_value_init(&uri_value, G_TYPE_STRING);
	    g_value_take_string(&uri_value, uri);
	    g_value_array_append(value_array, &uri_value);
	    g_value_unset(&uri_value);

	    g_value_take_boxed(value, value_array);
	  }
	else
	  {
	    st_handler_notice(local_handler, _("%s: unable to convert filename to URI: %s"), stream->pathname, err->message);
	    g_error_free(err);
	  }

	break;
      }
    }
}

static gboolean
stream_modify_cb (LocalStream *stream,
		  GSList *fields,
		  GSList *values,
		  gpointer data,
		  GError **err)
{
  GSList *f;
  GSList *v;
  gboolean modify_file = FALSE;

  for (f = fields, v = values; f && v; f = f->next, v = v->next)
    {
      STHandlerField *field = f->data;
      const GValue *value = v->data;

      switch (field->id)
	{
	case FIELD_FILENAME:
	  if (! stream_rename(stream, value, err))
	    return FALSE;
	  break;

	case FIELD_TITLE:
	case FIELD_ARTIST:
	case FIELD_ALBUM:
	case FIELD_YEAR:
	case FIELD_GENRE:
	case FIELD_COMMENT:
	  modify_file = TRUE;
	  break;

	default:
	  g_assert_not_reached();
	}
    }

  if (modify_file)
    {
#ifdef WITH_LOCAL_METADATA
      if (! metadata_write(stream, fields, values, err))
	return FALSE;
#else
      g_set_error(err, 0, 0, _("metadata support is disabled"));
      return FALSE;
#endif /* WITH_LOCAL_METADATA */
    }

  return TRUE;
}

static gboolean
stream_delete_cb (LocalStream *stream, gpointer data, GError **err)
{
  if (unlink(stream->pathname) < 0)
    {
      g_set_error(err, 0, 0, "%s", g_strerror(errno));
      return FALSE;
    }
  else
    return TRUE;
}

static void
stream_free_cb (LocalStream *stream, gpointer data)
{
  g_free(stream->pathname);
  g_free(stream->filename);
  g_free(stream->title);
  g_free(stream->artist);
  g_free(stream->album);
  g_free(stream->year);
  g_free(stream->genre);
  g_free(stream->comment);
  g_free(stream->duration);

  st_stream_free((STStream *) stream);
}

static gboolean
stream_rename (LocalStream *stream, const GValue *new_filename, GError **err)
{
  GError *tmp_err = NULL;
  char *filename;
  char *directory;
  char *new_pathname;

  g_return_val_if_fail(stream != NULL, FALSE);
  g_return_val_if_fail(G_IS_VALUE(new_filename), FALSE);

  filename = g_filename_from_utf8(g_value_get_string(new_filename), -1, NULL, NULL, &tmp_err);
  if (! filename)
    {
      g_set_error(err, 0, 0, _("unable to convert filename from UTF-8 encoding: %s"), tmp_err->message);
      g_error_free(tmp_err);

      return FALSE;
    }

  directory = g_path_get_dirname(stream->pathname);
  new_pathname = g_build_filename(directory, filename, NULL);
  g_free(directory);

  if (g_file_test(new_pathname, G_FILE_TEST_EXISTS))
    {
      g_set_error(err, 0, 0, _("target file already exists"));
      g_free(filename);
      g_free(new_pathname);
      
      return FALSE;
    }

  if (rename(stream->pathname, new_pathname) < 0)
    {
      g_set_error(err, 0, 0, "%s", g_strerror(errno));
      g_free(filename);
      g_free(new_pathname);

      return FALSE;
    }

  /* success */

  stream->pathname = new_pathname;
  stream->filename = g_value_dup_string(new_filename);

  return TRUE;
}

static gboolean
stream_tune_in_multiple_cb (GSList *streams, gpointer data, GError **err)
{
  char *m3uname;
  GSList *filenames = NULL;
  GSList *l;
  gboolean status;

  /* create a list of filenames from STREAMS */

  for (l = streams; l; l = l->next)
    {
      LocalStream *stream = l->data;
      filenames = g_slist_append(filenames, stream->pathname);
    }

  /* write the .m3u */

  m3uname = st_m3u_mktemp("streamtuner.local.XXXXXX", filenames, err);
  g_slist_free(filenames);

  if (! m3uname)
    return FALSE;

  /* open the .m3u */

  status = st_action_run("play-m3u", m3uname, err);
  g_free(m3uname);

  return status;
}

static gboolean
stream_browse_cb (LocalStream *stream, gpointer data, GError **err)
{
  char *url;
  char *s;
  gboolean status;

  if (stream->album)
    url = g_strconcat("http://www.allmusic.com/cg/amg.dll?p=amg&opt1=2&sql=", stream->album, NULL);
  else if (stream->title)
    url = g_strconcat("http://www.allmusic.com/cg/amg.dll?p=amg&opt1=3&sql=", stream->title, NULL);
  else if (stream->artist)
    url = g_strconcat("http://www.allmusic.com/cg/amg.dll?p=amg&opt1=1&sql=", stream->artist, NULL);
  else
    {
      g_set_error(err, 0, 0, _("file has no album, title or artist information"));
      return FALSE;
    }

  /* allmusic.com needs this */
  for (s = url; *s; s++)
    if (*s == ' ')
      *s = '|';

  status = st_action_run("view-web", url, err);
  g_free(url);
  
  return status;
}

static gboolean
reload_cb (STCategory *category,
	   GNode **categories,
	   GList **streams,
	   gpointer data,
	   GError **err)
{
  char *music_dir;
  gboolean status;

  *categories = g_node_new(NULL);

  music_dir = st_settings_get_music_dir();
  if (! music_dir)
    {
      g_set_error(err, 0, 0, _("you must set your music folder in the Preferences"));
      return FALSE;
    }
  
  status = reload_categories(music_dir, *categories, err)
    && reload_streams(music_dir, category, streams, err);

  g_free(music_dir);
  return status;
}

static gboolean
reload_categories (const char *music_dir, GNode *root, GError **err)
{
  GDir *dir;
  char *dirname;
  const char *filename;
  gboolean status = TRUE;
  GError *tmp_err = NULL;

  g_return_val_if_fail(music_dir != NULL, FALSE);
  g_return_val_if_fail(root != NULL, FALSE);

  dirname = root->data
    ? g_build_filename(music_dir, ((STCategory *) root->data)->url_postfix, NULL)
    : g_strdup(music_dir);
  
  dir = g_dir_open(dirname, 0, &tmp_err);
  if (! dir)
    {
      g_set_error(err, 0, 0, _("unable to open directory %s: %s"), dirname, tmp_err->message);
      g_error_free(tmp_err);

      status = FALSE;
      goto end;
    }
  
  while ((filename = g_dir_read_name(dir)))
    {
      GNode *node;
      char *pathname;
      
      if (st_is_aborted())
	{
	  status = FALSE;
	  goto end;
	}
      
      if (filename[0] == '.')
	continue;
      
      pathname = g_build_filename(dirname, filename, NULL);
      
      if (g_file_test(pathname, G_FILE_TEST_IS_DIR))
	{
	  STCategory *category;
	
	  category = st_category_new();
	
	  category->name = root->data
	    ? g_build_filename(((STCategory *) root->data)->url_postfix, filename, NULL)
	    : g_strdup(filename);

	  category->label = g_filename_to_utf8(filename, -1, NULL, NULL, &tmp_err);
	  if (! category->label)
	    {
	      st_handler_notice(local_handler, _("%s: unable to convert directory name to UTF-8 encoding: %s"), pathname, tmp_err->message);
	      g_clear_error(&tmp_err);
	    }
	
	  category->url_postfix = g_strdup(category->name);
	
	  node = g_node_append_data(root, category);
	  if (! reload_categories(music_dir, node, err))
	    {
	      status = FALSE;
	      goto end;
	    }
	}
      
      g_free(pathname);
    }

 end:
  if (dir)
    g_dir_close(dir);
  g_free(dirname);
  
  return status;
}

static gboolean
reload_streams (const char *music_dir,
		STCategory *category,
		GList **streams,
		GError **err)
{
  GDir *dir;
  char *dirname;
  const char *filename;
  gboolean status = TRUE;
  GError *tmp_err = NULL;

  g_return_val_if_fail(music_dir != NULL, FALSE);
  g_return_val_if_fail(category != NULL, FALSE);
  g_return_val_if_fail(streams != NULL, FALSE);

  dirname = category->url_postfix
    ? g_build_filename(music_dir, category->url_postfix, NULL)
    : g_strdup(music_dir);

  dir = g_dir_open(dirname, 0, &tmp_err);
  if (! dir)
    {
      g_set_error(err, 0, 0, _("unable to open directory %s: %s"), dirname, tmp_err->message);
      g_error_free(tmp_err);

      status = FALSE;
      goto end;
    }

  while ((filename = g_dir_read_name(dir)))
    {
      LocalStream *stream;
      char *extension;
      
      if (st_is_aborted())
	{
	  status = FALSE;
	  goto end;
	}
      
      if (filename[0] == '.')
	continue;
      
      extension = strrchr(filename, '.');
      if (! (extension++
	     && (! g_ascii_strcasecmp(extension, "mp3")
		 || ! g_ascii_strcasecmp(extension, "ogg")
		 || ! g_ascii_strcasecmp(extension, "m3u")
		 || ! g_ascii_strcasecmp(extension, "pls"))))
	continue;		/* unhandled */

      stream = stream_new_cb(NULL);
      stream->pathname = g_build_filename(dirname, filename, NULL);

      ((STStream *) stream)->name = g_strdup(filename);

      stream->filename = g_filename_to_utf8(filename, -1, NULL, NULL, &tmp_err);
      if (! stream->filename)
	{
	  st_handler_notice(local_handler, _("%s: unable to convert filename to UTF-8 encoding: %s"), stream->pathname, tmp_err->message);
	  g_clear_error(&tmp_err);
	}
      
#ifdef WITH_LOCAL_METADATA
      metadata_read(stream);
#endif

      *streams = g_list_append(*streams, stream);
    }
  
 end:
  if (dir)
    g_dir_close(dir);
  g_free(dirname);
  
  return status;
}

#ifdef WITH_LOCAL_METADATA
static void
metadata_read (LocalStream *stream)
{
  TagLib_File *file;
  TagLib_Tag *tag;
  const TagLib_AudioProperties *audio_properties;

  g_return_if_fail(stream != NULL);

  file = taglib_file_new(stream->pathname);
  if (! file)
    {
      st_handler_notice(local_handler, _("unable to open %s"), stream->pathname);
      return;
    }

  tag = taglib_file_tag(file);
  if (tag)
    {
      char *title;
      char *artist;
      char *album;
      unsigned int year;
      char *genre;
      char *comment;

      title = taglib_tag_title(tag);
      g_return_if_fail(title != NULL);
      artist = taglib_tag_artist(tag);
      g_return_if_fail(artist != NULL);
      album = taglib_tag_album(tag);
      g_return_if_fail(album != NULL);
      year = taglib_tag_year(tag);
      genre = taglib_tag_genre(tag);
      g_return_if_fail(genre != NULL);
      comment = taglib_tag_comment(tag);
      g_return_if_fail(comment != NULL);

      if (*title)
	stream->title = g_strdup(title);
      if (*artist)
	stream->artist = g_strdup(artist);
      if (*album)
	stream->album = g_strdup(album);
      if (year != 0)
	stream->year = g_strdup_printf("%u", year);
      if (*genre)
	stream->genre = g_strdup(genre);
      if (*comment)
	stream->comment = g_strdup(comment);

      taglib_tag_free_strings();
    }
  else
    st_handler_notice(local_handler, _("%s has no tag"), stream->pathname);
  
  audio_properties = taglib_file_audioproperties(file);
  if (audio_properties)
    {
      int length;
      
      length = taglib_audioproperties_length(audio_properties);
      if (length != 0)
	stream->duration = g_strdup_printf("%02u:%02u", length / 60, length % 60);
  
      stream->bitrate = taglib_audioproperties_bitrate(audio_properties);
      stream->samplerate = taglib_audioproperties_samplerate(audio_properties);
      stream->channels = taglib_audioproperties_channels(audio_properties);
    }
  else
    st_handler_notice(local_handler, _("%s has no audio properties"), stream->pathname);

  taglib_file_free(file);
}

static gboolean
metadata_write (LocalStream *stream,
		GSList *fields,
		GSList *values,
		GError **err)
{
  TagLib_File *file;
  TagLib_Tag *tag;
  gboolean status;

  g_return_val_if_fail(stream != NULL, FALSE);

  file = taglib_file_new(stream->pathname);
  if (! file)
    {
      g_set_error(err, 0, 0, _("unable to open file"));
      return FALSE;
    }

  tag = taglib_file_tag(file);
  if (tag)
    {
      GSList *f;
      GSList *v;

      for (f = fields, v = values; f && v; f = f->next, v = v->next)
	{
	  STHandlerField *field = f->data;
	  const GValue *value = v->data;
	  const char *str = g_value_get_string(value);
	  char **ptr = NULL;
	  
	  switch (field->id)
	    {
	    case FIELD_TITLE:
	      taglib_tag_set_title(tag, POINTER_TO_STRING(str));
	      ptr = &stream->title;
	      break;

	    case FIELD_ARTIST:
	      taglib_tag_set_artist(tag, POINTER_TO_STRING(str));
	      ptr = &stream->artist;
	      break;

	    case FIELD_ALBUM:
	      taglib_tag_set_album(tag, POINTER_TO_STRING(str));
	      ptr = &stream->album;
	      break;

	    case FIELD_YEAR:
	      {
		int year = str ? atoi(str) : 0;
		taglib_tag_set_year(tag, year);
		ptr = &stream->year;
	      }
	      break;

	    case FIELD_GENRE:
	      taglib_tag_set_genre(tag, POINTER_TO_STRING(str));
	      ptr = &stream->genre;
	      break;

	    case FIELD_COMMENT:
	      taglib_tag_set_comment(tag, POINTER_TO_STRING(str));
	      ptr = &stream->comment;
	      break;
	    }

	  if (ptr)
	    {
	      g_free(*ptr);
	      *ptr = g_strdup(str);
	    }
	}

      status = taglib_file_save(file);
      if (! status)
	g_set_error(err, 0, 0, _("unable to save file"));
    }
  else
    {
      status = FALSE;
      g_set_error(err, 0, 0, _("the tag structure is missing"));
    }

  taglib_file_free(file);
  
  return status;
}
#endif /* WITH_LOCAL_METADATA */

static void
init_handler (void)
{
  GNode *stock_categories;
  STCategory *category;
  STHandlerField *field;

  local_handler = st_handler_new_from_plugin(local_plugin);

  st_handler_set_description(local_handler, _("Local Music Collection"));

  stock_categories = g_node_new(NULL);

  category = st_category_new();
  category->name = "__main";
  category->label = _("Root");
  
  g_node_append_data(stock_categories, category);
  
  st_handler_set_stock_categories(local_handler, stock_categories);
  st_handler_set_flags(local_handler, ST_HANDLER_CONFIRM_DELETION);

  st_handler_bind(local_handler, ST_HANDLER_EVENT_RELOAD, reload_cb, NULL);

  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_NEW, stream_new_cb, NULL);
  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_FIELD_GET, stream_field_get_cb, NULL);
  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_FIELD_SET, stream_field_set_cb, NULL);
  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET, stream_stock_field_get_cb, NULL);
  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_MODIFY, stream_modify_cb, NULL);
  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_DELETE, stream_delete_cb, NULL);
  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_FREE, stream_free_cb, NULL);

  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_TUNE_IN_MULTIPLE, stream_tune_in_multiple_cb, NULL);
  st_handler_bind(local_handler, ST_HANDLER_EVENT_STREAM_BROWSE, stream_browse_cb, NULL);

  st_handler_add_field(local_handler,
		       st_handler_field_new(FIELD_PATHNAME,
					    _("Pathname"),
					    G_TYPE_STRING,
					    0));

  field = st_handler_field_new(FIELD_FILENAME,
			       _("Filename"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_EDITABLE);
  st_handler_field_set_description(field, _("The song filename"));
  st_handler_add_field(local_handler, field);

  field = st_handler_field_new(FIELD_TITLE,
			       _("Title"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_EDITABLE);
  st_handler_field_set_description(field, _("The song title"));
  st_handler_add_field(local_handler, field);

  field = st_handler_field_new(FIELD_ARTIST,
			       _("Artist"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_EDITABLE);
  st_handler_field_set_description(field, _("The performing artist"));
  st_handler_add_field(local_handler, field);

  field = st_handler_field_new(FIELD_ALBUM,
			       _("Album"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_EDITABLE);
  st_handler_field_set_description(field, _("The album the song was released on"));
  st_handler_add_field(local_handler, field);

  field = st_handler_field_new(FIELD_YEAR,
			       _("Year"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_EDITABLE);
  st_handler_field_set_description(field, _("The song release year"));
  st_handler_add_field(local_handler, field);

  field = st_handler_field_new(FIELD_GENRE,
			       _("Genre"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_EDITABLE
			       | ST_HANDLER_FIELD_START_HIDDEN);
  st_handler_field_set_description(field, _("The song genre"));
  st_handler_add_field(local_handler, field);

  field = st_handler_field_new(FIELD_COMMENT,
			       _("Comment"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_EDITABLE
			       | ST_HANDLER_FIELD_START_HIDDEN);
  st_handler_field_set_description(field, _("The song comment"));
  st_handler_add_field(local_handler, field);

  field = st_handler_field_new(FIELD_DURATION,
			       _("Duration"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE);
  st_handler_field_set_description(field, _("The song duration"));
  st_handler_add_field(local_handler, field);

  field = st_handler_field_new(FIELD_AUDIO,
			       _("Audio"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_START_HIDDEN
			       | ST_HANDLER_FIELD_VOLATILE);
  st_handler_field_set_description(field, _("The song audio properties"));
  st_handler_add_field(local_handler, field);
  
  /* invisible fields */

  st_handler_add_field(local_handler, st_handler_field_new(FIELD_BITRATE,
							   _("Bitrate"),
							   G_TYPE_INT,
							   0));
  st_handler_add_field(local_handler, st_handler_field_new(FIELD_SAMPLERATE,
							   _("Sample rate"),
							   G_TYPE_INT,
							   0));
  st_handler_add_field(local_handler, st_handler_field_new(FIELD_CHANNELS,
							   _("Channels"),
							   G_TYPE_INT,
							   0));

  st_handlers_add(local_handler);
}

static gboolean
check_api_version (GError **err)
{
  if (st_check_api_version(5, 8))
    return TRUE;
  else
    {
      g_set_error(err, 0, 0, _("API version mismatch"));
      return FALSE;
    }
}

G_MODULE_EXPORT gboolean
plugin_get_info (STPlugin *plugin, GError **err)
{
  GdkPixbuf *pixbuf;

  if (! check_api_version(err))
    return FALSE;

  local_plugin = plugin;

  st_plugin_set_name(plugin, "local");
  st_plugin_set_label(plugin, _("Local"));

  pixbuf = st_pixbuf_new_from_file(UIDIR "/local.png");
  if (pixbuf)
    {
      st_plugin_set_icon_from_pixbuf(plugin, pixbuf);
      g_object_unref(pixbuf);
    }

  return TRUE;
}

G_MODULE_EXPORT gboolean
plugin_init (GError **err)
{
  if (! check_api_version(err))
    return FALSE;

  init_handler();

  st_action_register("play-m3u", _("Listen to a .m3u file"), "xmms %q");
  st_action_register("view-web", _("Open a web page"), "epiphany %q");

  return TRUE;
}
