/*******************************************************************************
*                         Goggles Music Manager                                *
********************************************************************************
*           Copyright (C) 2006-2011 by Sander Jansen. All Rights Reserved      *
*                               ---                                            *
* This program is free software: you can redistribute it and/or modify         *
* it under the terms of the GNU General Public License as published by         *
* the Free Software Foundation, either version 3 of the License, or            *
* (at your option) any later version.                                          *
*                                                                              *
* This program is distributed in the hope that it will be useful,              *
* but WITHOUT ANY WARRANTY; without even the implied warranty of               *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                *
* GNU General Public License for more details.                                 *
*                                                                              *
* You should have received a copy of the GNU General Public License            *
* along with this program.  If not, see http://www.gnu.org/licenses.           *
********************************************************************************/
#include "GMTrackDatabase.h"
#include "GMTag.h"
#include "GMTrackList.h"
#include "GMList.h"
#include "GMSource.h"

/// For listing default genres
#include <id3v1genres.h>

#define GOGGLESMM_DATABASE_SCHEMA_VERSION 2009
/*

*****************************************************
    Goggles Music Manager Database Schema v2009
*****************************************************

    TABLE tracks
        id          INTEGER NOT NULL *
        path        INTEGER
        mrl         TEXT
        title       TEXT NOT NULL
        time        INTEGER
        no          INTEGER
        year        INTEGER
        playcount   INTEGER
        playdate    INTEGER
        importdate  INTEGER
        album       INTEGER NOT NULL
        genre       INTEGER
        rating      INTEGER
        artist      INTEGER NOT NULL
        replay_gain REAL
        replay_peak REAL

    TABLE genres
        id        INTEGER NOT NULL *
        name      TEXT NOT NULL UNIQUE

    TABLE artists
        id        INTEGER NOT NULL *
        name      TEXT NOT NULL UNIQUE

    TABLE albums
        id        INTEGER NOT NULL *
        name      TEXT
        artists   INTEGER NOT NULL
        year      INTEGER NOT NULL
        replay_gain REAL
        replay_peak REAL

    TABLE playlists
        id        INTEGER NOT NULL *
        name      TEXT

    TABLE playlist_tracks
        playlist  INTEGER
        track     INTEGER
        queue     INTEGER


*/

static FXString last_dir;
static FXint    last_path=-1;



const FXchar create_streams[]="CREATE TABLE streams ( id INTEGER NOT NULL, "
                                                     "url TEXT, "
                                                     "description TEXT, "
                                                     "genre INTEGER, "
                                                     "bitrate INTEGER, "
                                                     "rating INTEGER, "
                                                     "PRIMARY KEY (id) );";

const FXchar create_tracks[]="CREATE TABLE tracks ( id INTEGER NOT NULL, "
                                                   "path INTEGER, "
                                                   "mrl TEXT, "
                                                   "title TEXT NOT NULL, "
                                                   "time INTEGER, "
                                                   "no INTEGER, "
                                                   "year INTEGER, "
                                                   "playcount INTEGER, "
                                                   "playdate INTEGER, "
                                                   "importdate INTEGER, "
                                                   "album INTEGER NOT NULL, "
                                                   "genre INTEGER, "
                                                   "rating INTEGER, "
                                                   "artist INTEGER NOT NULL, "
                                                   "replay_gain REAL, "
                                                   "replay_peak REAL, "
                                                   "PRIMARY KEY (id) );";

const FXchar create_genres[]="CREATE TABLE genres ( id INTEGER NOT NULL, name TEXT NOT NULL UNIQUE, PRIMARY KEY (id) );";
const FXchar create_albums[]="CREATE TABLE albums ( id INTEGER NOT NULL, "
                                                   "name TEXT NOT NULL, "
                                                   "artist INTEGER NOT NULL, "
                                                   "year INTEGER, "
                                                   "replay_gain REAL, "
                                                   "replay_peak REAL, "
                                                   "PRIMARY KEY (id) );";
const FXchar create_artists[]="CREATE TABLE artists ( id INTEGER NOT NULL, name TEXT NOT NULL UNIQUE, PRIMARY KEY (id) );";


const FXchar create_playlists[]="CREATE TABLE playlists ( id INTEGER NOT NULL, name TEXT, PRIMARY KEY (id) );";
const FXchar create_playlist_tracks[]="CREATE TABLE playlist_tracks ( playlist INTEGER NOT NULL, track INTEGER NOT NULL, queue INTEGER );";

const FXchar create_directories[]="CREATE TABLE directories ( id INTEGER PRIMARY KEY,parent INTEGER,name TEXT,path TEXT);";


const FXchar create_trigger_diradd[]="CREATE TRIGGER directories_insert AFTER INSERT ON directories "
                                     "BEGIN "
                                     "UPDATE directories SET path = "
                                     "CASE WHEN parent ISNULL THEN '/' "
                                     "ELSE (SELECT path FROM directories WHERE id = new.parent) || parent || '/' "
                                     "END "
                                     "WHERE id = new.id; "
                                     "END;";

// Recursive Trigger not supported by SQLite
//const FXchar create_trigger_dirdel[]="CREATE TRIGGER directories_delete AFTER DELETE ON directories "
//                                     "BEGIN "
//                                     "DELETE FROM directories WHERE id IN "
//                                     "(SELECT id FROM directories WHERE path LIKE old.path || old.id || '/%' ); "
//                                     "END;";


GMTrackDatabase::GMTrackDatabase()  {
  }

GMTrackDatabase::~GMTrackDatabase() {
  }


FXbool GMTrackDatabase::clearTracks(FXbool removeplaylists){

  if (!db.execute("DELETE FROM tracks;"))
    return false;

  if (!db.execute("DELETE FROM artists"))
    return false;

  if (!db.execute("DELETE FROM albums;"))
    return false;

  if (!db.execute("DELETE FROM playlist_tracks;"))
    return false;

  if (removeplaylists) {
    if (!db.execute("DELETE FROM playlists;"))
      return false;
    }

  if (!db.execute("DELETE FROM directories;"))
    return false;

  last_path=-1;

  if (!db.execute("DELETE FROM genres WHERE id NOT IN (SELECT DISTINCT(genre) FROM tracks) AND id NOT IN (SELECT DISTINCT(genre) FROM streams);"))
    return false;

  return true;
  }


void GMTrackDatabase::clear(){
  db.clearTables();
  }

FXbool GMTrackDatabase::init(const FXString & database,FXbool checkversion) {
  if (!db.open(database))
    goto error;

  if (checkversion && db.getVersion()>GOGGLESMM_DATABASE_SCHEMA_VERSION) {
    if (FXMessageBox::question(FXApp::instance(),MBOX_OK_CANCEL,fxtr("Database Error"),fxtr("An incompatible (future) version of the database was found.\nThis usually happens when you try to downgrade to a older version of GMM\nPress OK to continue and reset the database (all information will be lost!).\nPress Cancel to quit now and leave the database as is."))==MBOX_CLICKED_CANCEL)
      return false;
    }

  if (!setup_tables())
    goto error;

  if (!setup_queries())
    goto error;

  return true;
error:
  FXMessageBox::error(FXApp::instance(),MBOX_OK,fxtr("Fatal Error"),fxtr("Goggles Music Manager was unable to open the database.\nThe database may have been corrupted. Please remove ~/.goggles/goggles.db to try again.\nif the error keeps occuring, please file an issue at http://code.google.com/p/gogglesmm"));
  return false;
  }







void GMTrackDatabase::setup_insert_queries() {
  }


void GMTrackDatabase::clear_insert_queries() {
  }


void GMTrackDatabase::beginDelete() {
  delete_artist.compile(db,"DELETE FROM artists WHERE id == ?;");
  delete_genre.compile(db,"DELETE FROM genres WHERE id == ?;");
  delete_album.compile(db,"DELETE FROM albums WHERE id == ?;");
  delete_track.compile(db,"DELETE FROM tracks WHERE id == ?;");
  delete_track_artist.compile(db,"DELETE FROM tracks WHERE artist == ? OR album IN ( SELECT id FROM albums WHERE artist == ?) ;");
  delete_track_album.compile(db,"DELETE FROM tracks WHERE album == ?;");
  delete_albums_artist.compile(db,"DELETE FROM albums WHERE artist == ?;");
  delete_track_from_playlist.compile(db,"DELETE FROM playlist_tracks WHERE track == ? AND playlist == ?;");
  delete_track_from_playlists.compile(db,"DELETE FROM playlist_tracks WHERE track == ?;");
  delete_artist_from_playlists.compile(db,"DELETE FROM playlist_tracks WHERE track IN ( SELECT id FROM tracks WHERE artist == ? OR album IN (SELECT album FROM albums WHERE artist == ?));");
  delete_album_from_playlists.compile(db,"DELETE FROM playlist_tracks WHERE track IN (SELECT id FROM tracks WHERE album == ?);");
  query_track_no.compile(db,"SELECT no FROM tracks WHERE id == ?;");
  }

void GMTrackDatabase::endDelete(FXbool vac){
  if (vac) vacuum();
  delete_artist.clear();
  delete_genre.clear();
  delete_track.clear();
  delete_track_artist.clear();
  delete_track_album.clear();
  delete_album.clear();
  delete_albums_artist.clear();
  delete_artist_from_playlists.clear();
  delete_album_from_playlists.clear();
  delete_track_from_playlist.clear();
  delete_track_from_playlists.clear();
  query_track_no.clear();
  }

void GMTrackDatabase::beginEdit() {
  update_track_genre_id.compile(db,"UPDATE tracks SET genre = ? WHERE id == ?;");
  update_mrl.compile(db,"UPDATE tracks SET path = ?, mrl = ? WHERE id == ?;");
  update_artist_album_id.compile(db,"UPDATE albums SET artist = ? WHERE id == ?;");
  update_track_title.compile(db,"UPDATE tracks SET title = ? WHERE id == ?;");
  update_track_album_id.compile(db,"UPDATE tracks SET album = ? WHERE id == ?;");
  update_track_genre.compile(db,"UPDATE tracks SET genre = ? WHERE genre == ?;");
  update_track_artist.compile(db,"UPDATE tracks SET artist = ? WHERE id == ?;");
  update_track_no.compile(db,"UPDATE tracks SET no = ? WHERE id == ?;");
  update_track_disc.compile(db,"UPDATE tracks SET no = ((no&65535)|(?<<16)) WHERE id == ?;");
  update_track_track.compile(db,"UPDATE tracks SET no = ((no&1048560)|?) WHERE id == ?;");
  update_track_importdate.compile(db,"UPDATE tracks SET importdate = ? WHERE id == ?;");


  update_track_year.compile(db,"UPDATE tracks SET year = ? WHERE id == ?;");
  update_track.compile(db,"UPDATE tracks SET title = ?, time = ?, no = ?, year = ?, album = ?, genre = ?, artist = ?, replay_gain = ?, replay_peak = ?, importdate = ? WHERE id == ?;");
  update_album.compile(db,"UPDATE albums SET year = ?, replay_gain = ?, replay_peak = ? WHERE id == ?;");

  query_track_no.compile(db,"SELECT no FROM tracks WHERE id == ?;");

  query_album_existing.compile(db,"SELECT id FROM albums WHERE artist == ? AND name == (SELECT albums.name FROM albums WHERE albums.id == (SELECT album FROM tracks WHERE id == ? ));");
  insert_album_existing.compile(db,"INSERT INTO albums ( name, artist, year, replay_gain, replay_peak) SELECT ?, ?, year,replay_gain,replay_peak FROM albums WHERE id == (SELECT album FROM tracks WHERE id == ? ) ;");
  insert_album_existing_name.compile(db,"INSERT INTO albums ( name, artist, year, replay_gain, replay_peak) SELECT name, ?, year,replay_gain,replay_peak FROM albums WHERE id == (SELECT album FROM tracks WHERE id == ? ) ;");
  query_artist_album.compile(db,"SELECT artist FROM albums WHERE id == (SELECT album FROM tracks WHERE id == ?);");


  }


void GMTrackDatabase::endEdit(FXbool vac){
  if (vac) vacuum();
  update_track_genre_id.clear();
  update_mrl.clear();
  update_artist_album_id.clear();
  update_track_title.clear();
  update_track_album_id.clear();
  update_track_genre.clear();
  update_track_artist.clear();
  update_track_no.clear();
  update_track_disc.clear();
  update_track_track.clear();
  update_track_year.clear();
  update_track.clear();
  update_album.clear();
  update_track_importdate.clear();
  query_track_no.clear();
  query_album_existing.clear();
  insert_album_existing.clear();
  insert_album_existing_name.clear();
  query_artist_album.clear();
  }







#include "GMPlayerManager.h"

#ifdef DEBUG
namespace FX {
  extern FXlong fxgetticks();
}

static FXlong total_ticks = 0;
static FXlong total_has_ticks = 0;
#endif


FXbool GMTrackDatabase::hasTrack(const FXString & dir,const FXString & filename,FXint & trackid) {
  FXint pathid=-1;
  trackid=-1;
#ifdef DEBUG
  FXlong start=fxgetticks();
#endif
  if (last_dir==dir) {
    if (last_path!=-1) {
      query_filename.setParameter(0,last_path);
      query_filename.setParameter(1,filename);
      query_filename.execute();
      query_filename.getResult(0,trackid);
      query_filename.reset();
      }
    }
  else if (findPath(dir,pathid)){
    query_filename.setParameter(0,pathid);
    query_filename.setParameter(1,filename);
    query_filename.execute();
    query_filename.getResult(0,trackid);
    query_filename.reset();
    last_path=pathid;
    last_dir=dir;
    }
  else {
    last_path=-1;
    last_dir=dir;
    }
#ifdef DEBUG
  FXlong end=fxgetticks();
  total_has_ticks+=(end-start);
#endif
  if (trackid==-1) {
    return false;
    }
  return true;
  }

FXbool GMTrackDatabase::hasTrack(const FXString & filename,FXint & trackid) {
  return hasTrack(FXPath::directory(filename),FXPath::name(filename),trackid);
  }


FXbool GMTrackDatabase::findPath(const FXString & path,FXint & id) {
  if (!FXPath::isTopDirectory(path)) {
    FXString parent = FXPath::upLevel(path);
    if (!findPath(parent,id)) return false;
    FXString name   = FXPath::name(path);
    FXint    nid    = -1;
    query_path_component.setParameter(0,id);
    query_path_component.setParameter(1,name);
    query_path_component.execute();
    query_path_component.getResult(0,nid);
    query_path_component.reset();
    if (nid==-1) return false;
    id=nid;
    }
  else {
    id=-1;
    query_root_component.setParameter(0,path);
    query_root_component.execute();
    query_root_component.getResult(0,id);
    query_root_component.reset();
    if (id==-1) return false;
    }
  return true;
  }

void GMTrackDatabase::addPath(const FXString & path,FXint & id) {
  if (!FXPath::isTopDirectory(path)) {
    FXString parent = FXPath::upLevel(path);
    FXString name   = FXPath::name(path);
    FXint    nid    = -1;

    addPath(parent,id);

    query_path_component.setParameter(0,id);
    query_path_component.setParameter(1,name);
    query_path_component.execute();
    query_path_component.getResult(0,nid);
    query_path_component.reset();
    if (nid==-1) {
      insert_path_component.setParameter(0,id);
      insert_path_component.setParameter(1,name);
      insert_path_component.execute();
      insert_path_component.reset();
      id = db.lastInsertRow();
      }
  else {
      id = nid;
      }
    }
  else {
    id=-1;
    query_root_component.setParameter(0,path);
    query_root_component.execute();
    query_root_component.getResult(0,id);
    query_root_component.reset();
    if (id==-1) {
      insert_root_component.setParameter(0,path);
      insert_root_component.execute();
      insert_root_component.reset();
      id = db.lastInsertRow();
      }
    }
  }



FXbool GMTrackDatabase::insertTrack(const GMTrack & track,FXint & result){
  FXint artist=-1;
  FXint album=-1;
  FXint genre=0;
  FXint path=-1;
  result=0;

#ifdef DEBUG
  FXlong start = fxgetticks();
#endif

  if (!queryArtist(artist,track.album_artist,true)) return FALSE;
  if (!queryAlbum(album,track.album,artist,track.year,track.album_gain,track.album_peak,true)) return FALSE;
  if (!track.genre.empty() && !queryGenre(genre,track.genre,true)) return FALSE;

  /// Insert Track Artist if needed
  if (!track.artist.empty() && track.artist!=track.album_artist && !queryArtist(artist,track.artist,true)) return false;


  static int cached=0;
  static int newentry=0;

  FXString dir=FXPath::directory(track.mrl);

  if (last_path>=0 && last_dir==dir){
    path=last_path;
    cached++;
    }
  else {
    addPath(dir,path);
    last_path=path;
    last_dir=dir;
    newentry++;
    }

  /// Needed for hasTrack
  try {
    insert_track.setParameter(0,path);

    if (path>=0)
      insert_track.setParameter(1,FXPath::name(track.mrl));
    else
      insert_track.setParameter(1,track.mrl);

    insert_track.setParameter(2,track.title);
    insert_track.setParameter(3,track.time);
    insert_track.setParameter(4,track.no);
    insert_track.setParameter(5,track.year);
    insert_track.setParameter(6,FXThread::time());
    insert_track.setParameter(7,album);
    insert_track.setParameter(8,genre);
    insert_track.setParameter(9,artist);
    insert_track.setParameter(10,track.track_gain);
    insert_track.setParameter(11,track.track_peak);
    insert_track.execute();
    result = db.lastInsertRow();
//    if (result>0 && trackdict_updated) {
//      trackdict.insert(track.mrl.text(),(void*)(FXival)result);
//      }
    insert_track.reset();
    }
  catch (FXExecuteException & e){
    fxmessage("Exception: %s\n",e.what());
    result=0;
    return FALSE;
    }

#ifdef DEBUG
  FXlong end = fxgetticks();
  total_ticks += (end-start);
  fxmessage("insert: %ld\n",end-start);
#endif
  return true;
  }


FXbool GMTrackDatabase::updateTrack(FXint id,const GMTrack & track){
  FXint album=-1;
  FXint genre=-1;
  FXint artist=-1;
  FXuint no=track.no;

  if (!queryArtist(artist,track.album_artist,true)) return FALSE;
  if (!updateAlbum(album,track,artist)) return false;

//  if (!queryAlbum(album,track.album,artist,track.year,track.album_gain,track.album_peak,true)) return FALSE;
  if (!track.genre.empty() && !queryGenre(genre,track.genre,true)) return FALSE;
  if (!track.artist.empty() && track.artist!=track.album_artist && !queryArtist(artist,track.artist,true)) return false;

  try {
    if (track.no==0)  {
      query_track_no.setParameter(0,id);
      query_track_no.execute();
      query_track_no.getResult(0,no);
      query_track_no.reset();
      }
    update_track.setParameter(0,track.title);
    update_track.setParameter(1,track.time);
    update_track.setParameter(2,no);
    update_track.setParameter(3,track.year);
    update_track.setParameter(4,album);
    update_track.setParameter(5,genre);
    update_track.setParameter(6,artist);
    update_track.setParameter(7,track.track_gain);
    update_track.setParameter(8,track.track_peak);
    update_track.setParameter(9,FXThread::time());
    update_track.setParameter(10,id);
    update_track.execute();
    update_track.reset();
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }



FXbool GMTrackDatabase::insertStream(const FXString & url,const FXString & description,const FXString & genre){
  FXString query;
  FXint genreid=0;
  if (!genre.empty() && !queryGenre(genreid,genre,true)) return false;
  query.format("INSERT INTO streams VALUES (NULL,\"%s\",\"%s\",%d,0,0);",url.text(),description.text(),genreid);
  return db.execute(query);
  }


/// Insert Playlist into database
FXbool GMTrackDatabase::insertPlaylist(const FXString & name,FXint & id) {
  try {
    GMQuery query(&db,"INSERT INTO playlists VALUES ( NULL, ? );");
    query.setParameter(0,name);
    query.execute();
    id = db.lastInsertRow();
    query.reset();
    }
  catch (FXCompileException &){
    return false;
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }



/// Insert Track in Playlist
FXbool GMTrackDatabase::insertTrackInPlaylist(FXint playlist,FXint & track) {
  FXint queue=0;
  try {
    GMQuery queu_query(&db,"SELECT MAX(queue) FROM playlist_tracks WHERE playlist == ?;");
    queu_query.setParameter(0,playlist);
    queu_query.execute();
    queu_query.getResult(0,queue);
    queu_query.reset();

    // Increment
    queue++;

    GMQuery insert_query(&db,"INSERT INTO playlist_tracks VALUES ( ? , ? , ?);");
    insert_query.setParameter(0,playlist);
    insert_query.setParameter(1,track);
    insert_query.setParameter(2,queue);
    insert_query.execute();

    }
  catch (FXCompileException & e){
    fxmessage("Compile Exception: %s\n",e.what());
    return false;
    }
  catch (FXExecuteException & e){
    fxmessage("Execute Exception: %s\n",e.what());
    return false;
    }
  return true;
  }


/// Insert Track in Playlist
FXbool GMTrackDatabase::insertTrackInPlaylist(FXint playlist,FXIntList & tracks) {
  FXint queue=0;
  try {
    query_playlist_queue.setParameter(0,playlist);
    query_playlist_queue.execute();
    query_playlist_queue.getResult(0,queue);
    query_playlist_queue.reset();

    // Increment
    queue++;

#ifdef DEBUG
    FXlong start = fxgetticks();
#endif

    db.begin();
    for (int i=0;i<tracks.no();i++){
      insert_track_playlist.setParameter(0,playlist);
      insert_track_playlist.setParameter(1,tracks[i]);
      insert_track_playlist.setParameter(2,queue++);
      insert_track_playlist.execute();
      insert_track_playlist.reset();
      }
    db.commit();

#ifdef DEBUG
    FXlong end = fxgetticks();
    fxmessage("insertTrackInPlaylist %ld\n",end-start);
#endif
    }
  catch (FXCompileException & e){
    fxmessage("Compile Exception: %s\n",e.what());
    return false;
    }
  catch (FXExecuteException & e){
    fxmessage("Execute Exception: %s\n",e.what());
    return false;
    }
  return true;
  }


/// Insert Track in Playlist
FXbool GMTrackDatabase::updateTrackPlaylists(FXint playlist,FXIntList & tracks) {
  FXint queue=1;
  try {
    GMQuery update_queue(&db,"UPDATE playlist_tracks SET queue = ? WHERE playlist == ? AND track == ?;");
    for (int i=0;i<tracks.no();i++){
      update_queue.setParameter(0,queue++);
      update_queue.setParameter(1,playlist);
      update_queue.setParameter(2,tracks[i]);
      update_queue.execute();
      update_queue.reset();
      }
    }
  catch (FXCompileException & e){
    fxmessage("Compile Exception: %s\n",e.what());
    return false;
    }
  catch (FXExecuteException & e){
    fxmessage("Execute Exception: %s\n",e.what());
    return false;
    }
  return true;
  }


/// List Playlists
FXbool GMTrackDatabase::listPlaylists(FXIntList & ids){
  FXint id=0;
  try {
    GMQuery query(&db,"SELECT id FROM playlists;");
    while(query.execute()){
      query.getResult(0,id);
      ids.append(id);
      }
    query.reset();
    }
  catch (FXCompileException & e){
    fxmessage("Compile Exception: %s\n",e.what());
    ids.clear();
    return false;
    }
  catch (FXExecuteException & e){
    fxmessage("Execute Exception: %s\n",e.what());
    ids.clear();
    return false;
    }
  return true;
  }


FXbool GMTrackDatabase::setStreamFilename(FXint id,const FXString & filename){
  FXString query;
  query.format("UPDATE streams SET url = \"%s\" WHERE id==%d;",filename.text(),id);
  db.execute(query);
  return true;
  }

FXbool GMTrackDatabase::setStreamDescription(FXint id,const FXString & description){
  FXString query;
  query.format("UPDATE streams SET description = \"%s\" WHERE id==%d;",description.text(),id);
  db.execute(query);
  return true;
  }

FXbool GMTrackDatabase::setStreamBitrate(FXint id,FXint rate){
  FXString query;
  query.format("UPDATE streams SET bitrate = \"%d\" WHERE id==%d;",rate,id);
  db.execute(query);
  return true;
  }


FXbool GMTrackDatabase::setStreamGenre(FXint id,const FXString & name){
  FXint genreid=-1;
  FXString query;
  try {

    /// Query for existing genre
    query_genre.setParameter(0,name);
    query_genre.execute();
    query_genre.getResult(0,genreid);
    query_genre.reset();

    if (genreid==-1) {
      /// Insert Genre
      insert_genre.setParameter(0,name);
      insert_genre.execute();
      genreid = db.lastInsertRow();
      insert_genre.reset();
      }

    query.format("UPDATE streams SET genre = %d WHERE id==%d;",genreid,id);
    db.execute(query);
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }




FXbool GMTrackDatabase::removeStream(FXint track) {
  FXString query;
  query.format("DELETE FROM streams WHERE id = \"%d\"",track);
  db.execute(query);
  return true;
  }


void GMTrackDatabase::setTrackFilename(FXint id,const FXString & filename){
  FXint path=-1;
  FXString dir=FXPath::directory(filename);
  addPath(dir,path);
  last_path=path;
  last_dir=dir;
  try {
    update_mrl.setParameter(0,path);
    if (path>=0)
      update_mrl.setParameter(1,FXPath::name(filename));
    else
      update_mrl.setParameter(1,filename);
    update_mrl.setParameter(2,id);
    update_mrl.execute();
    update_mrl.reset();
    }
  catch (FXExecuteException &){
    }
  }

FXString GMTrackDatabase::getTrackFilename(FXint id){
  FXString filename;
  FXString path;
  FXint    pathid;
  try {
    query_track_filename.setParameter(0,id);
    query_track_filename.execute();
    query_track_filename.getResult(0,filename);
    query_track_filename.getResult(1,pathid);
    query_track_filename.reset();

    query_path.setParameter(0,pathid);
    while(query_path.execute()) {
        if (!path.empty() && path!="/") path+=PATHSEPSTRING;
        path+=query_path.getResult(0);
        }
    query_path.reset();
    }
  catch (FXExecuteException &){
    return FXString::null;
    }

  if (path.empty())
      return filename;
  else
      return path + PATHSEPSTRING + filename;
  }

/// Return list of filenames
void GMTrackDatabase::getTrackFilenames(const FXIntList & tracks,FXStringList & filenames){
  filenames.no(tracks.no());
  for (FXint i=0;i<tracks.no();i++) {
    filenames[i]=getTrackFilename(tracks[i]);
    }
  }


FXbool GMTrackDatabase::getTrackFilenames(FXIntList & ids,FXStringList & files,FXLongList & importdates,const FXString & root) {
  FXString dir;
  FXint path;
  FXString filename;
  FXint last_path=-1;
  FXint id,rootid;
  FXlong date;

  FXString query;
  GMQuery list;
  try {
    if (!root.empty()) {
      if (!findPath(root,rootid)) return true;
      list.compile(db,"SELECT tracks.id,tracks.path,tracks.mrl,tracks.importdate FROM tracks, directories WHERE tracks.path == directories.id AND (directories.id == " + GMStringVal(rootid) + " OR directories.path LIKE '%/" + GMStringVal(rootid) +"/%');");
      }
    else {
      list.compile(db,"SELECT id,path,mrl,importdate FROM tracks ORDER BY path");
      }
    while(list.execute()){
      list.getResult(0,id);
      list.getResult(1,path);
      list.getResult(2,filename);
      list.getResult(3,date);
      if (path==-1) {
        files.append(filename);
        ids.append(id);
        importdates.append(date);
        }
      else {
        if (path!=last_path){
          dir = getPathName(path);
          last_path = path;
          }
        if (dir.empty())
          files.append(filename);
        else
          files.append(dir+PATHSEPSTRING+filename);
        ids.append(id);
        importdates.append(date);
        }
      }
    list.reset();
    }
  catch (FXCompileException &){
    list.reset();
    return false;
    }
  catch (FXExecuteException &){
    list.reset();
    return false;
    }
  return true;
  }


FXint GMTrackDatabase::getNumTracks() {
  FXint total=0;
  try {
    GMQuery query(&db,"SELECT COUNT(*) FROM tracks;");
    query.execute();
    query.getResult(0,total);
    query.reset();
    }
  catch (FXCompileException &){
    return 0;
    }
  catch (FXExecuteException &){
    return 0;
    }
  return total;
  }

FXint GMTrackDatabase::getNumStreams() {
  FXint total=0;
  try {
    GMQuery query(&db,"SELECT COUNT(*) FROM streams;");
    query.execute();
    query.getResult(0,total);
    query.reset();
    }
  catch (FXCompileException &){
    return 0;
    }
  catch (FXExecuteException &){
    return 0;
    }
  return total;
  }


FXint GMTrackDatabase::getTotalTime() {
  FXint total;
  try {
    GMQuery query(&db,"SELECT SUM(time) FROM tracks;");
    query.execute();
    query.getResult(0,total);
    query.reset();
    }
  catch (FXCompileException &){
    return 0;
    }
  catch (FXExecuteException &){
    return 0;
    }
  return total;
  }


FXint GMTrackDatabase::getNumArtists() {
  FXint total;
  try {
    GMQuery query(&db,"SELECT COUNT(id) FROM artists;");
    query.execute();
    query.getResult(0,total);
    query.reset();
    }
  catch (FXCompileException &){
    return 0;
    }
  catch (FXExecuteException &){
    return 0;
    }
  return total;
  }


FXint GMTrackDatabase::getNumAlbums() {
  FXint total;
  try {
    GMQuery query(&db,"SELECT COUNT(id) FROM albums;");
    query.execute();
    query.getResult(0,total);
    query.reset();
    }
  catch (FXCompileException &){
    return 0;
    }
  catch (FXExecuteException &){
    return 0;
    }
  return total;
  }


FXbool GMTrackDatabase::trackInPlaylist(FXint track,FXint playlist) {
  FXint total;
  try {
    GMQuery query(&db,"SELECT COUNT(queue) FROM playlist_tracks WHERE track == ? AND playlist == ? ");
    query.setParameter(0,track);
    query.setParameter(1,playlist);
    query.execute();
    query.getResult(0,total);
    query.reset();
    }
  catch (FXCompileException &){
    return false;
    }
  catch (FXExecuteException &){
    return false;
    }
  return (total>0);
  }

FXbool GMTrackDatabase::getAlbumTrack(FXint id,FXString & path) {
  FXString filename;
  FXint    pathid;
  try {
    query_track_filename_by_album.setParameter(0,id);
    query_track_filename_by_album.execute();
    query_track_filename_by_album.getResult(0,filename);
    query_track_filename_by_album.getResult(1,pathid);
    query_track_filename_by_album.reset();

    query_path.setParameter(0,pathid);
    while(query_path.execute()) {
        if (!path.empty() && path!="/") path+=PATHSEPSTRING;
        path+=query_path.getResult(0);
        }
    query_path.reset();
    }
  catch (FXExecuteException &){
    return false;
    }
  if (path.empty())
    path=filename;
  else
    path+=PATHSEPSTRING+filename;
  return true;
  }



FXint GMTrackDatabase::getAlbumPath(FXint id) {
  FXint path;
  try {
    GMQuery query(&db,"SELECT path FROM tracks WHERE album == ? LIMIT(1);");
    query.setParameter(0,id);
    query.execute();
    query.getResult(0,path);
    query.reset();
    }
  catch (FXCompileException &){
    return -1;
    }
  catch (FXExecuteException &){
    return -1;
    }
  return path;
  }

FXString GMTrackDatabase::getPathName(FXint id) {
  FXString path;
  try {
    query_path.setParameter(0,id);
    while(query_path.execute()) {
        if (!path.empty() && path!="/") path+=PATHSEPSTRING;
        path+=query_path.getResult(0);
        }
    query_path.reset();
    }
  catch (FXExecuteException &){
    return FXString::null;
    }
  return path;
  }

FXString GMTrackDatabase::getAlbumName(FXint id) {
  FXString result;
  queryAlbum(result,id);
  return result;
  }

FXString GMTrackDatabase::getAlbumArtist(FXint id) {
  FXString result;
  queryArtist(result,id);
  return result;
  }

FXString GMTrackDatabase::getArtistName(FXint id){
  FXString result;
  try {
    query_artist_name.setParameter(0,id);
    query_artist_name.execute();
    result = query_artist_name.getResult(0);
    query_artist_name.reset();
    }
  catch (FXExecuteException &){
    return FXString::null;
    }
  return result;
  }



FXString GMTrackDatabase::getGenreName(FXint id){
  FXString name;
  try {
    GMQuery query(&db,"SELECT name FROM genres WHERE id == ?;");
    query.setParameter(0,id);
    query.execute();
    query.getResult(0,name);
    query.reset();
    }
  catch (FXCompileException &){
    return FXString::null;
    }
  catch (FXExecuteException &){
    return FXString::null;
    }
  return name;
  }

FXint GMTrackDatabase::getTrackAlbum(FXint id){
  FXint result;
  FXint year;
  queryAlbum(result,year,id);
  return result;
  }

/*
FXint GMTrackDatabase::hasTrack(const FXString & mrl){
  FXString filename;
  FXint id;
  if (!trackdict_updated) {
    trackdict.clear();
    try {
      while(list_all_tracks.execute()){
        list_all_tracks.getResult(0,id);
        list_all_tracks.getResult(1,filename);
        trackdict.insert(filename.text(),(void*)(FXival)id);
        }
      list_all_tracks.reset();
      }
    catch (FXExecuteException & e){
      return 0;
      }
    trackdict_updated=true;
    }
  void * data = trackdict.find(mrl.text());
  if (data==NULL)
    return 0;
  else
    return (FXint)(FXival) data;
  }
*/

FXbool GMTrackDatabase::getStream(FXint id,GMStream & stream){
  FXString query;
  query.format("SELECT url,description,genres.name,bitrate FROM streams, genres WHERE genres.id == streams.genre AND streams.id == %d;",id);
  GMQuery get_stream;
  try {
    get_stream.compile(db,query);
    get_stream.execute();
    stream.url=get_stream.getResult(0);
    stream.description=get_stream.getResult(1);
    stream.genre=get_stream.getResult(2);
    get_stream.getResult(3,stream.bitrate);
    get_stream.reset();
    }
  catch (FXCompileException &){
    return false;
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }



FXbool GMTrackDatabase::getTrack(FXint id,GMTrack & track){
  FXString path,filename,number;
  FXint pathid;
  try {

    query_track.setParameter(0,id);
    query_track.execute();
    query_track.getResult(0,pathid);
    query_track.getResult(1,filename);
    query_track.getResult(2,track.album);
    query_track.getResult(3,track.album_artist);
    query_track.getResult(4,track.artist);
    query_track.getResult(5,track.genre);
    query_track.getResult(6,track.title);
    query_track.getResult(7,track.time);
    query_track.getResult(8,track.no);
    query_track.getResult(9,track.year);
    query_track.getResult(10,track.track_gain);
    query_track.getResult(11,track.track_peak);
    query_track.getResult(12,track.album_gain);
    query_track.getResult(13,track.album_peak);
    query_track.reset();

    if (pathid>=0) {
      query_path.setParameter(0,pathid);
      while(query_path.execute()) {
        if (!path.empty() && path!="/") path+=PATHSEPSTRING;
        path+=query_path.getResult(0);
        }
      query_path.reset();
      }
    if (!path.empty())
        track.mrl = path + PATHSEPSTRING + filename;
    else
        track.mrl = filename;
    }
  catch (FXExecuteException &){
    return FALSE;
    }
  return true;
  }

FXbool GMTrackDatabase::getTrackAssociation(FXint id,FXint & artist,FXint & album){
  try {
    query_album_artists.setParameter(0,id);
    query_album_artists.execute();
    query_album_artists.getResult(0,artist);
    query_album_artists.getResult(1,album);
    query_album_artists.reset();
    }
  catch (FXCompileException &){
    return false;
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }


FXbool GMTrackDatabase::removeGenre(FXint id,FXbool update_tracks) {
  GMQuery remove_genre;
  GMQuery remove_tracks;
  GMQuery remove_albums;
  GMQuery remove_artist;
  try {
    db.begin();
    remove_genre.compile((GMDatabase*)this,"DELETE FROM genres WHERE id == ?;");
    remove_tracks.compile((GMDatabase*)this,"DELETE FROM tracks WHERE genre == ?;");
    remove_albums.compile((GMDatabase*)this,"DELETE FROM albums WHERE id NOT IN ( SELECT album FROM tracks);");
    remove_artist.compile((GMDatabase*)this,"DELETE FROM artists WHERE id NOT IN ( SELECT DISTINCT(artist) FROM albums ) AND id NOT IN ( SELECT DISTINCT(artist) FROM tracks ) ; ");
    remove_genre.setParameter(0,id);
    remove_genre.perform();
    if (update_tracks) {
      remove_tracks.setParameter(0,id);
      remove_tracks.perform();
      remove_albums.perform();
      remove_artist.perform();
      remove_tracks.reset();
      remove_albums.reset();
      remove_artist.reset();
      }
    remove_genre.reset();
    db.commit();
    }
  catch (FXCompileException &){
    db.rollback();
    return false;
    }
  catch (FXExecuteException &){
    db.rollback();
    return false;
    }
  return true;
  }



FXbool GMTrackDatabase::removeArtist(FXint artist) {
  try {
    db.begin();

    /// Remove tracks from artist from playlists.
    delete_artist_from_playlists.setParameter(0,artist);
    delete_artist_from_playlists.setParameter(1,artist);
    delete_artist_from_playlists.execute();
    delete_artist_from_playlists.reset();

    /// Remove Tracks
    delete_track_artist.setParameter(0,artist);
    delete_track_artist.setParameter(1,artist);
    delete_track_artist.execute();
    delete_track_artist.reset();

    /// Remove Album Entries
    delete_albums_artist.setParameter(0,artist);
    delete_albums_artist.execute();
    delete_albums_artist.reset();

    /// Remove Artist itself
    delete_artist.setParameter(0,artist);
    delete_artist.execute();
    delete_artist.reset();
    db.commit();
    }
  catch (FXExecuteException &){
    db.rollback();
    return false;
    }
  return true;
  }

FXbool GMTrackDatabase::removeAlbum(FXint album) {
  try {
    db.begin();
    delete_album_from_playlists.setParameter(0,album);
    delete_album_from_playlists.execute();
    delete_album_from_playlists.reset();

    delete_track_album.setParameter(0,album);
    delete_track_album.execute();
    delete_track_album.reset();

    delete_album.setParameter(0,album);
    delete_album.execute();
    delete_album.reset();
    db.commit();
    }
  catch (FXExecuteException &){
    db.rollback();
    return false;
    }
  return true;
  }


FXbool GMTrackDatabase::removeTracks(const FXIntList & tracks) {
  try {
#ifdef DEBUG
    FXlong start = fxgetticks();
#endif
    db.begin();
    for (FXint i=0;i<tracks.no();i++){
      delete_track.setParameter(0,tracks[i]);
      delete_track.execute();
      delete_track.reset();
      }
    for (FXint i=0;i<tracks.no();i++){
      delete_track_from_playlists.setParameter(0,tracks[i]);
      delete_track_from_playlists.execute();
      delete_track_from_playlists.reset();
      }
    db.commit();
#ifdef DEBUG
    FXlong end = fxgetticks();
    fxmessage("Remove Tracks %d: %ld\n",tracks.no(),end-start);
#endif
    }
  catch (FXExecuteException &){
    db.rollback();
    return false;
    }
  return true;
  }


FXbool GMTrackDatabase::removeTrack(FXint track) {
  try {
    delete_track.setParameter(0,track);
    delete_track.execute();
    delete_track.reset();

    delete_track_from_playlists.setParameter(0,track);
    delete_track_from_playlists.execute();
    delete_track_from_playlists.reset();
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }


/// For each playlist, we reset the queue number from 1 to max tracks.
FXbool GMTrackDatabase::reorderPlaylists(){
  GMQuery remove_all;
  GMQuery count_all;
  GMQuery query_all;
  GMQuery insert_all;

  try {
    FXIntList playlists;
    FXIntList tracks;
    FXint num_tracks=0;
    FXint i=0,j=0;

    /// Count Rows
#ifdef DEBUG
    fxmessage("counting records\n");
#endif
    count_all.compile(db,"SELECT COUNT(*) FROM playlist_tracks;");
    count_all.execute();
    count_all.getResult(0,num_tracks);
    count_all.reset();

    playlists.no(num_tracks);
    tracks.no(num_tracks);

    /// Get All Rows in proper order
#ifdef DEBUG
    fxmessage("getting records\n");
#endif
    query_all.compile(db,"SELECT playlist,track FROM playlist_tracks ORDER BY playlist,queue;");
    while(query_all.execute()) {
      query_all.getResult(0,playlists[j]);
      query_all.getResult(1,tracks[j]);
      j++;
      }
    query_all.reset();

    /// Delete Contents
#ifdef DEBUG
    fxmessage("deleting records\n");
#endif
    remove_all.compile(db,"DELETE FROM playlist_tracks;");
    remove_all.execute();
    remove_all.reset();

    /// Reinsert playlists
#ifdef DEBUG
    fxmessage("inserting records\n");
#endif
    insert_all.compile(db,"INSERT INTO playlist_tracks VALUES ( ?, ? ,?);");
    for (i=0,j=1;i<num_tracks;i++,j++) {
      if (i>0 && playlists[i-1]!=playlists[i]) j=1;
      insert_all.setParameter(0,playlists[i]);
      insert_all.setParameter(1,tracks[i]);
      insert_all.setParameter(2,j);
      insert_all.execute();
      insert_all.reset();
      }
    }
  catch (FXCompileException &){
    return false;
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }


FXbool GMTrackDatabase::removeTracksFromPlaylist(const FXIntList & queue,FXint playlist){
  FXString query;

  db.begin();

  if (queue.no()==1) {

    query.format("DELETE FROM playlist_tracks WHERE playlist == %d AND queue == %d;",playlist,queue[0]);
    if (!db.execute(query))
      goto error;

    // Renumber queue following removed queue
    query.format("UPDATE playlist_tracks SET queue = queue - 1 WHERE playlist == %d AND queue > %d;",playlist,queue[0]);
    if (!db.execute(query))
      goto error;
    }
  else {
    query.format("DELETE FROM playlist_tracks WHERE playlist == %d AND queue IN ( %d",playlist,queue[0]);
    for (FXint i=1;i<queue.no();i++){
      query+=",";
      query+=GMStringVal(queue[i]);
      }
    query+=");";

    if (!db.execute(query))
      goto error;

    if (!reorderPlaylists())
      goto error;
    }
  db.commit();
  return true;
error:
  db.rollback();
  return false;
  }



FXbool GMTrackDatabase::removePlaylist(FXint playlist){
  try {
    GMQuery query_remove_tracks(&db,"DELETE FROM playlist_tracks WHERE playlist == ?;");
    GMQuery query_remove_playlist(&db,"DELETE FROM playlists WHERE id == ?;");

    db.begin();

    query_remove_tracks.setParameter(0,playlist);
    query_remove_tracks.execute();
    query_remove_tracks.reset();

    query_remove_playlist.setParameter(0,playlist);
    query_remove_playlist.execute();
    query_remove_playlist.reset();

    db.commit();
    }
  catch (FXCompileException &){
    db.rollback();
    return false;
    }
  catch (FXExecuteException &){
    db.rollback();
    return false;
    }
  return true;
  }



/// Get Playlist Name
FXString GMTrackDatabase::getPlaylistName(FXint playlist) {
  FXString name;
  try {
    GMQuery query(&db,"SELECT name FROM playlists WHERE id == ?;");
    query.setParameter(0,playlist);
    query.execute();
    query.getResult(0,name);
    query.reset();
    }
  catch (FXCompileException &){
    return FXString::null;
    }
  catch (FXExecuteException &){
    return FXString::null;
    }
  return name;
  }

/// Set Playlist Name
FXbool GMTrackDatabase::setPlaylistName(FXint playlist,const FXString & name) {
  try {
    GMQuery query(&db,"UPDATE playlists SET name = ? WHERE id == ?;");
    query.setParameter(0,name);
    query.setParameter(1,playlist);
    query.execute();
    query.reset();
    }
  catch (FXCompileException &){
    return false;
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }

/// Move Track in playlist
FXbool GMTrackDatabase::moveTrack(FXint playlist,FXint oldq,FXint newq){
  FXString query;
  FXint row=0;
  if (oldq==newq) return true;

  query = "SELECT ROWID FROM playlist_tracks WHERE playlist == " + GMStringVal(playlist) + " AND queue == " + GMStringVal(oldq) + ";";
  if (!db.execute_with_result(query,row))
    return false;

  if (oldq<newq)
    query = "UPDATE playlist_tracks SET queue = queue - 1 WHERE playlist == "+ GMStringVal(playlist) +" AND queue > " +GMStringVal(oldq) + " AND queue <= " + GMStringVal(newq) + ";";
  else
    query = "UPDATE playlist_tracks SET queue = queue + 1 WHERE playlist == "+ GMStringVal(playlist) +" AND queue < " +GMStringVal(oldq) + " AND queue >= " + GMStringVal(newq) + ";";

  if (!db.execute(query))
    return false;

  query = "UPDATE playlist_tracks SET queue = " + GMStringVal(newq) + " WHERE playlist == " + GMStringVal(playlist) + " AND ROWID == " + GMStringVal(row) + ";";
  if (!db.execute(query))
    return false;

  return true;
  }

/// Set the import date of a track
FXbool GMTrackDatabase::setTrackImported(FXint track,FXlong tm){
  try {
    update_track_importdate.setParameter(0,tm);
    update_track_importdate.setParameter(1,track);
    update_track_importdate.execute();
    update_track_importdate.reset();
    }
  catch (FXExecuteException & ){
    return false;
    }
  return true;
  }


/// Set the name of a track
FXbool GMTrackDatabase::setTrackTitle(FXint track,const FXString & name){
  FXString filename;
  try {
    update_track_title.setParameter(0,name);
    update_track_title.setParameter(1,track);
    update_track_title.execute();
    update_track_title.reset();
    }
  catch (FXExecuteException &){
    return FALSE;
    }
  return true;
  }


FXbool GMTrackDatabase::playedTrack(FXint track,FXlong time) {
  try {
    update_played.setParameter(0,time);
    update_played.setParameter(1,track);
    update_played.execute();
    update_played.reset();
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }


FXbool GMTrackDatabase::setTrackYear(FXint track,FXuint year){
  FXString mrl;
  try {
    update_track_year.setParameter(0,(FXint)year);
    update_track_year.setParameter(1,track);
    update_track_year.execute();
    update_track_year.reset();
    }
  catch (FXExecuteException &){
     return false;
     }
  return true;
  }


FXbool GMTrackDatabase::setTrackYear(const FXIntList & tracks,FXuint year){
  FXString mrl;
  try {
    for (FXint i=0;i<tracks.no();i++) {
      update_track_year.setParameter(0,(FXint)year);
      update_track_year.setParameter(1,tracks[i]);
      update_track_year.execute();
      update_track_year.reset();
      }
    }
  catch (FXExecuteException &){
     return false;
     }
  return true;
  }


FXbool GMTrackDatabase::setTrackDisc(FXint track,FXushort disc){
  try {
    update_track_disc.setParameter(0,disc);
    update_track_disc.setParameter(1,track);
    update_track_disc.execute();
    update_track_disc.reset();
    }
  catch (FXExecuteException &){
     return false;
     }
  return true;
  }


FXbool GMTrackDatabase::setTrackNumber(FXint track,FXushort number){
  try {
    update_track_track.setParameter(0,number);
    update_track_track.setParameter(1,track);
    update_track_track.execute();
    update_track_track.reset();
    }
  catch (FXExecuteException &){
     return false;
     }
  return true;
  }

FXbool GMTrackDatabase::setTrackDiscNumber(FXint track,FXuint no){
  try {
    update_track_no.setParameter(0,no);
    update_track_no.setParameter(1,track);
    update_track_no.execute();
    update_track_no.reset();
    }
  catch (FXExecuteException &){
     return FALSE;
     }
  return true;
  }

FXbool GMTrackDatabase::setTrackDisc(const FXIntList & tracks,FXushort disc){
  try {
    for (FXint i=0;i<tracks.no();i++) {
      update_track_disc.setParameter(0,disc);
      update_track_disc.setParameter(1,tracks[i]);
      update_track_disc.execute();
      update_track_disc.reset();
      }
    }
  catch (FXExecuteException & ){
     return FALSE;
     }
  return true;
  }




FXbool GMTrackDatabase::setTrackGenre(const FXIntList & tracks,const FXString & name){
  FXint genre=-1;
  FXString mrl;
  try {

    /// Query for existing genre
    query_genre.setParameter(0,name);
    query_genre.execute();
    query_genre.getResult(0,genre);
    query_genre.reset();

    if (genre==-1) {
      /// Insert Genre
      insert_genre.setParameter(0,name);
      insert_genre.execute();
      genre = db.lastInsertRow();
      insert_genre.reset();
      }

    /// Set Genre
    for (FXint i=0;i<tracks.no();i++) {
      update_track_genre_id.setParameter(0,genre);
      update_track_genre_id.setParameter(1,tracks[i]);
      update_track_genre_id.execute();
      update_track_genre_id.reset();
      }
    }
  catch (FXExecuteException &){
     return FALSE;
     }
  return true;
  }



FXbool GMTrackDatabase::setTrackGenre(FXint track,const FXString & name){
#ifdef DEBUG
  fxmessage("single genre called\n");
#endif
  FXIntList tracks;
  tracks.append(track);
  return setTrackGenre(tracks,name);
  }


/// Set Track Album
FXbool GMTrackDatabase::setTrackAlbum(const FXIntList & tracks,const FXString & album_title,FXbool sameartist){
  FXint album=-1;
  FXint artist=-1;
  try {

    if (sameartist) {

      /// Get the artist
      query_artist_album.setParameter(0,tracks[0]);
      query_artist_album.execute();
      query_artist_album.getResult(0,artist);
      query_artist_album.reset();

      FXASSERT(artist!=-1);

      /// Fetch album for given title and artist
      query_album.setParameter(0,album_title);
      query_album.setParameter(1,artist);
      query_album.execute();
      query_album.getResult(0,album);
      query_album.reset();

      /// Album does not exist yet, so create it
      if (album==-1) {
        insert_album_existing.setParameter(0,album_title);
        insert_album_existing.setParameter(1,artist);
        insert_album_existing.setParameter(2,tracks[0]);
        insert_album_existing.execute();
        album = db.lastInsertRow();
        insert_album_existing.reset();
        }

      FXASSERT(album!=-1);

      /// Assign this new album
      for (FXint i=0;i<tracks.no();i++) {
        update_track_album_id.setParameter(0,album);
        update_track_album_id.setParameter(1,tracks[i]);
        update_track_album_id.execute();
        update_track_album_id.reset();
        }
      }
    else {

      for (FXint i=0;i<tracks.no();i++) {

        /// Get the artist
        artist=-1;
        query_artist_album.setParameter(0,tracks[i]);
        query_artist_album.execute();
        query_artist_album.getResult(0,artist);
        query_artist_album.reset();


        /// Fetch album for given title and artist
        album=-1;
        query_album.setParameter(0,album_title);
        query_album.setParameter(1,artist);
        query_album.execute();
        query_album.getResult(0,album);
        query_album.reset();

        /// Album does not exist yet, so create it
        if (album==-1) {
          insert_album_existing.setParameter(0,album_title);
          insert_album_existing.setParameter(1,artist);
          insert_album_existing.setParameter(2,tracks[i]);
          insert_album_existing.execute();
          album = db.lastInsertRow();
          insert_album_existing.reset();
          }

        /// Update the Album Ids in the track list
        update_track_album_id.setParameter(0,album);
        update_track_album_id.setParameter(1,tracks[i]);
        update_track_album_id.execute();
        update_track_album_id.reset();
        }
      }
    }
  catch (FXExecuteException &){
     return false;
     }
  return true;
  }



FXbool GMTrackDatabase::setTrackAlbumArtist(const FXIntList & tracks,const FXString & artist_name,const FXString & album_title){
  try {
    FXint artist=-1;
    FXint album=-1;
    FXString albumname;

    if (!queryArtist(artist,artist_name,true))
      return false;

    if (album_title.empty()) {

      for (FXint i=0;i<tracks.no();i++){

        album=-1;
        query_album_existing.setParameter(0,artist);
        query_album_existing.setParameter(1,tracks[i]);
        query_album_existing.execute();
        query_album_existing.getResult(0,album);
        query_album_existing.getResult(1,albumname);
        query_album_existing.reset();

        if (album==-1) {
          insert_album_existing_name.setParameter(0,artist);
          insert_album_existing_name.setParameter(1,tracks[i]);
          insert_album_existing_name.execute();
          album = db.lastInsertRow();
          insert_album_existing_name.reset();
          }

        update_track_album_id.setParameter(0,album);
        update_track_album_id.setParameter(1,tracks[i]);
        update_track_album_id.execute();
        update_track_album_id.reset();

        }
      }
    else {
      query_album.setParameter(0,album_title);
      query_album.setParameter(1,artist);
      query_album.execute();
      query_album.getResult(0,album);
      query_album.reset();

      if (album==-1) {
        insert_album_existing.setParameter(0,album_title);
        insert_album_existing.setParameter(1,artist);
        insert_album_existing.setParameter(2,tracks[0]);
        insert_album_existing.execute();
        album = db.lastInsertRow();
        insert_album_existing.reset();
        }

      for (FXint i=0;i<tracks.no();i++){
        update_track_album_id.setParameter(0,album);
        update_track_album_id.setParameter(1,tracks[i]);
        update_track_album_id.execute();
        update_track_album_id.reset();
        }
      }
    }
  catch (FXExecuteException &){
    return false;
    }
  return true;
  }


/// Set Track Artist
FXbool GMTrackDatabase::setTrackAlbumArtist(FXint/* track*/,const FXString & /*artist_name*/,const FXString & /*album_title*/){
  //FIXME
  FXASSERT(0);
  return false;
#if 0
  FXString album_name;
  FXint current_album=-1;
  FXint artist=-1;
  FXint album=-1;
  FXint year=0;
  FXdouble gain,peak;
  try {

    /// Fetch Album of track
    if (!queryAlbum(current_album,year,track)) return FALSE;

    /// Fetch new artist id
    if (!queryArtist(artist,artist_name,true)) return FALSE;

    /// Fetch new album id
    if (album_title.empty()) {
      if (!queryAlbum(album_name,current_album)) return FALSE;
      }
    else {
      album_name=album_title;
      }

    /// Get Album Id for album_name gor given artist
    if (!queryAlbum(album,album_name,artist,year,gain,peak,true)) return FALSE;

    /// Set New Album id
    update_track_album_id.setParameter(0,album);
    update_track_album_id.setParameter(1,track);
    update_track_album_id.execute();
    update_track_album_id.reset();

    }
  catch (FXExecuteException & e){
     return FALSE;
     }
  return true;
#endif
  }

/// Set Track Artist
FXbool GMTrackDatabase::setTrackAlbumArtist(FXint /*track*/,const FXString &/* artist_name*/){
  //FIXME
  FXASSERT(0);
  return false;
#if 0
  FXString album_name;
  FXint current_album=-1;
  FXint artist=-1;
  FXint album=-1;
  FXint year=0;
  FXdouble gain,peak;
  try {

    /// Fetch Album of track
    if (!queryAlbum(current_album,year,track)) return false;

    /// Fetch Album Name
    album_name = getAlbumName(current_album);

    /// Fetch new artist id
    if (!queryArtist(artist,artist_name,true)) return false;

    /// Fetch new album id
    if (!queryAlbum(album,album_name,artist,year,gain,peak,true)) return false;

    /// Set New Album id
    update_track_album_id.setParameter(0,album);
    update_track_album_id.setParameter(1,track);
    update_track_album_id.execute();
    update_track_album_id.reset();

    }
  catch (FXExecuteException & e){
     return FALSE;
     }
  return true;
#endif
  }

/// Set Track Artist
FXbool GMTrackDatabase::setTrackArtist(FXint track,const FXString & artist_name){
  FXIntList tracks;
  tracks.append(track);
  return setTrackArtist(tracks,artist_name);
  }

/// Set Track Artist
FXbool GMTrackDatabase::setTrackArtist(const FXIntList & tracks,const FXString & artist_name){
  FXint artist=-1;
  try {

    /// Fetch new artist id
    if (!queryArtist(artist,artist_name,true)) return false;

    /// Set New Album id
    for (FXint i=0;i<tracks.no();i++) {
      update_track_artist.setParameter(0,artist);
      update_track_artist.setParameter(1,tracks[i]);
      update_track_artist.execute();
      update_track_artist.reset();
      }
    }
  catch (FXExecuteException &){
     return false;
     }
  return true;
  }


FXbool GMTrackDatabase::listGenres(FXComboBox * list,FXbool insert_default){
  register int i=0;
  FXDict genres;
  FXString name;
  FXint id;
  try {
    if (insert_default) {
      for (i=0;!TagLib::ID3v1::genre(i).isNull();i++) {
        genres.insert(TagLib::ID3v1::genre(i).toCString(true),(void*)(FXival)1);
        list->appendItem(TagLib::ID3v1::genre(i).toCString(true));
        }
      while(list_genres.execute()) {
        list_genres.getResult(0,id);
        list_genres.getResult(1,name);
        if (!genres.find(name.text()))
          list->appendItem(name);
        }
      }
    else {
      while(list_genres.execute()) {
        list_genres.getResult(0,id);
        list_genres.getResult(1,name);
        list->appendItem(name);
        }
      }
    list_genres.reset();
    list->sortItems();
    }
  catch(FXExecuteException &){
    list->clearItems();
    return FALSE;
    }
  return true;
  }




FXbool GMTrackDatabase::listArtists(FXComboBox * list){
  FXString name;
  FXint id;
  try {
    while(list_artists.execute()){
      list_artists.getResult(0,id);
      list_artists.getResult(1,name);
      list->appendItem(name);
      }
    list_artists.reset();
    list->sortItems();
    }
  catch(FXExecuteException & ){
    list->clearItems();
    return FALSE;
    }
  return true;
  }

FXbool GMTrackDatabase::listAlbums(FXComboBox * list,FXint album){
  FXString name;
  FXint id;
  FXint artist;
  try {
    if (!queryArtist(artist,album)) return FALSE;

    list_albums_artist.setParameter(0,artist);
    while(list_albums_artist.execute()){
      list_albums_artist.getResult(0,id);
      list_albums_artist.getResult(1,name);
      list->appendItem(name);
      }
    list_albums_artist.reset();
    list->sortItems();
    }
  catch(FXExecuteException & ){
    list->clearItems();
    return FALSE;
    }
  return true;
  }


FXbool GMTrackDatabase::listTracks(FXIntList & list,FXint album,FXint genre/*=-1*/,FXint playlist/*=-1*/){
  GMQuery query;
  FXint id;
  try {
    if (genre==-1) {
      if (playlist==-1) {
        list_tracks_from_album.setParameter(0,album);
        while(list_tracks_from_album.execute()){
          list_tracks_from_album.getResult(0,id);
          list.append(id);
          }
        list_tracks_from_album.reset();
        }
      else {
        query.compile((GMDatabase*)this,"SELECT tracks.id FROM tracks,genres,playlist_tracks WHERE playlist_tracks.track == tracks.id AND tracks.album == ? AND playlist_tracks.playlist == ?;");
        query.setParameter(0,album);
        query.setParameter(1,playlist);
        while(query.execute()) {
          query.getResult(0,id);
          list.append(id);
          }
        query.reset();
        }
      }
    else {
      if (playlist==-1) {
        list_tracks_from_album_genre.setParameter(0,album);
        list_tracks_from_album_genre.setParameter(1,genre);
        while(list_tracks_from_album_genre.execute()){
          list_tracks_from_album_genre.getResult(0,id);
          list.append(id);
          }
        list_tracks_from_album_genre.reset();
        }
      else {
        query.compile((GMDatabase*)this,"SELECT tracks.id FROM tracks,genres,playlist_tracks WHERE genres.id == tracks.genre AND playlist_tracks.track == tracks.id AND tracks.album == ? AND tracks.genre == ? AND playlist_tracks.playlist == ?;");
        query.setParameter(0,album);
        query.setParameter(1,genre);
        query.setParameter(2,playlist);
        while(query.execute()) {
          query.getResult(0,id);
          list.append(id);
          }
        query.reset();
        }
      }
    }
  catch(FXCompileException & e){
    fxmessage("compile exception: %s\n",e.what());
    list.clear();
    return false;
    }
  catch(FXExecuteException & e){
    fxmessage("execute exception: %s\n",e.what());
    list.clear();
    return false;
    }
  return true;
  }

FXbool GMTrackDatabase::listTracks(FXIntList & list,FXint genre){
  FXint id;
  try {
    list_tracks_genre_only.setParameter(0,genre);
    while(list_tracks_genre_only.execute()){
      list_tracks_genre_only.getResult(0,id);
      list.append(id);
      }
    list_tracks_genre_only.reset();
    }
  catch(FXExecuteException & e){
    fxwarning("Exception: %s\n",e.what());
    list.clear();
    return false;
    }
  return true;
  }

FXbool GMTrackDatabase::listTracksArtists(FXIntList & list,FXint artist,FXint genre/*=-1*/,FXint playlist/*=-1*/){
  GMQuery query;
  FXint id;
  try {
    if (genre==-1) {
      if (playlist==-1) {
        query.compile((GMDatabase*)this,"SELECT tracks.id FROM tracks,albums,artists WHERE tracks.album = albums.id AND artists.id == albums.artist AND artists.id == ?;");
        query.setParameter(0,artist);
        while(query.execute()) {
          query.getResult(0,id);
          list.append(id);
          }
        query.reset();
        }
      else {
        query.compile((GMDatabase*)this,"SELECT tracks.id FROM tracks,albums,artists,playlist_tracks WHERE tracks.album == albums.id AND artists.id == albums.artist AND playlist_tracks.track == tracks.id AND artists.id == ? AND playlist_tracks.playlist == ? ;");
        query.setParameter(0,artist);
        query.setParameter(1,playlist);
        while(query.execute()) {
          query.getResult(0,id);
          list.append(id);
          }
        query.reset();
        }
      }
    else {
      if (playlist==-1) {
        query.compile((GMDatabase*)this,"SELECT tracks.id FROM tracks,albums,artists WHERE tracks.album = albums.id AND artists.id == albums.artist AND tracks.genre == ? AND artists.id == ?;");
        query.setParameter(0,genre);
        query.setParameter(1,artist);
        while(query.execute()) {
          query.getResult(0,id);
          list.append(id);
          }
        query.reset();
        }
      else {
        query.compile((GMDatabase*)this,"SELECT tracks.id FROM tracks,albums,artists,playlist_tracks WHERE tracks.album = albums.id AND artists.id == albums.artist AND playlist_tracks.track == tracks.id AND tracks.genre == ? AND artists.id == ? AND playlist_tracks.playlist == ?;");
        query.setParameter(0,genre);
        query.setParameter(1,artist);
        query.setParameter(2,playlist);
        while(query.execute()) {
          query.getResult(0,id);
          list.append(id);
          }
        query.reset();
        }
      }
    }
  catch(FXCompileException & e){
    fxmessage("compile: %s\n",e.what());
    list.clear();
    return false;
    }
  catch(FXExecuteException & e){
    fxmessage("execute: %s\n",e.what());
    list.clear();
    return false;
    }
  return true;
  }


FXbool GMTrackDatabase::upgrade_to_2009() {

  if (!db.execute("ALTER TABLE albums RENAME TO albums_old;"))
    return false;

  if (!db.execute(create_albums))
    return false;

  if (!db.execute("INSERT INTO albums (id, name, artist, year) SELECT albums_old.id, name, artist,year FROM albums_old,album_artist,tracks WHERE album_artist.album == albums_old.id AND tracks.album==albums_old.id GROUP BY albums_old.id;"))
    return false;

  if (!db.execute("DROP TABLE albums_old;"))
    return false;

  if (!db.execute("DROP TABLE album_artist;"))
    return false;

  if (!db.execute("ALTER TABLE tracks ADD COLUMN artist INTEGER;"))
    return false;
  if (!db.execute("ALTER TABLE tracks ADD COLUMN replay_gain REAL;"))
    return false;
  if (!db.execute("ALTER TABLE tracks ADD COLUMN replay_peak REAL;"))
    return false;

  if (!db.execute("UPDATE tracks SET artist = ( SELECT DISTINCT(albums.artist) FROM albums WHERE albums.id == tracks.album );"))
    return false;

  GMQuery q;
  q.compile(db,"UPDATE tracks SET replay_gain = ?, replay_peak = ?;");
  FXdouble v=NAN;
  q.setParameter(0,v);
  q.setParameter(1,v);
  q.execute();
  q.reset();


  if (!db.hasTable("streams")) {
    if (!db.execute(create_streams))
      return false;
    }

  if (sqlite3_libversion_number()>=SQVERSION(3,3,0)) {
    db.execute("CREATE INDEX IF NOT EXISTS tracks_album ON tracks(album);");
    db.execute("CREATE INDEX IF NOT EXISTS playlist_tracks_idx ON playlist_tracks(track);");
    }
  else {
    if (!db.hasIndex("tracks_album")) db.execute("CREATE INDEX tracks_album ON tracks(album);");
    if (!db.hasIndex("playlist_tracks_idx")) db.execute("CREATE INDEX playlist_tracks_idx ON playlist_tracks(track);");
    }

  db.setVersion(GOGGLESMM_DATABASE_SCHEMA_VERSION);

  /// Rebuild the database
  db.execute("VACUUM;");

  return true;
  }

void GMTrackDatabase::fix_genres() {
  FXint total=0;
  try {
    GMQuery count_tracks(&db,"SELECT COUNT(id) FROM tracks WHERE genre == 0;");
    count_tracks.execute();
    count_tracks.getResult(0,total);
    count_tracks.reset();
    if (total>0) {
      FXint genre=0;
      if (queryGenre(genre,"Untitled",true)) {
        GMQuery fix;
        fix.compile(db,"UPDATE tracks SET genre = ? WHERE genre == 0;");
        fix.setParameter(0,genre);
        fix.execute();
        fix.reset();
        }
      }
    }
  catch (FXCompileException &){
    }
  catch (FXExecuteException &){
    }
  }

FXbool GMTrackDatabase::setup_tables() {
  FXString sql;
  FXint  version = db.getVersion();
  GMQuery list_tables;
  GMQuery list_index;
  try {

    if (version==GOGGLESMM_DATABASE_SCHEMA_VERSION) {
      return true;
      }

    if (version==2008) {
      if (upgrade_to_2009())
        return true;
      }


    db.clear();

    /// Create Tables
    if (!db.execute(create_tracks))
      return FALSE;
    if (!db.execute(create_genres))
      return FALSE;
    if (!db.execute(create_albums))
      return FALSE;
    if (!db.execute(create_artists))
       return FALSE;
    if (!db.execute(create_playlists))
       return FALSE;
    if (!db.execute(create_playlist_tracks))
       return FALSE;
    if (!db.execute(create_directories))
       return FALSE;
    if (!db.execute(create_trigger_diradd))
       return FALSE;
    if (!db.execute(create_streams))
      return FALSE;

    db.execute("CREATE INDEX tracks_album ON tracks(album);");
    db.execute("CREATE INDEX playlist_tracks_idx ON playlist_tracks(track);");
    db.setVersion(GOGGLESMM_DATABASE_SCHEMA_VERSION);
    }
  catch (FXCompileException &){
      return false;
      }
  catch (FXExecuteException &){
      return false;
      }
  return true;
  }

FXbool GMTrackDatabase::setup_queries(){
  try {
    insert_root_component.compile(db,"INSERT INTO directories (id,parent,name) VALUES (NULL,NULL,?);");
    insert_path_component.compile(db,"INSERT INTO directories (id,parent,name) VALUES (NULL,?,?);");
    insert_track.compile(db,"INSERT INTO tracks VALUES ( NULL , ? , ? , ? , ? , ? , ? , 0 , 0 , ? , ? , ?, 0, ?, ?, ? );");
    insert_genre.compile(db,"INSERT INTO genres VALUES ( NULL , ?  );");
    insert_artist.compile(db,"INSERT INTO artists VALUES ( NULL , ?  );");
    insert_album.compile(db,"INSERT INTO albums VALUES ( NULL , ?, ?, ?, ?, ? );");
    query_filename.compile(db,"SELECT id FROM tracks WHERE path == ? AND mrl == ?;");

    query_root_component.compile(db,"SELECT id FROM directories WHERE parent isnull AND name = ?;");
    query_path_component.compile(db,"SELECT id FROM directories WHERE parent = ? AND name = ?;");


    query_path.compile(db,"SELECT name FROM directories WHERE (select path || id || '/' from directories WHERE id = ? ) like path || id || '/%' order by path;");
    query_track.compile(db,"SELECT path, mrl, albums.name, a1.name, a2.name, genres.name, title, time, no, tracks.year, tracks.replay_gain, tracks.replay_peak, albums.replay_gain, albums.replay_peak "
                           "FROM tracks, albums, genres, artists AS a1, artists AS a2 "
                           "WHERE albums.id == tracks.album "
                             "ANd a1.id == albums.artist "
                             "AND genres.id == tracks.genre "
                             "AND a2.id == tracks.artist "
                             "AND tracks.id == ?;");

    query_genre.compile(db,"SELECT id FROM genres WHERE name == ?;");
    query_artist.compile(db,"SELECT id FROM artists WHERE name == ?;");
    query_artist_name.compile(db,"SELECT name FROM artists WHERE id == ?;");
    query_album.compile(db,"SELECT id FROM albums WHERE name == ? AND artist == ?;");
    query_album_name.compile(db,"SELECT name FROM albums WHERE id == ?;");
    query_artist_with_album.compile(db,"SELECT artist FROM albums WHERE id == ?;");
    query_artist_name_album.compile(db,"SELECT name FROM artists WHERE id == ( SELECT artist FROM albums WHERE id == ? );");
    query_album_with_track.compile(db,"SELECT album,year FROM tracks WHERE id == ?;");
    query_track_filename.compile(db,"SELECT mrl,path FROM tracks WHERE tracks.id == ? ;");
    query_track_filename_by_album.compile(db,"SELECT mrl,path FROM tracks WHERE album == ? ORDER BY no LIMIT(1);");

    query_track_genre_id.compile(db,"SELECT genre FROM tracks WHERE id == ?;");
    query_artist_year.compile(db,"SELECT albums.artist,tracks.year FROM tracks,albums WHERE albums.id ==tracks.album AND tracks.id == ?;");
    query_album_artists.compile(db,"SELECT albums.artist,album FROM tracks,albums WHERE albums.id ==tracks.album AND tracks.id == ?;");

    update_played.compile(db,"UPDATE tracks SET playcount = playcount + 1, playdate = ? WHERE id == ?;");

    list_genres.compile(db,"SELECT id, name FROM genres WHERE id IN (SELECT DISTINCT(genre) FROM tracks);");
    list_artists.compile(db,"SELECT id,name FROM artists;");

    list_albums_artist.compile(db,"SELECT albums.id, albums.name FROM albums WHERE albums.artist == ? ;");

    list_tracks_from_album.compile(db,"SELECT id FROM tracks WHERE album == ?");
    list_tracks_from_album_genre.compile(db,"SELECT id FROM tracks WHERE album == ? AND genre == ?;");
    list_tracks_genre_only.compile(db,"SELECT id FROM tracks WHERE genre == ?;");
    query_playlist_queue.compile(db,"SELECT MAX(queue) FROM playlist_tracks WHERE playlist == ?;");
    insert_track_playlist.compile(db,"INSERT INTO playlist_tracks VALUES ( ? , ? , ?);");


    fix_genres();
    }
  catch (FXCompileException & e){
    fxmessage("Compile Exception: %s\n",e.what());
    return FALSE;
    }
  return true;
  }


FXbool GMTrackDatabase::queryGenre(FXint & result,const FXString & name,FXbool insert){
  result=-1;
  try {
    query_genre.setParameter(0,name);
    query_genre.execute();
    query_genre.getResult(0,result);
    query_genre.reset();

    if (result==-1 && insert) {
      insert_genre.setParameter(0,name);
      insert_genre.execute();
      result = db.lastInsertRow();
      insert_genre.reset();
      }
    }
  catch (FXExecuteException &){
    result=-1;
    return FALSE;
    }
  return true;
  }

/*
FXbool GMTrackDatabase::queryGenre(FXString & result,FXint trackid){
  result=FXString::null;
  try {
    query_track_genre.setParameter(0,trackid);
    query_track_genre.execute();
    query_track_genre.getResult(0,result);
    query_track_genre.reset();
    }
  catch (FXExecuteException & e){
    result=FXString::null;
    return FALSE;
    }
  return true;
  }
*/

FXbool GMTrackDatabase::getTrackGenre(FXint & genre,FXint trackid) {
  genre=-1;
  try {
    query_track_genre_id.setParameter(0,trackid);
    query_track_genre_id.execute();
    query_track_genre_id.getResult(0,genre);
    query_track_genre_id.reset();
    }
  catch (FXExecuteException &){
    genre=-1;
    return FALSE;
    }
  return true;
  }



FXbool GMTrackDatabase::queryArtist(FXint & result,const FXString & name,FXbool insert){
  result=-1;
  try {
    query_artist.setParameter(0,name);
    query_artist.execute();
    query_artist.getResult(0,result);
    query_artist.reset();

    if (result==-1 && insert) {
      insert_artist.setParameter(0,name);
      insert_artist.execute();
      result = db.lastInsertRow();
      insert_artist.reset();
      }
    }
  catch (FXExecuteException &){
    result=-1;
    return FALSE;
    }
  return true;
  }


FXbool GMTrackDatabase::queryArtist(FXint & result,FXint album){
  result=-1;
  try {
    query_artist_with_album.setParameter(0,album);
    query_artist_with_album.execute();
    query_artist_with_album.getResult(0,result);
    query_artist_with_album.reset();
    }
  catch (FXExecuteException &){
    result=-1;
    return FALSE;
    }
  return true;
  }


FXbool GMTrackDatabase::queryArtist(FXString & result,FXint album){
  result=FXString::null;
  try {
    query_artist_name_album.setParameter(0,album);
    query_artist_name_album.execute();
    query_artist_name_album.getResult(0,result);
    query_artist_name_album.reset();
    }
  catch (FXExecuteException &){
    result=FXString::null;
    return FALSE;
    }
  return true;
  }


FXbool GMTrackDatabase::queryAlbum(FXint & album,FXint & year,FXint track){
  album=-1;
  year=0;
  try {
    query_album_with_track.setParameter(0,track);
    query_album_with_track.execute();
    query_album_with_track.getResult(0,album);
    query_album_with_track.getResult(1,year);
    query_album_with_track.reset();
    }
  catch (FXExecuteException &){
    album=-1;
    year=0;
    return false;
    }
  return true;
  }


FXbool GMTrackDatabase::updateAlbum(FXint & result,const GMTrack & track,FXint artist){
  result=-1;
  try {
    query_album.setParameter(0,track.album);
    query_album.setParameter(1,artist);
    query_album.execute();
    query_album.getResult(0,result);
    query_album.reset();

    if (result==-1) {
      insert_album.setParameter(0,track.album);
      insert_album.setParameter(1,artist);
      insert_album.setParameter(2,track.year);
      insert_album.setParameter(3,track.album_gain);
      insert_album.setParameter(4,track.album_peak);
      insert_album.execute();
      result = db.lastInsertRow();
      insert_album.reset();
      }
    else {
      update_album.setParameter(0,track.year);
      update_album.setParameter(1,track.album_gain);
      update_album.setParameter(2,track.album_peak);
      update_album.setParameter(3,result);
      update_album.execute();
      update_album.reset();
      }
    }
  catch (FXExecuteException & e){
    fxmessage("Exception: %s\n",e.what());
    result=-1;
    return FALSE;
    }
  return true;
  }


FXbool GMTrackDatabase::queryAlbum(FXint & result,const FXString & name,FXint artist,FXint year,FXdouble gain,FXdouble peak,FXbool insert){
  result=-1;
  try {
    query_album.setParameter(0,name);
    query_album.setParameter(1,artist);
    query_album.execute();
    query_album.getResult(0,result);
    query_album.reset();

    if (result==-1 && insert) {
      insert_album.setParameter(0,name);
      insert_album.setParameter(1,artist);
      insert_album.setParameter(2,year);
      insert_album.setParameter(3,gain);
      insert_album.setParameter(4,peak);
      insert_album.execute();
      result = db.lastInsertRow();
      insert_album.reset();
      }
    }
  catch (FXExecuteException & e){
    fxmessage("Exception: %s\n",e.what());
    result=-1;
    return FALSE;
    }
  return true;
  }


FXbool GMTrackDatabase::queryAlbum(FXString & name,FXint album){
  name=FXString::null;
  try {
    query_album_name.setParameter(0,album);
    query_album_name.execute();
    query_album_name.getResult(0,name);
    query_album_name.reset();
    }
  catch (FXExecuteException &){
    name=FXString::null;
    return FALSE;
    }
  return true;
  }






FXbool GMTrackDatabase::vacuum() {

#ifdef DEBUG
  FXlong end, start;
  start = fxgetticks();
#endif

  /// Remove empty playlists ? FIXME: may be not...
  /// if (!db.execute("DELETE FROM playlists WHERE id NOT IN (SELECT DISTINCT(playlist) FROM playlist_tracks);"))
  //  return false;
  db.begin();

#ifdef DEBUG
  fxmessage("Reorder Playlists\n");
#endif

  /// Reorder Playlists
  if (!reorderPlaylists()) goto error;

#ifdef DEBUG
  fxmessage("Remove Empty Albums\n");
#endif

  /// Remove empty albums
  if (!db.execute("DELETE FROM albums WHERE id NOT IN (SELECT DISTINCT(album) FROM tracks);"))
    goto error;

#ifdef DEBUG
  fxmessage("Remove unused artists\n");
#endif

  /// Remove unused artists
  if (!db.execute("DELETE FROM artists WHERE id NOT IN (SELECT DISTINCT(artist) FROM albums) ANd id NOT IN (SELECT DISTINCT(artist) FROM tracks);"))
    goto error;

#ifdef DEBUG
  fxmessage("Remove unused genres\n");
#endif

  /// Remove unused genres
  if (!db.execute("DELETE FROM genres WHERE id NOT IN (SELECT DISTINCT(genre) FROM tracks) AND id NOT IN (SELECT DISTINCT(genre) FROM streams);"))
    goto error;


#ifdef DEBUG
  fxmessage("Remove empty leafs\n");
#endif

  /// Remove empty leafs
  do {
    if (!db.execute("DELETE FROM directories WHERE id NOT IN (SELECT DISTINCT(parent) FROM directories) AND id NOT IN (SELECT DISTINCT(path) FROM tracks);"))
      goto error;
    }
  while (db.changes()>0);
  last_path=-1;

  /// commit changes
  db.commit();

#ifdef DEBUG
  end = fxgetticks();
  fxmessage("Vacuum Database in %ld ticks\n",end-start);
#endif

  return true;
error:
  db.rollback();
  return false;
  }


FXbool GMTrackDatabase::exportList(const FXString & filename,FXint playlist,FXuint filetype,FXuint opts){
  const FXchar * c_artist;
  const FXchar * c_album;
  const FXchar * c_title;
  const FXchar * c_genre;
  FXint time;
  FXint no;
  FXint id;
  FXint cnt=1;
  FXint year;
  FILE * fp = NULL;
  FXString query;
  FXString title;
  FXString file;
  FXString basepath=FXPath::directory(filename);
  GMQuery list;
  try {

    query = "SELECT tracks.id,title,artists.name,albums.name,genres.name,no,time,tracks.year "
            "FROM tracks, genres, albums, artists";

    if (playlist>=0)
      query+=", playlist_tracks";

    query+=" WHERE genres.id == tracks.genre AND "
                  "albums.artist == artists.id AND "
                  "tracks.album == albums.id";

    if (playlist>=0) {
      query+=" AND playlist_tracks.track == tracks.id";
      query+=" AND playlist_tracks.playlist == "+GMStringVal(playlist);
      query+=" ORDER BY playlist_tracks.queue; ";
      title=getPlaylistName(playlist);
      }
    else {
      query+=";";
      }

    list.compile(db,query);

    FILE * fp = fopen(filename.text(),"w");
    if (!fp) return false;

    if (filetype==PLAYLIST_XSPF) {
      fprintf(fp,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
      fprintf(fp,"<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n");
      if (!title.empty()) fprintf(fp,"\t<title>%s</title>\n",title.text());
      fprintf(fp,"\t<trackList>\n");
      }
    else if (filetype==PLAYLIST_M3U_EXTENDED) {
      fprintf(fp,"#EXTM3U\n");
      }
    else if (filetype==PLAYLIST_PLS) {
      fprintf(fp,"[playlist]\n");
      }
    else if (filetype==PLAYLIST_CSV) {
      fprintf(fp,"No,Title,Album,Artist,Genre,Duration,Year,Filename\n");
      }

    while(list.execute()){
      list.getResult(0,id);
      c_title = list.getResult(1);
      c_artist = list.getResult(2);
      c_album = list.getResult(3);
      c_genre = list.getResult(4);
      list.getResult(5,no);
      list.getResult(6,time);
      list.getResult(7,year);
      file = getTrackFilename(id);

      if (opts&PLAYLIST_OPTIONS_RELATIVE)
        file = FXPath::relative(basepath,file);

      if (filetype==PLAYLIST_XSPF) {
        fprintf(fp,"\t\t<track>\n");
        fprintf(fp,"\t\t\t<location>%s</location>\n",GMURL::fileToURL(gm_url_encode(file)).text());
        fprintf(fp,"\t\t\t<creator>%s</creator>\n",c_artist);
        fprintf(fp,"\t\t\t<album>%s</album>\n",c_album);
        fprintf(fp,"\t\t\t<title>%s</title>\n",c_title);
        fprintf(fp,"\t\t\t<duration>%d</duration>\n",time*1000);
        fprintf(fp,"\t\t\t<trackNum>%u</trackNum>\n",no);
        fprintf(fp,"\t\t</track>\n");
        }
      else if (filetype==PLAYLIST_M3U_EXTENDED) {
        fprintf(fp,"#EXTINF:%d,%s - %s\n",time,c_artist,c_title);
        fprintf(fp,"%s\n",file.text());
        }
      else if (filetype==PLAYLIST_M3U) {
        fprintf(fp,"%s\n",file.text());
        }
      else if (filetype==PLAYLIST_PLS) {
        fprintf(fp,"File%d=%s\n",cnt,file.text());
        fprintf(fp,"Title%d=%s - %s\n",cnt,c_artist,c_title);
        fprintf(fp,"Length%d=%d\n",cnt,time);
        cnt++;
        }
      else if (filetype==PLAYLIST_CSV){
        fprintf(fp,"%d,\"%s\",\"%s\",\"%s\",\"%s\",%d,%d,\"%s\"\n",no,c_title,c_album,c_artist,c_genre,time,year,file.text());
        }
      }
    list.reset();
    if (filetype==PLAYLIST_XSPF) {
      fprintf(fp,"\t</trackList>\n");
      fprintf(fp,"</playlist>");
      }
    else if (filetype==PLAYLIST_PLS){
      fprintf(fp,"Version=2\n");
      fprintf(fp,"NumberOfEntries=%d",cnt-1);
      }
    fclose(fp);
    fp=NULL;
    }
  catch (FXCompileException &){
    if (fp) fclose(fp);
    return false;
    }
  catch (FXExecuteException &){
    if (fp) fclose(fp);
    return false;
    }
  return true;
  }

