//  BMP
//  Copyright (C) 2005-2007 BMP development.
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License Version 2 
//  as published by the Free Software Foundation.
//
//  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.
//
//  --
//
//  The BMPx project hereby grants permission for non-GPL compatible GStreamer
//  plugins to be used and distributed together with GStreamer and BMPx. This
//  permission is above and beyond the permissions granted by the GPL license
//  BMPx is covered by.

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

#include <glibmm.h>

#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gtkmm.h>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <iostream>
#include <sstream>

#include <boost/shared_ptr.hpp>
#include <boost/format.hpp>
#include <boost/optional.hpp>
#include <boost/algorithm/string.hpp>

// TagLib
#include <taglib/taglib.h>
#include <taglib/fileref.h>
#include <taglib/audioproperties.h>
#include <taglib/tfile.h>
#include <taglib/tag.h>

// GStreamer
#include <gst/gst.h>
#include <gst/gstelement.h>

// BMP Audio
#include "audio/audio-typefind.hh"
#include "audio/audio.hh"

// BMP Misc 
#include "database.hh"
#include "debug.hh"
#include "library.hh"
#include "paths.hh"
#include "uri.hh"
#include "util.hh"
#include "util-file.hh"
#include "util-string.hh"

#ifdef HAVE_HAL
#  include "x_hal.hh"
#endif //HAVE_HAL
#include "x_library.hh"

#define TAG_READ_TUPLE(_data) ((TagReadTuple*)(_data))

#define MLIB_VERSION_CUR "6"
#define MLIB_VERSION_REV "1"
#define MLIB_VERSION_AGE "0"

using namespace Bmp::DB;
using namespace Glib;
using namespace TagLib;
using namespace boost;
using namespace std;

namespace
{
  using namespace Bmp;

  const std::string VARIOUS_ARTIST = "89ad4ac3-39f7-470e-963a-56509c";

  AttributeInfo
  attrinfo[N_ATTRIBUTES] =
  {
      {
        "Location",
        "location",
        VALUE_TYPE_STRING,
        true
      },
      {
        "Title",
        "title",
        VALUE_TYPE_STRING,
        true
      },
      {
        "Track",
        "tracknumber",
        VALUE_TYPE_INT,
        true
      },
      {
        "Time",
        "time",
        VALUE_TYPE_INT,
        true
      },
      {
        "Genre",
        "genre",
        VALUE_TYPE_STRING,
        true
      },
      {
        "Comment",
        "comment",
        VALUE_TYPE_STRING,
        true
      },
      {
        "Rating",
        "rating",
        VALUE_TYPE_INT,
        true
      },
      {
        "Date",
        "date",
        VALUE_TYPE_INT,
        true
      },
      {
        "Time Modified",
        "mtime",
        VALUE_TYPE_INT,
        true
      },
      {
        "Bitrate",
        "bitrate",
        VALUE_TYPE_INT,
        true
      },
      {
        "Samplerate",
        "samplerate",
        VALUE_TYPE_INT,
        true
      },
      {
        "Play count",
        "count",
        VALUE_TYPE_INT,
        true
      },
      {
        "Last Played Date",
        "playdate",
        VALUE_TYPE_INT,
        true
      },
      {
        "MusicIP PUID",
        "puid",
        VALUE_TYPE_STRING,
        true
      },
      {
        "New Item",
        "new_item",
        VALUE_TYPE_INT,
        true
      },
      {
        "Track Hash",
        "hash",
        VALUE_TYPE_STRING,
        true
      },
      {
        "MusicBrainz Track ID",
        "mb_track_id",
        VALUE_TYPE_STRING,
        true
      },
      {
        "Artist",
        "artist",
        VALUE_TYPE_STRING,
        false
      },
      {
        "MusicBrainz Artist Id",
        "mb_artist_id",
        VALUE_TYPE_STRING,
        false
      },
      {
        "MusicBrainz Artist Sortname",
        "mb_artist_sort_name",
        VALUE_TYPE_STRING,
        false
      },
      {
        "Album",
        "album",
        VALUE_TYPE_STRING,
        false
      },
      {
        "MusicBrainz Album Id",
        "mb_album_id",
        VALUE_TYPE_STRING,
        false
      },
      {
        "MusicBrainz Release Date",
        "mb_release_date",
        VALUE_TYPE_STRING,
        false
      },
      {
        "Amazon Asin",
        "asin",
        VALUE_TYPE_STRING,
        false
      },
      {
        "Is MusicBrainz Album Artist",
        "is_mb_album_artist",
        VALUE_TYPE_INT,
        false
      },
      {
        "MusicBrainz Album Artist",
        "mb_album_artist",
        VALUE_TYPE_STRING,
        false
      },
      {
        "MusicBrainz Album Artist Id",
        "mb_album_artist_id",
        VALUE_TYPE_STRING,
        false
      },
      {
        "MusicBrainz Album Artist Sortname",
        "mb_album_artist_sort_name",
        VALUE_TYPE_STRING,
        false
      },
      {
        "Active Track",
        "active",
        VALUE_TYPE_INT,
        true
      },
      {
        "File Type",
        "type",
        VALUE_TYPE_STRING,
        true
      },
      {
        "Volume UDI",
        "volume_udi",
        VALUE_TYPE_STRING,
        true
      },
      {
        "Device UDI",
        "device_udi",
        VALUE_TYPE_STRING,
        true
      },
      {
        "Volume Relative Path",
        "volume_relative_path",
        VALUE_TYPE_STRING,
        true
      },
  };
}

namespace Bmp
{
    namespace
    {
      inline bool
      is_module (std::string const& basename)
      {
        return Bmp::Util::str_has_suffix_nocase
          (basename.c_str (), G_MODULE_SUFFIX);
      } 

      struct TagReadTuple
      {
          Row * row;
          bool  eos;
      };

      //////////////////////////////////////////////////////////////////////////

      void
      for_each_tag (const GstTagList * list,
                    const char       * tag,
                    TagReadTuple     * tuple)
      {
        int count = gst_tag_list_get_tag_size (list, tag);
        for (int i = 0; i < count; ++i)
        {
          GValue const* value = gst_tag_list_get_value_index (list, tag, i);

          if (G_VALUE_HOLDS (value, G_TYPE_INT))
          {
            tuple->row->insert (std::make_pair (std::string (tag), guint64(g_value_get_int(value))));
          }

          else

          if (G_VALUE_HOLDS (value, G_TYPE_UINT))
          {
            tuple->row->insert (std::make_pair (std::string (tag), guint64(g_value_get_uint(value))));
          }

          else

          if (G_VALUE_HOLDS (value, G_TYPE_STRING))
          {
            const char * v (g_value_get_string (value));
            if (v)
            {
              tuple->row->insert (std::make_pair (std::string (tag), std::string (v))); 
            }
          }

        }
      }

      gboolean
      metadata_bus_handler (GstBus       *  bus,
                            GstMessage   *  message,
                            TagReadTuple *  tuple)
      {
          switch (GST_MESSAGE_TYPE (message))
          {
            case GST_MESSAGE_EOS:
            {
              tuple->eos = true;
              break;
            }

            case GST_MESSAGE_TAG:
            {
              GstTagList * tags = 0;
              gst_message_parse_tag (message, &tags);
              if (tags)
              {
                gst_tag_list_foreach (tags, GstTagForeachFunc (for_each_tag), tuple);
                gst_tag_list_free (tags);
              }
              break;
            }

            case GST_MESSAGE_ERROR:
            {
              GError * error = 0;
              gst_message_parse_error (message, &error, 0);
              debug ("library", "%s ERROR: %s\n", G_STRLOC, error->message);
              g_error_free (error);
              tuple->eos = true;
              break;
            }

            default: break;
          }
          return FALSE;
      }

      void
      metadata_event_loop (TagReadTuple * tuple,
                           GstElement   * element,
                           gboolean       block)
      {
          GstBus * bus (gst_element_get_bus (element));

          g_return_if_fail (bus != 0);

          bool done = false;

          while (!done && !tuple->eos)
          {
              GstMessage * message = 0;

              if (block)
              {
                message = gst_bus_poll (bus, GST_MESSAGE_ANY, -1);
              }
              else
              {
                message = gst_bus_pop  (bus);
              }

              if (message == 0)
              {
                gst_object_unref (bus);
                return;
              }

              done = metadata_bus_handler (bus, message, tuple);
              gst_message_unref (message);
          }
          gst_object_unref (bus);
      }
    } // end anonymous namespace 

    void 
    Library::metadata_set_taglib (Track & track)
    {
      std::string filename;

      try {
        filename = filename_from_uri (track.location.get());
      }
      catch (Glib::ConvertError& cxe)
      {
        throw MetadataWriteError ((boost::format (_("Unable to convert URI to filename for location: %s"))  % track.location.get().c_str()).str());
      } 

      std::string type;
      if (!Audio::typefind (filename, type))
      {
        throw MetadataWriteError ((boost::format (_("Unknown file type: %s")) % filename).str());
      }

      TaglibPluginsMap::iterator i = m_taglib_plugins.find (type);
      if (i == m_taglib_plugins.end())
        throw MetadataWriteError ((boost::format (_("No plugin to handle metadata of type %s")) % type).str());

      if (i->second->set (filename, track))
        return;
      else
        throw MetadataWriteError ((boost::format (_("Error writing metadata for file %s")) % filename).str());
    }

    void
    Library::metadata_get_taglib (std::string const& uri, DB::Row & row, string const& type) const
    {
      try{
          std::string filename = filename_from_uri (uri);
          TaglibPluginsMap::const_iterator i = m_taglib_plugins.find (type);
          if (i != m_taglib_plugins.end() && i->second->get && i->second->get (filename, row))
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_LOCATION].id, uri));
            return;
          }
          else
          {
            throw NoMetadataTaglibError ((boost::format (_("No plugin to handle metadata of type %s")) % type).str());
          }
        }
      catch (Glib::ConvertError & cxe)
        {
            throw NoMetadataTaglibError ((boost::format (_("Glib Conversion error: ")) % cxe.what().c_str()).str());
        }
    }

    void
    Library::metadata_get_gst (string const& uri, DB::Row & row) const
    {
      boost::shared_ptr<TagReadTuple> ttuple;

      ttuple = boost::shared_ptr<TagReadTuple> (new TagReadTuple);
      ttuple->eos = false;
      ttuple->row = &row;
      ttuple->row->insert (std::make_pair (attrinfo[ATTRIBUTE_LOCATION].id, string (uri)));

      GstElement *element   = gst_element_factory_make ("playbin",  "playbin0");
      GstElement *fakesink  = gst_element_factory_make ("fakesink", "fakesink0");
      GstElement *videosink = gst_element_factory_make ("fakesink", "fakesink1");

      g_object_set (G_OBJECT (element), "uri", uri.c_str(), "audio-sink", fakesink, "video-sink", videosink, NULL);

      GstStateChangeReturn state_ret = gst_element_set_state (element, GST_STATE_PAUSED);
      int change_timeout = 5;

      while (state_ret == GST_STATE_CHANGE_ASYNC && change_timeout > 0)
      {
        GstState state;
        state_ret = gst_element_get_state (GST_ELEMENT (element), &state, 0, 1 * GST_SECOND);
        change_timeout--;
      }

      if (state_ret != GST_STATE_CHANGE_FAILURE && state_ret != GST_STATE_CHANGE_ASYNC)
      {
        // Post application specific message so we'll know when to stop the message loop
        GstBus *bus;
        bus = gst_element_get_bus (GST_ELEMENT (element));
        if (bus)
        {
          gst_bus_post (bus, gst_message_new_application (GST_OBJECT (element), 0));
          gst_object_unref (bus);
        }
        // Poll the bus for messages
        metadata_event_loop (ttuple.get(), GST_ELEMENT (element), FALSE);
      }

      GstFormat  format = GST_FORMAT_TIME;
      GstQuery  *query  = gst_query_new_duration (format);

      if (gst_element_query (GST_ELEMENT(element), query))
      {
        gst_element_get_state (GST_ELEMENT(element), 0, 0, 100 * GST_MSECOND);
        gint64 length_in_nanoseconds;
        gst_query_parse_duration (query, &format, &length_in_nanoseconds);
        guint64 length = length_in_nanoseconds / GST_SECOND;
        ttuple->row->insert (std::make_pair (attrinfo[ATTRIBUTE_TIME].id, Variant (length)));
      }
      gst_query_unref (query);
      gst_element_set_state (GST_ELEMENT (element), GST_STATE_NULL);
      gst_object_unref (GST_ELEMENT (element));
    }

    guint64
    Library::get_track_artist_id (Row const &row, bool only_if_exists)
    {
      Artist artist (row);
      RowV rows; 

      char const* select_artist_f ("SELECT id FROM artist WHERE %s %s AND %s %s AND %s %s;"); 
      m_db->get (rows, mprintf (select_artist_f,

                                    attrinfo[ATTRIBUTE_ARTIST].id,
                                    (artist.artist               ? mprintf (" = '%q'", artist.artist.get().c_str()).c_str()
                                                                 : "IS NULL"),

                                    attrinfo[ATTRIBUTE_MB_ARTIST_ID].id,
                                    (artist.mb_artist_id         ? mprintf (" = '%q'", artist.mb_artist_id.get().c_str()).c_str()
                                                                 : "IS NULL"),

                                    attrinfo[ATTRIBUTE_MB_ARTIST_SORTNAME].id,
                                    (artist.mb_artist_sort_name  ? mprintf (" = '%q'", artist.mb_artist_sort_name.get().c_str()).c_str()
                                                                 : "IS NULL")));

  
      guint64 artist_j = 0;

      if (rows.size ())
      {
        Row const& row (rows[0]);
        artist_j = boost::get <guint64> (row.find ("id")->second);
      }
      else
      if (!only_if_exists)
      {
        char const* set_artist_f ("INSERT INTO artist (%s, %s, %s) VALUES (%s, %s, %s);");
        m_db->exec_sql (mprintf (set_artist_f,

                                     attrinfo[ATTRIBUTE_ARTIST].id,
                                     attrinfo[ATTRIBUTE_MB_ARTIST_ID].id,
                                     attrinfo[ATTRIBUTE_MB_ARTIST_SORTNAME].id,

                                     (artist.artist               ? mprintf ("'%q'", artist.artist.get().c_str()).c_str()
                                                                  : "NULL"),

                                     (artist.mb_artist_id         ? mprintf ("'%q'", artist.mb_artist_id.get().c_str()).c_str()
                                                                  : "NULL"),

                                     (artist.mb_artist_sort_name  ? mprintf ("'%q'", artist.mb_artist_sort_name.get().c_str()).c_str()
                                                                  : "NULL")));

        artist_j = m_db->last_insert_rowid ();
      }
      return artist_j;
    }

    guint64
    Library::get_album_artist_id (const Row &row, bool only_if_exists)
    {
      AlbumArtist artist (row);

      if (artist.album_artist && artist.mb_album_artist_id)
      {
        artist.is_mb_album_artist = true;
      }
      else
      {
        artist.is_mb_album_artist = false;
        artist.album_artist = Artist (row).artist; 
      }

      RowV rows;

      char const* select_artist_f ("SELECT id FROM album_artist WHERE (%s %s) AND (%s %s) AND (%s %s) AND (%s %s);"); 
      m_db->get (rows, (mprintf (select_artist_f, 

        attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST].id,
        (artist.album_artist                  ? mprintf (" = '%q'", artist.album_artist.get().c_str()).c_str()
                                              : "IS NULL"), 

        attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_ID].id,
        (artist.mb_album_artist_id            ? mprintf (" = '%q'", artist.mb_album_artist_id.get().c_str()).c_str()
                                              : "IS NULL"), 

        attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_SORTNAME].id,
        (artist.mb_album_artist_sort_name     ? mprintf (" = '%q'", artist.mb_album_artist_sort_name.get().c_str()).c_str()
                                              : "IS NULL"), 

        attrinfo[ATTRIBUTE_IS_MB_ALBUM_ARTIST].id,
        (artist.is_mb_album_artist            ? "= '1'" : "IS NULL")))

      );

      guint64 artist_j = 0;

      if (!rows.empty())
      {
        artist_j = boost::get <guint64> (rows[0].find ("id")->second);
      }
      else if (!only_if_exists)
      {
        char const* set_artist_f ("INSERT INTO album_artist (%s, %s, %s, %s) VALUES (%s, %s, %s, %s);");
        m_db->exec_sql (mprintf (set_artist_f,

          attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST].id,
          attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_ID].id,
          attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_SORTNAME].id,
          attrinfo[ATTRIBUTE_IS_MB_ALBUM_ARTIST].id,

          (artist.album_artist                ? mprintf ("'%q'", artist.album_artist.get().c_str()).c_str()
                                              : "NULL"),

          (artist.mb_album_artist_id          ? mprintf ("'%q'", artist.mb_album_artist_id.get().c_str()).c_str()
                                              : "NULL"),

          (artist.mb_album_artist_sort_name   ? mprintf ("'%q'", artist.mb_album_artist_sort_name.get().c_str()).c_str()
                                              : "NULL"), 

          (artist.is_mb_album_artist          ? "1" 
                                              : "NULL"))

        );

        artist_j = m_db->last_insert_rowid ();
      }
      return artist_j;
    }

    guint64
    Library::get_album_id (const Row &row, guint64 artist_id, bool only_if_exists)
    {
      Album album (row);
      guint64 album_j = 0;
      RowV rows;

      char const* select_album_f ("SELECT album, id FROM album WHERE (%s %s) AND (%s %s) AND (%s %s) AND (%s %s) AND (%s = %llu);"); 
      std::string sql = mprintf (select_album_f,

            attrinfo[ATTRIBUTE_ALBUM].id,
            (album.album               ? mprintf (" = '%q'", album.album.get().c_str()).c_str()
                                       : "IS NULL"), 

            attrinfo[ATTRIBUTE_MB_ALBUM_ID].id,
            (album.mb_album_id         ? mprintf (" = '%q'", album.mb_album_id.get().c_str()).c_str()
                                       : "IS NULL"), 

            attrinfo[ATTRIBUTE_MB_RELEASE_DATE].id,
            (album.mb_release_date     ? mprintf (" = '%q'", album.mb_release_date.get().c_str()).c_str()
                                       : "IS NULL"), 

            attrinfo[ATTRIBUTE_ASIN].id,
            (album.asin                ? mprintf (" = '%q'", album.asin.get().c_str()).c_str()
                                       : "IS NULL"), 
            "album_artist_j",
            artist_id
      );
      m_db->get (rows, sql); 

      if (!rows.empty())
      {
          album_j = boost::get <guint64> (rows[0].find ("id")->second);
      }
      else if (!only_if_exists)
      {
        char const* set_album_f ("INSERT INTO album (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %llu);");

        std::string sql = mprintf (set_album_f,
                                   attrinfo[ATTRIBUTE_ALBUM].id,
                                   attrinfo[ATTRIBUTE_MB_ALBUM_ID].id,
                                   attrinfo[ATTRIBUTE_MB_RELEASE_DATE].id,
                                   attrinfo[ATTRIBUTE_ASIN].id,
                                   "album_artist_j",
                                   (album.album              ? mprintf ("'%q'", album.album.get().c_str()).c_str()
                                                             : "NULL"),
                                   (album.mb_album_id        ? mprintf ("'%q'", album.mb_album_id.get().c_str()).c_str()
                                                             : "NULL"),
                                   (album.mb_release_date    ? mprintf ("'%q'", album.mb_release_date.get().c_str()).c_str()
                                                             : "NULL"),
                                   (album.asin               ? mprintf ("'%q'", album.asin.get().c_str()).c_str()
                                                             : "NULL"), artist_id);

        m_db->exec_sql (sql);
        album_j = m_db->last_insert_rowid ();
      }
      return album_j;
    }

    guint64
    Library::get_tag_id (std::string const& attr_id, std::string const& tag, bool only_if_exists)
    {
      RowV rows;

      char const* select_tag_f ("SELECT id FROM %s_tag WHERE tag ='%s';"); 
      m_db->get (rows, (mprintf (select_tag_f, attr_id.c_str(), tag.c_str())));

      guint64 tag_id = 0;

      if (rows.size ())
      {
        Row const& row (rows[0]);
        tag_id = boost::get <guint64> (row.find ("id")->second);
      }
      else
      if (!only_if_exists)
      {
        char const* set_album_f ("INSERT INTO %s_tag (%s) VALUES (%s);");
        m_db->exec_sql (mprintf (set_album_f, attr_id.c_str(), "tag", tag.c_str()));
        tag_id = m_db->last_insert_rowid ();
      }
      return tag_id;
    }

    void
    Library::tag_track (guint64 bmpx_track_id, StrV const& tags, bool refresh)
    {
      if (refresh)
      {
        char const* delete_title_tags_f ("DELETE FROM title_tag WHERE fki = %llu;");
        m_db->exec_sql (mprintf (delete_title_tags_f, bmpx_track_id));
      }

      for (StrV::const_iterator i = tags.begin(); i != tags.end(); ++i)
      {
        char const* title_tag_f ("INSERT INTO title_tag (tag_id, fki) VALUES (%llu, %llu);");
        m_db->exec_sql( mprintf( title_tag_f, get_tag_id ("title", *i, false), bmpx_track_id ) ); 
      }
    }
 
    void
    Library::tag_artist (guint64 bmpx_artist_id, StrV const& tags, bool refresh)
    {
      if (refresh)
      {
        char const* delete_artist_tags_f ("DELETE FROM artist_tag WHERE fki = %llu;");
        m_db->exec_sql (mprintf (delete_artist_tags_f, bmpx_artist_id));
      }

      for (StrV::const_iterator i = tags.begin(); i != tags.end(); ++i)
      {
        char const* artist_tag_f ("INSERT INTO artist_tag (tag_id, fki) VALUES (%llu, %llu);");
        m_db->exec_sql (mprintf (artist_tag_f, get_tag_id ("artist", *i, false), bmpx_artist_id)); 
      }
    }

    void
    Library::track_set_rating (guint64 bmpx_track_id, int rating) 
    {
      char const* set_rating_f ("UPDATE track SET rating='%d' WHERE id='%llu';");
      m_db->exec_sql (mprintf (set_rating_f, rating, bmpx_track_id));
      mark_dirty (bmpx_track_id);
      Track x (get_track (bmpx_track_id));
      signal_track_modified_.emit (x);
    }

    void
    Library::track_set_active (guint64 bmpx_track_id, bool active) 
    {
      char const* set_active_f ("UPDATE track SET active='%d' WHERE id='%llu';");
      m_db->exec_sql (mprintf (set_active_f, int (active), bmpx_track_id));
      mark_dirty (bmpx_track_id);
      Track x (get_track (bmpx_track_id));
      signal_track_modified_.emit (x);
    }

    guint64
    Library::get_track_id (Track const& track) const
    {
      RowV rows;
      if (m_db_flags & DB_FLAG_USING_HAL)
      {
        static boost::format
          select_f ("SELECT %s FROM track WHERE %s='%s' AND %s='%s' AND %s='%s';");

        m_db->get (rows, (select_f  % "id" 
                                    % attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id
                                    % track.volume_udi.get()
                                    % attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id
                                    % track.device_udi.get()
                                    % attrinfo[ATTRIBUTE_VOLUME_RELATIVE_PATH].id
                                    % mprintf ("%q", track.volume_relative_path.get().c_str())).str());
      }
      else
      {
        static boost::format
          select_f ("SELECT %s FROM track WHERE %s='%s';");

        m_db->get (rows, (select_f  % "id" 
                                    % attrinfo[ATTRIBUTE_LOCATION].id
                                    % mprintf ("%q", track.location.get().c_str())).str());
      }

      if (rows.size() && (rows[0].count( "id" ) != 0))
      {
        return boost::get <guint64> (rows[0].find ("id")->second);
      }

      return 0;
    }

    guint64
    Library::get_track_mtime (Track const& track) const
    {
      RowV rows;
      if (m_db_flags & DB_FLAG_USING_HAL)
      {
        static boost::format
          select_f ("SELECT %s FROM track WHERE %s='%s' AND %s='%s' AND %s='%s';");

        m_db->get (rows, (select_f  % attrinfo[ATTRIBUTE_MTIME].id
                                    % attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id
                                    % track.volume_udi.get()
                                    % attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id
                                    % track.device_udi.get()
                                    % attrinfo[ATTRIBUTE_VOLUME_RELATIVE_PATH].id
                                    % mprintf ("%q", track.volume_relative_path.get().c_str())).str());
      }
      else
      {
        static boost::format
          select_f ("SELECT %s FROM track WHERE %s='%s';");

        m_db->get (rows, (select_f  % attrinfo[ATTRIBUTE_MTIME].id
                                    % attrinfo[ATTRIBUTE_LOCATION].id
                                    % mprintf ("%q", track.location.get().c_str())).str());
      }

      if (rows.size() && (rows[0].count (attrinfo[ATTRIBUTE_MTIME].id) != 0))
      {
        return boost::get <guint64> (rows[0].find (attrinfo[ATTRIBUTE_MTIME].id)->second);
      }

      return 0;
    }
 
    AddOp
    Library::insert (std::string const& uri, std::string const& insert_path)
    {
      std::string type;        
   
      try{ 
          if (!Audio::typefind (filename_from_uri (uri), type)) throw MetadataReadError (_("Unable to determine File Type"));
        }
      catch (Glib::ConvertError & cxe)
        {
            throw MetadataReadError (_("Unable to convert URI to Filename"));
        }

      debug("library","Location: %s", uri.c_str());

      Row row;
      row.insert (std::make_pair (attrinfo[ATTRIBUTE_TYPE].id, type));
      row.insert (std::make_pair (attrinfo[ATTRIBUTE_LOCATION].id, uri));

      string insert_path_value = insert_path;

#ifdef HAVE_HAL
      if (m_db_flags & DB_FLAG_USING_HAL)
      {
            try{
                HAL::Volume const& volume (hal->get_volume_for_uri (uri));

                row.insert (std::make_pair (attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id,
                              string (volume.volume_udi)));

                row.insert (std::make_pair (attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id,
                              string (volume.device_udi)));

                row.insert (std::make_pair (attrinfo[ATTRIBUTE_VOLUME_RELATIVE_PATH].id,
                              string (filename_from_uri (uri).substr (volume.mount_point.length()))));

                insert_path_value = insert_path.substr (volume.mount_point.length());
            }
            catch (HAL::Exception & cxe)
            {
                return LIBRARY_ADD_NOOP;
            }
          catch (Glib::ConvertError & cxe)
            {
                throw MetadataReadError (_("Unable to convert URI to Filename"));
            }
      }
      else 
      {
        insert_path_value = insert_path;
      }
#endif //HAVE_HAL

      row.insert (std::make_pair ("insert_path", Variant (string (insert_path))));
      row.insert (std::make_pair (attrinfo[ATTRIBUTE_NEW_ITEM].id, guint64 (1)));

      struct stat fstat;
      if (!stat (filename_from_uri (uri).c_str(), &fstat))
      {
        row.insert (std::make_pair (attrinfo[ATTRIBUTE_MTIME].id, guint64 (fstat.st_mtime)));
      }
      else
      {
        int errsv = errno;
        g_warning ("%s: Couldn't stat file: '%s'", G_STRLOC, g_strerror (errsv));
      }

      time_t mtime = get_track_mtime (row);
      if ((mtime != 0) && (mtime == fstat.st_mtime))
      {
        return LIBRARY_ADD_NOCHANGE;
      }

      try{
          metadata_get_taglib (uri, row, type);
        }
      catch (NoMetadataTaglibError & cxe)
#if 0
        {
          metadata_get_gst (uri, row);
        }
      catch (NoMetadataGstError & cxe)
#endif
        {
          throw MetadataReadError (_("Couldn't read Metadata"));
        }
      catch (Glib::ConvertError & cxe)
        {
          throw MetadataReadError (_("Unable to convert URI to Filename"));;
        }

      char const* track_set_f ("INSERT INTO track (%s) VALUES (%s);");

      /* Now let's prepare the SQL */
      string column_names;
      column_names.reserve (0x400);

      string column_values;
      column_values.reserve (0x400);

      try{
        guint64 artist_j = get_track_artist_id (row);
        guint64 album_j = get_album_id (row, get_album_artist_id (row));

        Row::iterator i;
        unsigned int last = 0;

        if (m_db_flags & DB_FLAG_USING_HAL)
          last = ATTRIBUTE_VOLUME_RELATIVE_PATH;
        else
          last = ATTRIBUTE_TYPE;

        for (unsigned int n = 0; n <= last; ++n)
        {
          if (!attrinfo[n].use)
            continue;

          i = row.find (attrinfo[n].id);
          if (i != row.end())
          {
            switch (attrinfo[n].type)
            {
              case VALUE_TYPE_STRING:
                column_values += mprintf ("'%q'", boost::get <string> (i->second).c_str());
                break;

              case VALUE_TYPE_INT:
                column_values += mprintf ("'%llu'", boost::get <guint64> (i->second));
                break;

              case VALUE_TYPE_REAL:
                column_values += mprintf ("'%f'", boost::get <double> (i->second));
                break;
            }

            column_names += std::string (attrinfo[n].id) + ",";
            column_values += ",";
          }
        }

        column_names += "artist_j, album_j, insert_path";
        column_values += mprintf ("'%llu'", artist_j) + "," + mprintf ("'%llu'", album_j) + ",";

#ifdef HAVE_HAL
        if (m_db_flags & DB_FLAG_USING_HAL)
          column_values += mprintf ("'%q'", insert_path_value.c_str());
        else
          column_values += mprintf ("'%q'", insert_path.c_str());
#else
          column_values += mprintf ("'%q'", insert_path.c_str());
#endif //HAVE_HAL

        m_db->exec_sql (mprintf (track_set_f, column_names.c_str(), column_values.c_str()));
        return LIBRARY_ADD_IMPORT;
      }
    catch (SqlConstraintError & cxe)
      {
        Track track (row);
        guint64 id = get_track_id (track);
        if( id == 0 )
        {
            return LIBRARY_ADD_ERROR;
        }
        remove (id);
        m_db->exec_sql (mprintf (track_set_f, column_names.c_str(), column_values.c_str()));
        guint64 new_id = m_db->last_insert_rowid ();
        m_db->exec_sql (mprintf ("UPDATE track SET id = '%llu' WHERE id = '%llu';", id, new_id));
        return LIBRARY_ADD_UPDATE;
      }
    catch (SqlExceptionC & cxe)
      {
        debug ("library", "SQL Error: %s", cxe.what());
        return LIBRARY_ADD_ERROR;
      }

      return LIBRARY_ADD_NOOP;
    }

    void
    Library::remove (Track const& track)
    {
      if (m_db_flags & DB_FLAG_USING_HAL)
      {
        char const* delete_track_f ("DELETE FROM track WHERE volume_udi='%q' AND device_udi='%q' AND volume_relative_path='%q';");
        m_db->exec_sql( mprintf( delete_track_f,  track.volume_udi.get().c_str(),
                                                  track.device_udi.get().c_str(),
                                                  track.volume_relative_path.get().c_str() ) );
      }
      else
      {
        char const* delete_track_f ("DELETE FROM track WHERE location='%llu';");
        m_db->exec_sql( mprintf( delete_track_f, track.location.get().c_str() ) );
      }
    }

    void
    Library::remove (guint64 bmpx_track_id)
    {
      static boost::format delete_track_f ("DELETE FROM track WHERE id='%llu';");
      m_db->exec_sql ((delete_track_f % bmpx_track_id).str()); 
    }

    void
    Library::vacuum_nonexistent ()
    {
      m_track_cache.clear ();
      m_track_cache_tags.clear ();

      RowV rows;
      m_db->get (rows, "SELECT * FROM track_v"); 

      signal_vacuum_begin_.emit (rows.size());
      for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i) 
      {
        Track track (*i);
        if (!file_test (filename_from_uri (track.location.get()), FILE_TEST_EXISTS))
        {
          remove (track.bmpx_track_id);
        }
        signal_vacuum_step_.emit ();
      }
      signal_vacuum_end_.emit ();
    } 

    void
    Library::vacuum_tables ()
    {
      try{
        m_track_cache.clear ();
        m_track_cache_tags.clear ();

        typedef std::set <guint64> IdSet;
        static boost::format delete_f ("DELETE FROM %s WHERE id = '%llu';");

        signal_vacuum_begin_.emit (3);

        RowV rows;
        IdSet idset1;
        IdSet idset2;

        idset1.clear();
        rows.clear();
        m_db->get (rows, "SELECT DISTINCT artist_j FROM track;");
        for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i)
          idset1.insert (boost::get<guint64>(i->find ("artist_j")->second));
        idset2.clear();
        rows.clear();
        m_db->get (rows, "SELECT DISTINCT id FROM artist;");
        for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i)
          idset2.insert (boost::get<guint64>(i->find ("id")->second));

        for (IdSet::const_iterator i = idset2.begin(); i != idset2.end(); ++i)
        {
          if (idset1.find (*i) == idset1.end())
            m_db->exec_sql ((delete_f % "artist" % (*i)).str());
        }
        signal_vacuum_step_.emit ();


        idset1.clear();
        rows.clear();
        m_db->get (rows, "SELECT DISTINCT album_j FROM track;");
        for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i)
          idset1.insert (boost::get<guint64>(i->find ("album_j")->second));
        idset2.clear();
        rows.clear();
        m_db->get (rows, "SELECT DISTINCT id FROM album;");
        for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i)
          idset2.insert (boost::get<guint64>(i->find ("id")->second));

        for (IdSet::const_iterator i = idset2.begin(); i != idset2.end(); ++i)
        {
          if (idset1.find (*i) == idset1.end())
            m_db->exec_sql ((delete_f % "album" % (*i)).str());
        }
        signal_vacuum_step_.emit ();


        idset1.clear();
        rows.clear();
        m_db->get (rows, "SELECT DISTINCT album_artist_j FROM album;");
        for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i)
          idset1.insert (boost::get<guint64>(i->find ("album_artist_j")->second));
        idset2.clear();
        rows.clear();
        m_db->get (rows, "SELECT DISTINCT id FROM album_artist;");
        for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i)
          idset2.insert (boost::get<guint64>(i->find ("id")->second));

        for (IdSet::const_iterator i = idset2.begin(); i != idset2.end(); ++i)
        {
          if (idset1.find (*i) == idset1.end())
            m_db->exec_sql ((delete_f % "album_artist" % (*i)).str());
        }
        signal_vacuum_step_.emit ();

        signal_vacuum_end_.emit ();
      }
      catch (SqlExceptionC & cxe)
      {
        g_critical ("%s: Critical Error: Problem during vacuumize: %s", G_STRLOC, cxe.what());
      }
    }

    void
    Library::query (Query const& query, RowV & rows, bool reopen_db) const
    {
      m_db->get ("track_v", rows, query, reopen_db); 
    }

    void
    Library::volume_insert_paths (StrV & insert_paths) const
    {
      RowV rows;
      m_db->get (rows, "SELECT DISTINCT insert_path FROM track;");

      for (RowV::const_iterator i = rows.begin (); i != rows.end(); ++i)
      {
        Row const& row (*i); 
        Row::const_iterator const& insert_path_i (row.find ("insert_path"));
        insert_paths.push_back (boost::get <string> (insert_path_i->second));
      }
    }

#ifdef HAVE_HAL
    void
    Library::vacuum_nonexistent ( std::string const& volume_udi,
                                  std::string const& device_udi,
                                  std::string const& insert_path )
    {
      static char const* select_volume_tracks_f
        ("SELECT * FROM track_v WHERE volume_udi='%q' AND device_udi='%q';");

      static char const* select_volume_tracks_insert_path_f
        ("SELECT * FROM track_v WHERE volume_udi='%q' AND device_udi='%q' AND insert_path='%q';");

      static char const* delete_track_f
        ("DELETE FROM track WHERE id='%llu';");

      RowV rows;
      
      if (insert_path.size())
        m_db->get (rows, (mprintf (select_volume_tracks_insert_path_f
                                   , volume_udi.c_str()
                                   , device_udi.c_str()
                                   , insert_path.c_str())));
      else
        m_db->get (rows, (mprintf (select_volume_tracks_f
                                   , volume_udi.c_str() 
                                   , device_udi.c_str())));

      signal_vacuum_begin_.emit (rows.size());
      for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i) 
      {
        try{
            Track track (*i);
            if (!file_test (filename_from_uri (track.location.get()), FILE_TEST_EXISTS))
            {
              m_db->exec_sql (mprintf (delete_track_f, boost::get<guint64> (i->find ("id")->second)));
            }
        }
        catch (...)
        {
            debug("library","%s: Track construction failed for ID %llu", G_STRLOC, (boost::get<guint64> (i->find ("id")->second)));
        }
        signal_vacuum_step_.emit ();
      }
      signal_vacuum_end_.emit ();
      vacuum_tables ();
    } 

    void
    Library::remove_volume  (std::string const& volume_udi,
                             std::string const& device_udi)
    {
      char const* remove_tracks_f ("DELETE FROM track WHERE volume_udi='%q' AND device_udi='%q'");
      m_db->exec_sql (mprintf (remove_tracks_f, volume_udi.c_str(), device_udi.c_str()));
      vacuum_tables ();
      signal_modified_.emit ();
    }

    void
    Library::remove_path    (std::string const& volume_udi,
                             std::string const& device_udi,
                             std::string const& insert_path)
    {
      char const* remove_tracks_f ("DELETE FROM track WHERE volume_udi='%q' AND device_udi='%q' AND insert_path='%q'");
      m_db->exec_sql (mprintf (remove_tracks_f, volume_udi.c_str(), device_udi.c_str(), insert_path.c_str()));
      vacuum_tables ();
      signal_modified_.emit ();
    }


    void
    Library::volume_insert_paths (std::string const& volume_udi,
                                  std::string const& device_udi,
                                  StrV & insert_paths) const
    {
      char const* insert_paths_f ("SELECT DISTINCT insert_path FROM track WHERE %q = '%q' AND %q = '%q';");
      RowV rows;
      m_db->get (rows, (mprintf (insert_paths_f , attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id
                                                , volume_udi.c_str()
                                                , attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id
                                                , device_udi.c_str())));

      for (RowV::const_iterator i = rows.begin (); i != rows.end(); ++i)
      {
        Row const& row = *i;
        Row::const_iterator const& insert_path_i (row.find ("insert_path"));
        insert_paths.push_back (boost::get <string> (insert_path_i->second));
      }
    }

    bool
    Library::volume_exists (std::string const& volume_udi,
                            std::string const& device_udi) const
    {
      static boost::format f_volume_query ("SELECT DISTINCT %s, %s FROM %s WHERE %s='%s' AND %s='%s';");
      std::string query ((f_volume_query  % attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id
                                          % attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id
                                          % "track"  
                                          % attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id
                                          % volume_udi
                                          % attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id
                                          % device_udi).str());

      return (m_db->exec_sql (query) > 0);
    }
#endif //HAVE_HAL

    DB::RowV 
    Library::get_albums () const 
    {
      DB::RowV rows;
      m_db->get (rows,  "SELECT * FROM album JOIN album_artist ON album.album_artist_j = album_artist.id "
                        "WHERE " /*album IS NOT NULL AND*/ "mb_album_artist IS NOT NULL "
                        //"GROUP BY mb_album_artist, mb_album_artist_sort_name, mb_release_date, album "
                        "ORDER BY ifnull(mb_album_artist_sort_name, mb_album_artist), ifnull(mb_release_date, album) ", true);
      return rows;
    }

    DB::RowV 
    Library::get_albums_by_artist (guint64 bmpx_album_artist_id) const 
    {
      RowV r;
      static boost::format sql_f
              ( "SELECT * FROM album JOIN album_artist ON album.album_artist_j = album_artist.id "
                "WHERE " /*album IS NOT NULL AND*/ "mb_album_artist IS NOT NULL AND album_artist_j = '%llu' "
                //"GROUP BY mb_album_artist_sort_name, mb_album_artist, mb_release_date, album "
                "ORDER BY ifnull(mb_album_artist_sort_name, mb_album_artist), ifnull(mb_release_date, album);");
      m_db->get (r, (sql_f % bmpx_album_artist_id).str(), true);
      return r;
    }

    DB::RowV
    Library::get_album (guint64 bmpx_album_id) const
    {
      RowV r;
      static boost::format sql_f
              ( "SELECT * FROM album JOIN album_artist ON album.album_artist_j = album_artist.id "
                "WHERE " /*album IS NOT NULL AND*/ "mb_album_artist IS NOT NULL AND album.id = '%llu' ");
      m_db->get (r, (sql_f % bmpx_album_id).str(), true);
      return r;
    }

    DB::RowV
    Library::get_album_tracks (guint64 album_id) const
    {
      DB::RowV r;
      AttributeV attributes; 
      attributes.push_back (Attribute (EXACT, "album_j", album_id)); 
      DB::Query query (attributes);
      query.set_suffix ("ORDER BY tracknumber");
      m_db->get ("track_v", r, query, true); 
      return r;
    }

    DB::RowV
    Library::get_artists () const
    {
      DB::RowV  r;
      DB::Query q;
      q.set_prefix ("DISTINCT");
      q.set_suffix ("ORDER BY mb_album_artist_sort_name");
      m_db->get ("album_artist", r, q, true); 
      return r;
    }

    DB::RowV
    Library::get_artist (guint64 bmpx_album_artist_id) const
    {
      DB::RowV        r;
      DB::Query       q;
      DB::AttributeV  v;

      v.push_back (Attribute (EXACT, "id", bmpx_album_artist_id)); 
      q.add_attr_v (v);
      m_db->get ("album_artist", r, q, true); 
      return r;
    }

    void 
    Library::get (DB::RowV & r, std::string const& sql, bool newConn)
    {
      m_db->get (r, sql, newConn);
    }

    GHashTable*
    Library::get_metadata_hash_table_for_uri (string const& uri) const
    {
      Row row;
      get_metadata (uri, row);

      row.insert (std::make_pair ("location", uri));
      GHashTable* table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, GDestroyNotify (Util::free_gvalue_ptr));
                                                
      for (Row::const_iterator i = row.begin(); i != row.end(); ++i)
      {
        GValue* value = g_new0 (GValue, 1);
        switch (ValueType(i->second.which()))
        {
          case VALUE_TYPE_STRING:
              g_value_init (value, G_TYPE_STRING);
              g_value_set_string (value, (boost::get <string> (i->second)).c_str());
              break;

          case VALUE_TYPE_INT:
              g_value_init (value, G_TYPE_UINT64);
              g_value_set_uint64 (value, (boost::get <guint64> (i->second)));
              break;

          case VALUE_TYPE_REAL:
              g_value_init (value, G_TYPE_DOUBLE);
              g_value_set_double (value, (boost::get <double> (i->second)));
              break;
        }
        g_hash_table_insert (table, g_strdup (i->first.c_str()), value);
      }

      g_assert (table != NULL);
      return table;
    }

    void
    Library::get_metadata (string const& uri, DB::Row & row, bool reopen_db) const
    {
      Bmp::URI u (uri);

      if (u.get_protocol() != URI::PROTOCOL_FILE)
      {
        try{
            metadata_get_gst (uri, row);
            return;
          }
        catch (NoMetadataGstError& cxe)
          {
            throw MetadataReadError (_("Couldn't read Metadata"));
          }
      }

      AttributeV a;
      RowV rows;

#ifdef HAVE_HAL

      if (m_db_flags & DB_FLAG_USING_HAL)
      {
        try{
            HAL::Volume const& volume (hal->get_volume_for_uri (uri));
            string volume_relative_path = filename_from_uri (uri).substr (volume.mount_point.length());
            a.push_back (Attribute (EXACT, attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id, volume.volume_udi)); 
            a.push_back (Attribute (EXACT, attrinfo[ATTRIBUTE_VOLUME_RELATIVE_PATH].id, volume_relative_path)); 
            query (a, rows, reopen_db);
          }
        catch (HAL::Exception & cxe)
          {
            throw MetadataReadError (_("Failure to acquire HAL information for given uri!"));
          }
      }
      else
      {
        a.push_back (Attribute (EXACT, attrinfo[ATTRIBUTE_LOCATION].id, uri)); 
        query (a, rows, reopen_db);
      }

#else //HAVE_HAL
      a.push_back (Attribute (EXACT, attrinfo[ATTRIBUTE_LOCATION].id, uri)); 
      query (a, rows, reopen_db);
#endif //!HAVE_HAL

      if (rows.empty())
      {
        std::string type;        

        if (!Audio::typefind (filename_from_uri (uri), type))
          throw MetadataReadError (_("Unable to determine File Type"));

        try{
            metadata_get_taglib (uri, row, type);
          }
        catch (NoMetadataTaglibError& cxe)
          {
            metadata_get_gst (uri, row);
          }

#ifdef HAVE_HAL
        if ((m_db_flags & DB_FLAG_USING_HAL) && (hal->is_initialized()))
        {
              try{

                  HAL::Volume volume (hal->get_volume_for_uri (uri));

                  row.insert (std::make_pair (attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id,
                                string (volume.volume_udi)));

                  row.insert (std::make_pair (attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id,
                                string (volume.device_udi)));

                  row.insert (std::make_pair (attrinfo[ATTRIBUTE_VOLUME_RELATIVE_PATH].id,
                                string (filename_from_uri (uri).substr (volume.mount_point.length()))));

              }
              catch (HAL::Exception & cxe)
              {
                throw MetadataReadError (_("Failure to acquire HAL information for given uri!"));
              }
        }
#endif //HAVE_HAL

        if (row.find ("type") == row.end())
        {
          row.insert (std::make_pair ("type", type));
        }

        if (row.find ("title") == row.end())
        {
          std::string basename = filename_to_utf8 (path_get_basename (filename_from_uri (uri)));
          row.insert (std::make_pair ("title", basename)); 
        }
      }
      else
      {
        row = rows[0];
      }
    }

    Track
    Library::get_track (guint64 bmpx_track_id, bool reopen_db)
    {
      TrackMap::iterator i = m_track_cache.find (bmpx_track_id);
      if (i != m_track_cache.end())
      {
        UidSet::iterator i2 = m_track_cache_tags.find (bmpx_track_id);

        if (i2 == m_track_cache_tags.end())
          return i->second;
        else
          m_track_cache_tags.erase (i2);

        m_track_cache.erase (i);
      }

      RowV v;
      m_db->get (v, (boost::format ("SELECT * FROM track_v WHERE id = %llu;") % bmpx_track_id).str(), reopen_db);

      Track x (v[0]);
      m_track_cache.insert (std::make_pair (bmpx_track_id, x)); 
      return x; 
    }

    void
    Library::mark_dirty (guint64 id)
    {
      m_track_cache_tags.insert (id);
    }

    Track
    Library::get_track (string const& uri, bool reopen_db) const
    {
      Row row;
      get_metadata (uri, row, reopen_db);

      Bmp::URI u (uri);
      Bmp::URI::Protocol p (u.get_protocol ());
      if ((p == Bmp::URI::PROTOCOL_HTTP || 
          p == Bmp::URI::PROTOCOL_MMS || 
          p == Bmp::URI::PROTOCOL_MMSU || 
          p == Bmp::URI::PROTOCOL_MMST ||
          p == Bmp::URI::PROTOCOL_CDDA) && (m_db_flags & DB_FLAG_USING_HAL))
      {
        return Track (row, 0);
      }
      else
      {
        return Track (row);
      }
    }

    StrV
    Library::get_asins () const
    {
      StrV v;
      RowV rows;
      m_db->get (rows, "SELECT DISTINCT asin FROM album WHERE asin NOT NULL;");
      for (RowV::const_iterator i = rows.begin(); i != rows.end(); ++i)
      {
        v.push_back (boost::get <string> (i->find ("asin")->second));
      }
      return v;
    }

    void
    Library::recreate_db ()
    {
      static boost::format library_f ("library-%d-%d-%d");
      static boost::format library_dot_f ("%d:%d:%d");

      delete m_db;

      m_db = new Database ((library_f  % MLIB_VERSION_CUR 
                                       % MLIB_VERSION_REV
                                       % MLIB_VERSION_AGE).str(), BMP_PATH_DATA_DIR, DB_TRUNCATE);

      m_db->exec_sql ("CREATE TABLE IF NOT EXISTS meta (version STRING, flags INTEGER DEFAULT 0);");
      m_db_flags = 0;

#ifdef HAVE_HAL
      //XXX: This is going to be a user defined option
      m_db_flags |= DB_FLAG_USING_HAL;
#endif //HAVE_HAL

      static boost::format sql_meta_version_f ("INSERT INTO %s (version, flags) VALUES ('%s', '%d');");
      m_db->exec_sql ((sql_meta_version_f  % "meta" 
                                           % (library_dot_f  % MLIB_VERSION_CUR 
                                                             % MLIB_VERSION_REV 
                                                             % MLIB_VERSION_AGE).str()
                                                             % m_db_flags).str());

    }

    void
    Library::modify (TrackV & v, TrackModFlags flags, bool display_progress) 
    {
      if (display_progress)
      {
        signal_modify_start_.emit (v.size ());
      }

      for (TrackV::iterator i = v.begin (); i != v.end (); ++i)
      {
        Track & track = *i;
        AttributeV a;

        if (flags & TRACK_MOD_UPDATE_DB)
        {
          Row row;

          if (track.artist)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_ARTIST].id,
                                          track.artist.get().c_str()));
          }

          if (track.mb_artist_id)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_MB_ARTIST_ID].id,
                                          track.mb_artist_id.get().c_str()));
          }

          if (track.mb_artist_sort_name)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_MB_ARTIST_SORTNAME].id,
                                          track.mb_artist_sort_name.get().c_str()));
          }

          if (track.album)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_ALBUM].id,
                                          track.album.get().c_str()));
          }
    
          if (track.mb_album_id)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_MB_ALBUM_ID].id,
                                          track.mb_album_id.get().c_str()));
          }

          if (track.asin)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_ASIN].id,
                                          track.asin.get().c_str()));
          }

          if (track.mb_album_artist)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST].id,
                                          track.mb_album_artist.get().c_str()));
          }

          if (track.mb_album_artist_id)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_ID].id,
                                          track.mb_album_artist_id.get().c_str()));
          } 

          if (track.mb_album_artist_sort_name)
          {
            row.insert (std::make_pair (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_SORTNAME].id,
                                          track.mb_album_artist_sort_name.get().c_str()));
          } 

          // XXX: We handle release date here because it needs to be set as a row value for finding or creating the appropriate album
          if (track.mb_release_date)
          {
            int y = 0, m = 0, d = 0;
            sscanf (track.mb_release_date.get().c_str(), "%04d-%02d-%02d", &y, &m, &d);

            row.insert (std::make_pair (attrinfo[ATTRIBUTE_MB_RELEASE_DATE].id, track.mb_release_date.get()));
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_DATE].id,
                                      guint64 (y))); 
          }
          else if (track.date)
          {
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_DATE].id,
                                      guint64 (track.date.get()))); 
          }

          guint64 artist_j (get_track_artist_id (row));
          guint64 album_j (get_album_id (row, get_album_artist_id (row)));
          a.push_back (Attribute (std::string ("artist_j"), artist_j));
          a.push_back (Attribute (std::string ("album_j"), album_j));

          if (track.title)
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_TITLE].id,
                           (track.title.get().c_str()))); 

          if (track.genre)
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_GENRE].id,
                           (track.genre.get().c_str()))); 

          if (track.comment)
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_COMMENT].id,
                           (track.comment.get().c_str()))); 

          if (track.tracknumber)
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_TRACK].id,
                          guint64 (track.tracknumber.get()))); 

          if (track.rating)
             a.push_back (Attribute (attrinfo[ATTRIBUTE_RATING].id,
                          guint64 (track.rating.get()))); 

          if (track.count)
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_COUNT].id,
                          guint64 (track.count.get()))); 

          if (track.puid)
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_MUSICIP_PUID].id,
                           (track.puid.get().c_str()))); 

          if (track.mb_track_id)
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_MB_TRACK_ID].id,
                           (track.mb_track_id.get().c_str()))) ;

          if (track.new_item)
            a.push_back (Attribute  (attrinfo[ATTRIBUTE_NEW_ITEM].id,
                            guint64 (track.new_item.get())));

          static boost::format where_f (" WHERE id = %llu "); 
          m_db->set ("track", (where_f % track.bmpx_track_id).str(), a);
        }

        if (flags & TRACK_MOD_RETAG)
        {
          try{
              metadata_set_taglib (track);
            }
          catch (MetadataWriteError & cxe)
            {
              g_critical ("%s: Couldn't write metadata to file %s", G_STRLOC, track.location.get().c_str());
            }
        }

        if (flags & TRACK_MOD_UPDATE_DB)
        {
          struct stat fstat;
          if (stat (filename_from_uri (track.location.get()).c_str(), &fstat))
          {
            int errsv = errno;
            g_warning ("%s: Couldn't stat file: '%s'", G_STRLOC, g_strerror (errsv));
          }
          else
          {
            static boost::format stat_f ("UPDATE track SET %s='%llu' WHERE id = %llu "); 
            m_db->exec_sql ((stat_f % get_attribute_info (ATTRIBUTE_MTIME).id
                                    % guint64 (fstat.st_mtime)
                                    % track.bmpx_track_id).str());
          }
        }

        mark_dirty (track.bmpx_track_id);

        if (display_progress)
        {
          signal_modify_step_.emit (); 
        }

        signal_track_modified_.emit (track);
      }   

      if (display_progress)
      {
        signal_modify_end_.emit ();
      }
    }
  
    void
    Library::begin_txn ()
    {
      m_db->exec_sql ("BEGIN;");
    }

    void
    Library::close_txn ()
    {
      m_db->exec_sql ("COMMIT;");
    }

    void
    Library::cancel_txn ()
    {
      m_db->exec_sql ("ROLLBACK;");
    }

    DBFlags 
    Library::get_db_flags ()
    {
      if (!m_db->table_exists ("meta"))
      {
        recreate_db ();
      }

      RowV rows;
      m_db->get ("meta", rows);
      return DBFlags (boost::get<guint64>(rows[0].find("flags")->second));
    }

    bool
    Library::open ()   
    {
      static boost::format library_f ("library-%d-%d-%d");

      try {
          m_db = new Database ((library_f  % MLIB_VERSION_CUR 
                                           % MLIB_VERSION_REV
                                           % MLIB_VERSION_AGE).str(),
                                             BMP_PATH_DATA_DIR, DB_OPEN);
          return true;
        }
      catch (DbInitError& cxe)
        {
          return false; 
        }
    }

    void 
    Library::init ()
    {
      m_db_flags = get_db_flags ();

      static boost::format
        artist_table_f ("CREATE TABLE IF NOT EXISTS artist "
                          "(id INTEGER PRIMARY KEY AUTOINCREMENT, '%s' TEXT, '%s' TEXT, '%s' TEXT, "
                          "UNIQUE ('%s', '%s', '%s'));");

      m_db->exec_sql ((artist_table_f  % attrinfo[ATTRIBUTE_ARTIST].id
                                            % attrinfo[ATTRIBUTE_MB_ARTIST_ID].id
                                            % attrinfo[ATTRIBUTE_MB_ARTIST_SORTNAME].id
                                            % attrinfo[ATTRIBUTE_ARTIST].id
                                            % attrinfo[ATTRIBUTE_MB_ARTIST_ID].id
                                            % attrinfo[ATTRIBUTE_MB_ARTIST_SORTNAME].id).str());

      static boost::format
        album_artist_table_f ("CREATE TABLE IF NOT EXISTS album_artist "
                                "(id INTEGER PRIMARY KEY AUTOINCREMENT, '%s' TEXT, '%s' TEXT, "
                                "'%s' TEXT, '%s' INTEGER, UNIQUE ('%s', '%s', '%s', '%s'));");

      m_db->exec_sql ((album_artist_table_f  % attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST].id
                                                  % attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_ID].id
                                                  % attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_SORTNAME].id
                                                  % attrinfo[ATTRIBUTE_IS_MB_ALBUM_ARTIST].id
                                                  % attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST].id
                                                  % attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_ID].id
                                                  % attrinfo[ATTRIBUTE_IS_MB_ALBUM_ARTIST].id
                                                  % attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_SORTNAME].id).str());

      static boost::format
        album_table_f ("CREATE TABLE IF NOT EXISTS album "
                        "(id INTEGER PRIMARY KEY AUTOINCREMENT, '%s' TEXT, '%s' TEXT, "
                        "'%s' TEXT, '%s' TEXT, '%s' INTEGER, UNIQUE "
                        "('%s', '%s', '%s', '%s', '%s'));");

      m_db->exec_sql ((album_table_f % attrinfo[ATTRIBUTE_ALBUM].id
                                        % attrinfo[ATTRIBUTE_MB_ALBUM_ID].id
                                        % attrinfo[ATTRIBUTE_MB_RELEASE_DATE].id
                                        % attrinfo[ATTRIBUTE_ASIN].id
                                        % "album_artist_j"
                                        % attrinfo[ATTRIBUTE_ALBUM].id
                                        % attrinfo[ATTRIBUTE_MB_ALBUM_ID].id
                                        % attrinfo[ATTRIBUTE_MB_RELEASE_DATE].id
                                        % attrinfo[ATTRIBUTE_ASIN].id
                                        % "album_artist_j").str());

      unsigned int last = 0;

      if (m_db_flags & DB_FLAG_USING_HAL)
        last = ATTRIBUTE_VOLUME_RELATIVE_PATH;
      else
        last = ATTRIBUTE_TYPE;

      StrV columns;

      for (unsigned int n = 0; n <= last; ++n)
      {
        if (attrinfo[n].use)
        {
          std::string column (attrinfo[n].id);
          switch (attrinfo[n].type)
          {
            case VALUE_TYPE_STRING:
              column += (" TEXT DEFAULT NULL ");
              break;

            case VALUE_TYPE_INT:
              column += (" INTEGER DEFAULT NULL ");
              break;

            case VALUE_TYPE_REAL:
              column += (" REAL DEFAULT NULL ");
              break;
          }
          columns.push_back (column);
        }
      }

      std::string column_names (::Bmp::Util::stdstrjoin (columns, ", "));
#ifdef HAVE_HAL
      static boost::format
        track_table_f ("CREATE TABLE IF NOT EXISTS track (id INTEGER PRIMARY KEY AUTOINCREMENT, %s, %s, %s, %s, "
                       "UNIQUE (%s, %s, %s));");

      m_db->exec_sql ((track_table_f % column_names
                                     % "artist_j INTEGER NOT NULL"   // track artist information 
                                     % "album_j INTEGER NOT NULL"    // album + album artist
                                     % "insert_path TEXT"            // VRP for HAL usage, otherwise full URI
                                     % attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id
                                     % attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id
                                     % attrinfo[ATTRIBUTE_VOLUME_RELATIVE_PATH].id).str());
#else //!HAVE_HAL
      static boost::format
        track_table_f ("CREATE TABLE IF NOT EXISTS track (id INTEGER PRIMARY KEY AUTOINCREMENT, %s, %s, %s, %s, "
                       "UNIQUE (%s));");

      m_db->exec_sql ((track_table_f % column_names
                                     % "artist_j INTEGER NOT NULL"   // track artist information 
                                     % "album_j INTEGER NOT NULL"    // album + album artist
                                     % "insert_path TEXT"            // VRP for HAL usage, otherwise full URI
                                     % attrinfo[ATTRIBUTE_LOCATION].id).str());
#endif //HAVE_HAL

      if(m_db->table_exists("track_v"))
      {
        m_db->exec_sql ("DROP VIEW track_v");
      }

      m_db->exec_sql

      // FIXME: This gives an ugly reply on .schema track_v since sqlite3 saves all blanks/spaces

      (     "CREATE VIEW IF NOT EXISTS track_v AS SELECT "

                          "track.*, "

                          "artist.artist AS artist, "
                          "artist.mb_artist_id AS mb_artist_id, "
                          "artist.mb_artist_sort_name AS mb_artist_sort_name, "

                          "album.album AS album, "
                          "album.mb_album_id AS mb_album_id, "
                          "album.mb_release_date AS mb_release_date, "
                          "album.asin AS asin, "
                          "album.album_artist_j AS album_artist_j, "

                          "album_artist.mb_album_artist AS mb_album_artist, "
                          "album_artist.mb_album_artist_id AS mb_album_artist_id, "
                          "album_artist.mb_album_artist_sort_name AS mb_album_artist_sort_name, "

                          "album_artist.id AS bmpx_album_artist_id "

                          "FROM track "

                          "CROSS JOIN artist ON track.artist_j = artist.id " 
                          "CROSS JOIN album  ON track.album_j = album.id " 
                          "CROSS JOIN album_artist ON album.album_artist_j = album_artist.id;"
      );

      //// Tags    
      
      static boost::format
        tag_table_f ("CREATE TABLE IF NOT EXISTS tag "
                      "(id INTEGER PRIMARY KEY AUTOINCREMENT, '%s' TEXT NOT NULL)");

      static boost::format
        tag_datum_table_f ("CREATE TABLE IF NOT EXISTS %s "
                            "(id INTEGER PRIMARY KEY AUTOINCREMENT, tag_id INTEGER NOT NULL, fki INTEGER NOT NULL)");

      m_db->exec_sql ((tag_table_f  % "tag").str()); 
      m_db->exec_sql ((tag_datum_table_f  % "artist_tag").str()); 
      m_db->exec_sql ((tag_datum_table_f  % "title_tag").str()); 

      debug("library","%s: Library initalized OK.", G_STRLOC);
    }

    Library::Library ()
    {
      Util::dir_for_each_entry (BMP_TAGLIB_PLUGIN_DIR, sigc::mem_fun (this, &Bmp::Library::load_taglib_plugin));  
    }

    Library::~Library ()
    {
      delete m_db;
    }

    /// Signals
    Library::SignalTrackModified&
    Library::signal_track_modified ()
    {
      return signal_track_modified_;
    }

    Library::SignalModifyStart &
    Library::signal_modify_start ()
    { 
      return signal_modify_start_;
    }

    Library::SignalModifyStep &
    Library::signal_modify_step ()
    { 
      return signal_modify_step_;
    }

    Library::SignalModifyEnd &
    Library::signal_modify_end ()
    {
      return signal_modify_end_;
    }

    Library::SignalModified &
    Library::signal_modified ()
    {
      return signal_modified_;
    }

    Library::SignalVacuumBegin &
    Library::signal_vacuum_begin ()
    {
      return signal_vacuum_begin_;
    }

    Library::SignalVacuumStep &
    Library::signal_vacuum_step ()
    {
      return signal_vacuum_step_;
    }

    Library::SignalVacuumEnd &
    Library::signal_vacuum_end ()
    {
      return signal_vacuum_end_;
    }

    bool
    Library::load_taglib_plugin (std::string const& path)
    {
      enum
      {
        LIB_BASENAME1,
        LIB_BASENAME2,
        LIB_PLUGNAME,
        LIB_SUFFIX
      };

      const std::string type = "taglib_plugin";

      std::string basename (path_get_basename (path));
      std::string pathname (path_get_dirname  (path));

      if (!is_module (basename))
        return false;

      std::vector<std::string> subs;
      split (subs, basename, is_any_of ("_."));
      std::string name  = type + std::string("_") + subs[LIB_PLUGNAME];
      std::string mpath = Module::build_path (BMP_TAGLIB_PLUGIN_DIR, name);

      Module module (mpath, ModuleFlags (0)); 
      if (!module)
      {
        debug("library","Taglib plugin load FAILURE '%s': %s", mpath.c_str (), module.get_last_error().c_str());
        return false;
      }

      void * _plugin_version;
      if (module.get_symbol ("_plugin_version", _plugin_version))
      {
        int version = *((int*)(_plugin_version));
        if (version != PLUGIN_VERSION)
        {
          debug("library","Taglib plugin is of old version %d, not loading ('%s')", version, mpath.c_str ());
          return false;
        }
      }

      module.make_resident();
      debug("library","Taglib plugin load SUCCESS '%s'", mpath.c_str ());

      void * _plugin_has_accessors = 0; //dummy
      if (module.get_symbol ("_plugin_has_accessors", _plugin_has_accessors))
      {
        TaglibPluginPtr plugin = TaglibPluginPtr (new TaglibPlugin());

        if (!g_module_symbol (module.gobj(), "_get", (gpointer*)(&plugin->get)))
          plugin->get = NULL;

        if (!g_module_symbol (module.gobj(), "_set", (gpointer*)(&plugin->set)))
          plugin->set = NULL;
        else
          debug("library","     >> Plugin '%s' can write metadata", subs[LIB_PLUGNAME].c_str());

        if (g_module_symbol (module.gobj(), "_mimetypes", (gpointer*)(&plugin->mimetypes)))
        {
          const char ** mimetypes (plugin->mimetypes());
          while (*mimetypes)
          {
            debug("library","     >> Plugin registers for %s", *mimetypes); 
            m_taglib_plugins.insert (std::make_pair (std::string (*mimetypes), plugin));

            ++mimetypes;
          }
        }
        else
        {
          m_taglib_plugins_keeper.push_back (plugin);
        }
      }

      return false;
    }

    Album::Album (Row const& row)
    {
      m_row = row;

      Row::const_iterator i, end = row.end();

      i = row.find (attrinfo[ATTRIBUTE_ALBUM].id);
      if (i != end)
        album = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ID].id);
      if (i != end)
        mb_album_id = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_RELEASE_DATE].id);
      if (i != end)
        mb_release_date = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_ASIN].id);
      if (i != end)
        asin = boost::get <string> (i->second);

      i = row.find ("album_artist_j");
      if (i != end)
        bmpx_album_artist_id = boost::get <guint64> (i->second);

      i = row.find ("id");
      if (i != end)
        bmpx_album_id = boost::get <guint64> (i->second);

      //FIXME: Well, whacky..

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST].id);
      if (i != end)
        album_artist = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_ID].id);
      if (i != end)
        mb_album_artist_id = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_SORTNAME].id);
      if (i != end)
        mb_album_artist_sort_name = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_IS_MB_ALBUM_ARTIST].id);
      if (i != end)
        is_mb_album_artist = true; 
      else
        is_mb_album_artist = false; 
    }

    Track::Track (Row const& row, bool use_hal)
    {
      m_row = row;

      Row::const_iterator i, end = row.end ();

#ifdef HAVE_HAL
      if (use_hal)
      {
        i = row.find (attrinfo[ATTRIBUTE_HAL_VOLUME_UDI].id);
        if (i != end)
          volume_udi = boost::get <string> (i->second);

        i = row.find (attrinfo[ATTRIBUTE_HAL_DEVICE_UDI].id);
        if (i != end)
          device_udi = boost::get <string> (i->second);

        i = row.find (attrinfo[ATTRIBUTE_VOLUME_RELATIVE_PATH].id);
        if (i != end)
          volume_relative_path = boost::get <string> (i->second);

        std::string mount_point (hal->get_mount_point_for_volume (volume_udi.get().c_str(), device_udi.get().c_str()));
        location = filename_to_uri (build_filename (mount_point.c_str(), volume_relative_path.get().c_str()));
      }
      else
      {
        i = row.find (attrinfo[ATTRIBUTE_LOCATION].id);
        if (i != end)
          location = boost::get <string> (i->second);
      }
#else
      i = row.find (attrinfo[ATTRIBUTE_LOCATION].id);
      if (i != end)
        location = boost::get <string> (i->second);
#endif

      i = row.find (attrinfo[ATTRIBUTE_MB_TRACK_ID].id);
      if (i != end)
        mb_track_id = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_TITLE].id);
      if (i != end)
        title = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_GENRE].id);
      if (i != end)
        genre = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_COMMENT].id);
      if (i != end)
        comment = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_TRACK].id);
      if (i != end)
        tracknumber = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_TIME].id);
      if (i != end)
        duration = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_DATE].id);
      if (i != end)
        date = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_RATING].id);
      if (i != end)
        rating = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_COUNT].id);
      if (i != end)
        count = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MTIME].id);
      if (i != end)
        mtime = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_BITRATE].id);
      if (i != end)
        bitrate = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_SAMPLERATE].id);
      if (i != end)
        samplerate = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_ARTIST].id);
      if (i != end)
        artist = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ARTIST_ID].id);
      if (i != end)
        mb_artist_id = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ARTIST_SORTNAME].id);
      if (i != end)
        mb_artist_sort_name = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST].id);
      if (i != end)
        mb_album_artist = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_ID].id);
      if (i != end)
        mb_album_artist_id = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_SORTNAME].id);
      if (i != end)
        mb_album_artist_sort_name = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_ALBUM].id);
      if (i != end)
        album = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ID].id);
      if (i != end)
        mb_album_id = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_RELEASE_DATE].id);
      if (i != end)
        mb_release_date = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MUSICIP_PUID].id);
      if (i != end)
        puid = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_HASH].id);
      if (i != end)
        hash = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_ASIN].id);
      if (i != end)
        asin = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_TYPE].id);
      if (i != end)
        type = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_NEW_ITEM].id);
      if (i != end)
        new_item = boost::get <guint64> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_ACTIVE].id);
      if (i != end)
        active = boost::get <guint64> (i->second);
      else
        active = true; 

      i = row.find ("id");
      if (i != end)
        bmpx_track_id = boost::get <guint64> (i->second); 

      i = row.find ("artist_j");
      if (i != end)
        bmpx_artist_id = boost::get <guint64> (i->second); 

      i = row.find ("album_j");
      if (i != end)
        bmpx_album_id = boost::get <guint64> (i->second); 
    }

    std::string
    Track::get_canonical_filename (std::string const& ext) const
    {
      std::string filename;    

      static boost::format
        track_canonical_name_1_f ("%s - %s - %02llu. %s.%s");

      static boost::format
        track_canonical_name_2_f ("%s - %s - %02llu. %s (%s).%s");

      if (mb_album_artist && album && tracknumber && title && type)
      {
        if (mb_artist_id && mb_album_artist_id && (mb_artist_id != mb_album_artist_id))
        {
          filename = ((track_canonical_name_2_f
                            % mb_album_artist.get()
                            % album.get()
                            % tracknumber.get()
                            % title.get()
                            % artist.get()
                            % (ext.size() ? ext : ::Bmp::Audio::get_ext_for_type (type.get()))).str());
        }
        else
        {
          filename = ((track_canonical_name_1_f
                            % mb_album_artist.get()
                            % album.get()
                            % tracknumber.get()
                            % title.get()
                            % (ext.size() ? ext : ::Bmp::Audio::get_ext_for_type (type.get()))).str());
        }
      }
      return filename_from_utf8 (filename); 
    }
      
    Artist::Artist (Bmp::DB::Row const& row) 
    {
      m_row = row;

      Row::const_iterator i, end = row.end ();

      i = row.find (attrinfo[ATTRIBUTE_ARTIST].id);
      if (i != end)
        artist = boost::get <string> (i->second);
         
      i = row.find (attrinfo[ATTRIBUTE_MB_ARTIST_ID].id);
      if (i != end)
        mb_artist_id = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ARTIST_SORTNAME].id);
      if (i != end)
        mb_artist_sort_name = boost::get <string> (i->second);

      i = row.find ("id");
      if (i != end)
        bmpx_artist_id = boost::get <guint64> (i->second);
    }

    AlbumArtist::AlbumArtist (Bmp::DB::Row const& row) 
    {
      m_row = row;

      Row::const_iterator i, end = row.end ();

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST].id);
      if (i != end)
        album_artist = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_ID].id);
      if (i != end)
        mb_album_artist_id = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_MB_ALBUM_ARTIST_SORTNAME].id);
      if (i != end)
        mb_album_artist_sort_name = boost::get <string> (i->second);

      i = row.find (attrinfo[ATTRIBUTE_IS_MB_ALBUM_ARTIST].id);
      if (i != end)
        is_mb_album_artist = true; 
      else
        is_mb_album_artist = false; 

      i = row.find ("id");
      if (i != end)
        bmpx_album_artist_id = boost::get <guint64> (i->second);
    }

    Bmp::AttributeInfo
    get_attribute_info (Bmp::AttributeId id)
    {
      return attrinfo[id];
    }

} // namespace Bmp
