/*
*  qm_mpdcom.cpp
*  QUIMUP MPD command class
*  © 2008-2024 Johan Spee
*  SPDX-License-Identifier: GPL-3.0-or-later
*/


#include "qm_mpdcom.h"
#include "qm_modes_ids.h"
#include <QGuiApplication>


qm_mpdCommand::qm_mpdCommand(qm_Config *cfg)
{
    if (objectName().isEmpty())
        setObjectName("mpdCom");

    config           = cfg;
    config->mpd_is_installed = is_mpd_installed(config->cout_extensive);

    p_conn           = nullptr;
    connect_Thread   = nullptr;
    connect_Task     = nullptr;

    b_is_on_start =    true;
    b_shutting_down =  false;
    b_reload =         true;
    b_mpdconf_found =  false;
    b_try_connect_ok = false;
    b_statcheck_busy = false;
    b_dbase_updating = false;
    b_no_volume =      true;
    b_remote_server =  false;
    b_new_conn_keep =  false;
    plist_lenght =     0;
    current_playlist = -10;
    current_song_id =  -10;
    current_song_pos = -10;
    current_status =   -10;
    current_volume =   -10;
    server_port =      6600;
    server_name =      "";
    server_password =  "";
    mpdconf_path = get_mpdconf_path();
    get_autodetect_params();

    statusLoop = new QTimer(this);
    connect(statusLoop, SIGNAL(timeout()), this, SLOT(status_check()));

    connect_Task = new qm_workerTask();
    connect(connect_Task, SIGNAL(work_done(uchar)), this, SLOT(on_workdone_sgnl(uchar)));

    connect_Thread = new QThread(this);
    connect(connect_Thread, SIGNAL(started()), connect_Task, SLOT(do_work()));
    connect(connect_Thread, SIGNAL(finished()), this, SLOT(on_threadfinished_sgnl()));

    connect_Task->moveToThread(connect_Thread);
}


void qm_mpdCommand::configure_connection()
{
    switch (config->profile_current)
    {
        case 0:
        {
            server_port = config->profile_port_0;
            server_name = config->profile_host_0;
            server_password = config->profile_pswd_0;
            // just in case...
            server_password = server_password.left(server_password.lastIndexOf("@"));
            break;
        }
        case 1:
        {
            server_port = config->profile_port_1;
            server_name = config->profile_host_1;
            server_password = config->profile_pswd_1;
            // just in case...
            server_password = server_password.left(server_password.lastIndexOf("@"));
            break;
        }
        case 2:
        {
            server_port = config->profile_port_2;
            server_name = config->profile_host_2;
            server_password = config->profile_pswd_2;
            // just in case...
            server_password = server_password.left(server_password.lastIndexOf("@"));
            break;
        }
        case 3:
        {
            server_port = config->profile_port_3;
            server_name = config->profile_host_3;
            server_password = config->profile_pswd_3;
            // just in case...
            server_password = server_password.left(server_password.lastIndexOf("@"));
            break;
        }
        case 4:
        {
            server_port = config->profile_port_4;
            server_name = config->profile_host_4;
            server_password = config->profile_pswd_4;
            // just in case...
            server_password = server_password.left(server_password.lastIndexOf("@"));
            break;
        }
        default:
            break;
    }

    // (in)validate server_name as IP

    if ( validate_ip_address(server_name) && server_name != "127.0.0.1" && server_name != "0.0.0.0")
    {
        b_remote_server = true;
        if (config->cout_extensive)
            printf ("Host is a remote server address\n");
    }
    else
    {
        b_remote_server = false;
        if (config->cout_extensive)
        {
            if (server_name.contains("/socket"))
                printf ("Host is a local socket server address\n");
            else
                printf ("Host is a local server address\n");
        }
    }
}


bool qm_mpdCommand::set_outputs(qm_outputList outputlist)
{
    if (p_conn == nullptr)
        return false;

    bool wasplaying = false;
    if (current_status == MPD_STATE_PLAY)
    {
        wasplaying = true;
        pause();
    }

    bool b_success = true;

    QListIterator<qm_output> i(outputlist);
    while (i.hasNext())
    {
        qm_output output = i.next();
        if (output.enabled)
            mpd_send_enable_output(p_conn, output.id);
        else
            mpd_send_disable_output(p_conn, output.id);
        mpd_response_finish(p_conn);
        if (!error_check("set_outputs"))
            b_success = false;
    }

    if (wasplaying)
        resume();

    return b_success;
}


qm_outputList qm_mpdCommand::get_outputs()
{
    qm_outputList outputlist;
    // get current output devices
    if (p_conn == nullptr)
        return outputlist;

    mpd_output *output;
    if (mpd_send_outputs(p_conn))
    {
        while ((output = mpd_recv_output(p_conn)))
        {
            qm_output qoutput;
            qoutput.id  = mpd_output_get_id(output);
            qoutput.enabled  = mpd_output_get_enabled(output);
            qoutput.name = QString::fromUtf8(mpd_output_get_name(output));
            outputlist.push_back(qoutput);
            mpd_output_free(output);
        }
    }
    mpd_response_finish(p_conn);
    error_check("get_outputs");

    return outputlist;
}


QString qm_mpdCommand::get_musicPath()
{
    QString result = "";

    // This is useless on a remote connection
    if (b_remote_server)
        return result;

    // Try reading from mpd.conf
    if (b_mpdconf_found)
    {
        result = get_string("music_directory");
        if (!result.isEmpty())
            printf ("MPD's music dir: %s\n", result.toUtf8().constData()); // check dir later
        else
            printf ("MPD's music dir not found in mpd.conf!\n");
     }

    return result;
}


QString qm_mpdCommand::get_host()
{
    QString result = "";

    if (!b_mpdconf_found)
         return result;

    QFile file(mpdconf_path);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
         return result;

    QTextStream in(&file);

    // 1st try 'socket'
    while (!in.atEnd())
    {
         QString line = in.readLine().simplified(); // trim spaces
         if ( !line.startsWith("#") && line.contains("bind_to_address", Qt::CaseInsensitive))
         {
            line = line.section('"', 1, 1);
            if (line.contains("/socket") )
            {
                result = line;
                file.close();
                return result;
            }
         }
    }

    // 2nd try 'localhost'
    while (!in.atEnd())
    {

         QString line = in.readLine().simplified(); // trim spaces
         if ( !line.startsWith("#") && line.contains("bind_to_address", Qt::CaseInsensitive))
         {
            line = line.section('"', 1, 1);
            if (line == "localhost" )
            {
                result = line;
                file.close();
                return result;
            }
         }
    }

    // 3rd try '127.0.0.1'
    while (!in.atEnd())
    {
         QString line = in.readLine().simplified(); // trim spaces
         if ( !line.startsWith("#") && line.contains("bind_to_address", Qt::CaseInsensitive))
         {
            line = line.section('"', 1, 1);
            if (line == "127.0.0.1" )
            {
                result = line;
                file.close();
                return result;
            }
         }
    }

    // 3rd try 'IP-address'
    while (!in.atEnd())
    {
         QString line = in.readLine().simplified(); // trim spaces
         if ( !line.startsWith("#") && line.contains("bind_to_address", Qt::CaseInsensitive))
         {
            line = line.section('"', 1, 1);
            if (validate_ip_address(line ))
            {
                result = line;
                file.close();
                return result;
            }
         }
    }

    // 3rd try 'any'
    while (!in.atEnd())
    {
         QString line = in.readLine().simplified(); // trim spaces
         if ( !line.startsWith("#") && line.contains("bind_to_address", Qt::CaseInsensitive))
         {
            line = line.section('"', 1, 1);
            if (line == "any" )
            {
                result = line;
                file.close();
                return result;
            }
         }
    }

    return result;
}


int qm_mpdCommand::get_port()
{
    QString get = get_string("port")
                  ;
    return get.toInt();
}


QString qm_mpdCommand::get_password()
{
    QString result = get_string("password");
    result = result.left(result.lastIndexOf("@"));
    return result;
}


QString qm_mpdCommand::get_string(QString key)
{
    if (!b_mpdconf_found)
        return "";

    QFile file(mpdconf_path);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return "";

    QTextStream in(&file);
    while (!in.atEnd())
    {
        QString line = in.readLine().trimmed(); // trim trailing & leading spaces
        // "bind_to_address" may not be "any"
        if ( !line.startsWith("#") && line.contains(key, Qt::CaseInsensitive) && !line.contains("any") )
        {
            QString result = line.section('"', 1, 1);
            file.close();
            return result.simplified();
        }
    }

    return "";
}

// combines get_etc_default_mpd() and find_config_files()
QString qm_mpdCommand::get_mpdconf_path()
{
    QString confpath = get_etc_default_mpd();

    if (confpath.isEmpty())
    {
        if (config->profile_current == 0)
            mpdconf_path = find_config_files(true);
        else
            mpdconf_path = find_config_files(false);
    }

    if (confpath.isEmpty())
        b_mpdconf_found = false;
    else
        b_mpdconf_found = true;

    return confpath;
}

// Get mpd.conf from  /etc/default/mpd
QString qm_mpdCommand::get_etc_default_mpd()
{
    QFile file;
    QString line = "";

    file.setFileName("/etc/default/mpd");
    if ( !file.exists() )
    {
        if (config->cout_extensive)
            printf ("MPD's config: Tried /etc/default/mpd but it does not exist\n");
        return line;
    }

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        if (config->cout_extensive)
            printf ("MPD's config: Tried /etc/default/mpd but it is not readable\n");
        return line;
    }
    else
    {
        QTextStream in(&file);
        while (!in.atEnd())
        {
            line = in.readLine().simplified();
            if ( !line.startsWith("#") && line.contains("MPDCONF") )
            {
                line = line.right(line.size() - (line.lastIndexOf("MPDCONF=")+8));
                break;
            }
        }
        file.close();
    }

    if (!line.isEmpty())
    {
        file.setFileName(line);
        if (!file.exists())
        {
            if (config->cout_extensive)
                printf ("MPD's config in /etc/default/mpd does not exist\n");
            line = "";
        }
    }
    else
    {
        if (config->cout_extensive)
            printf ("MPD's config: Tried /etc/default/mpd but MPDCONF is not set\n");
    }

    if (!line.isEmpty() && config->cout_extensive)
    {
        printf ("MPD's config from /etc/default/mpd: %s\n", line.toUtf8().constData());
    }

    return line;

}


void qm_mpdCommand::update_all()
{
    if (p_conn == nullptr)
    {
        b_dbase_updating = false;
        emit signal_update_ready();
        return;
    }

    if (config->mpd_update_allowed)
    {
        b_dbase_updating = true;
        current_playlist++; // prevent listupdate
        const char *cc = "/";
        mpd_send_update(p_conn, cc);
        mpd_response_finish(p_conn);
        if (error_check("update_all"))
            printf("Updating MPD database...\n");
        else
            b_dbase_updating = false;
    }
    else
    {
        b_dbase_updating = false;
        emit signal_update_ready();
        printf ("Updating MPD database: not allowed\n");
        emit signal_update_ready();
    }
}


void qm_mpdCommand::mpd_try_connect(bool b_new_conn)
{
    // Come back later if thread is still running
    if (connect_Thread != nullptr)
    {
        if (connect_Thread->isRunning())
        {
            if (b_new_conn)
                b_new_conn_keep = true;
            connect_Task->abort_loop();
            return;
        }
        /*  Spaghetti code:
        abort_loop() triggers signal work_done(MPD_ERROR_CLOSED).
        Next on_workdone_sgnl() runs connect_Thread->quit()
        and sets b_try_connect_ok = false.
        connect_Thread->quit() triggers the finished() signal.
        Next on_threadfinished_sgnl() calls mpd_re_connect().
        But: b_new_conn will always return as FALSE here. So if
        it is TRUE we must keep that, hence: b_new_conn_keep.  */
    }
    else
    {
        // connect_Thread crashed?
        connect_Thread = new QThread(this);
        connect(connect_Thread, SIGNAL(started()), connect_Task, SLOT(do_work()));
        connect(connect_Thread, SIGNAL(finished()), this, SLOT(on_threadfinished_sgnl()));
        connect_Task->moveToThread(connect_Thread);
    }

    // disconnect if connected
    if (p_conn != nullptr)
    {
        mpd_disconnect(false);
        printf ("Closing current connection\n");
    }

    // no server, no go.
    if (server_name.isEmpty())
    {
        server_name = "localhost";
        printf ("No host name: using \"localhost\"\n");
    }

    // port number must be > 1024
    if (!(server_port > 1024))
    {
        printf ("Invalid port: %i [using 6600]\n", server_port);
        server_port = 6600;
    }

    /*  Start MPD if:
        Starting MPD is configured,
        MPD is installed,
        MPD is not already running,
        ... and we are starting up.           */

    if (b_is_on_start)
    {
        b_is_on_start = false;

        if (config->onstart_startmpd)
        {
            bool success = false;

            if (!config->mpd_is_installed)
            {
                printf ("Starting MPD failed: it is not installed\n");
            }
            else
            {
                if (!is_mpd_running(true))
                {
                    printf ("Starting MPD, as configured\n");
                    success = start_mpd(); // this will printf
                }
                else
                    success = true;
            }

            if (!success && !b_remote_server)
            {
                printf ("Connection attempt terminated\n");
                emit signal_connected(false, b_remote_server);
                return;
            }
        }
        else
            printf ("Not starting MPD, as configured\n");
    }
    else
    if (!b_remote_server)
    {
        if (!config->mpd_is_installed)
        {
            printf ("MPD is not installed > Not connecting\n");
            emit signal_connected(false, b_remote_server);
            return;
        }

        if (!is_mpd_running(true) )
        {
            printf ("MPD is not running > Not connecting\n");
            emit signal_connected(false, b_remote_server);
            return;
        }
    }

    // We either have a remote connection or MPD is up and running...

    if (b_new_conn || b_new_conn_keep)
    {
        b_new_conn_keep = false;
        // try 4 times with 350 msec delay and a timeout of 2000 msec
        connect_Task->set_host_port_tmout(server_name, server_port, 2000 );
        connect_Task->set_tries_delay(4, 350);
    }
    else // reconnecting
    {
        // try 10 times with 2000 msec delay and the configured timeout
        connect_Task->set_host_port_tmout(server_name, server_port, config->mpd_timeout * 1000 );
        connect_Task->set_tries_delay(10, 2000);
    }

    // On success we call mpd_do_connect() from on_threadfinished()
    connect_Thread->start();
}


bool qm_mpdCommand::mpd_do_connect()
{
    // timeouts go unnoticed using mpd_conn from mpd_try_connect() (why?)
    // so close it and start a new connection from here:

    if (connect_Task->p_conn != nullptr)
    {
        mpd_connection_free(connect_Task->p_conn);
        connect_Task->p_conn = nullptr;
    }

    mpd_disconnect(false);

    config->reset_temps();

    int timeout = config->mpd_timeout * 1000; // MPD wants ms

    // connect
    p_conn = mpd_connection_new( static_cast<const char*>(server_name.toUtf8()), static_cast<uint>(server_port), timeout);

    if (p_conn == nullptr)
    {
        printf ("Connection failed on first try\n");
        emit signal_connected(false, false);
        return false;
    }

    if ( mpd_connection_get_error(p_conn) != MPD_ERROR_SUCCESS )
    {
        printf ("Connection error: %s\n", mpd_connection_get_error_message(p_conn));
        mpd_connection_clear_error(p_conn);
        mpd_connection_free(p_conn);
        p_conn = nullptr;
        return false;
    }

    // SUCCESS

    QString version = get_version(true);
    if (version.isEmpty())
    {

        printf ("Error: MPD did not return version\n");
    }
    else
    {
    if (b_remote_server)
        {
            QString str = "Connected to MPD " + version + " / remote\n";
            char *c = str.toUtf8().data();
            printf("%s", c);
        }
        else
        {
            QString str = "Connected to MPD " + version + " / local\n";
            char *c = str.toUtf8().data();
            printf("%s", c);        }
    }

    bool b_status_allowed = false;
    char *command;
    mpd_pair *pair;

    // First let's see if we can get MPD's status
    mpd_send_allowed_commands (p_conn);
    while ( (pair = mpd_recv_command_pair(p_conn)) != nullptr)
    {
        command = const_cast<char*>(pair->value);
        if (!strcmp(command, "status"))
        {
            b_status_allowed = true;
            mpd_return_pair(p_conn, pair);
            break;
        }
        else
            mpd_return_pair(p_conn, pair);
    }
    mpd_response_finish(p_conn);

    if (!server_password.isEmpty())
    {
        mpd_send_password(p_conn, static_cast<const char*>(server_password.toUtf8()));
        mpd_response_finish(p_conn);
        if ( mpd_connection_get_error(p_conn) != MPD_ERROR_SUCCESS )
        {
            printf ("Error: %s\n", mpd_connection_get_error_message(p_conn));
            mpd_connection_clear_error(p_conn);
            if (b_status_allowed)
                printf ("Password was refused, but not required\n");
        }
        else
            printf("Password accepted\n");
    }
    else
    {
        if (!b_status_allowed)
            printf("Password probably required but not provided\n");
    }

    if (!b_status_allowed)
    {
        mpd_disconnect(true);
        if (server_password.isEmpty())
            printf ("No permission to get MPD's status. A Password is probably required\n");
        else
            printf ("No permission to get MPD's status. The Password is probably wrong\n");
        show_messagebox(tr("No permission to get MPD's status"), tr("Possibly a (different) password is required. Check out the 'Connect' tab in the 'settings' window."));
        return false;
    }

    // we're in business


    if (config->cout_extensive)
    {
        printf("Disallowed MPD commands: ");
        mpd_send_disallowed_commands (p_conn);
        int i = 0;
        while ( (pair = mpd_recv_command_pair(p_conn)) != nullptr)
        {
            command = const_cast<char*>(pair->value);
            printf(" %s ", command);
            mpd_return_pair(p_conn, pair);
            i++;
        }
        mpd_response_finish(p_conn);

        if (i == 0)
            printf("none\n");
        else
            printf("\n");
    }

    mpd_send_allowed_commands(p_conn);
    while ( (pair = mpd_recv_command_pair(p_conn)) != nullptr)
    {
        command = const_cast<char*>(pair->value);

        if (!strcmp(command, "status"))
            b_status_allowed = true;

        if (!strcmp(command, "config"))
            config->mpd_config_allowed = true;

        if (!strcmp(command, "outputs"))
            config->mpd_op_listall_allowed = true;

        if (!strcmp(command, "disableoutput"))
            config->mpd_op_disable_allowed = true;

        if (!strcmp(command, "enableoutput"))
            config->mpd_op_enable_allowed = true;

        if (!strcmp(command, "update"))
            config->mpd_update_allowed = true;

        if (!strcmp(command, "delete"))         // remove pos from playlist
            config->mpd_delete_allowed = true;

        if (!strcmp(command, "deleteid"))       // remove id from playlist
            config->mpd_deleteid_allowed = true;

        if (!strcmp(command, "rm"))             // remove saved playlist
            config->mpd_rm_allowed = true;

        if (!strcmp(command, "save"))           // save playlist
            config->mpd_save_allowed = true;

        if (!strcmp(command, "single"))
            config->mpd_single_allowed = true;

        if (!strcmp(command, "consume"))
            config->mpd_consume_allowed = true;

        if (!strcmp(command, "crossfade"))
            config->mpd_xfade_allowed = true;

        if (!strcmp(command, "repeat"))
            config->mpd_repeat_allowed = true;

        if (!strcmp(command, "rescan"))
            config->mpd_rescan_allowed = true;

        if (!strcmp(command, "random"))
            config->mpd_random_allowed = true;

        if (!strcmp(command, "shuffle"))
            config->mpd_shuffle_allowed = true;

        if (!strcmp(command, "clear"))
            config->mpd_clear_allowed = true;

        if (!strcmp(command, "stats"))          // database statistics
            config->mpd_stats_allowed = true;

        if (!strcmp(command, "replay_gain_mode"))
            config->mpd_setrpgain_allowed = true;

        if (!strcmp(command, "albumart"))       // albumart from file
            config->mpd_albumart_allowed = true;

        if (!strcmp(command, "readpicture"))    // albumart from tag
            config->mpd_readpic_allowed = true;

        mpd_return_pair(p_conn, pair);
    }
    mpd_response_finish(p_conn);

    if (error_check("mpd_connect"))
    {
        if (config->cout_extensive)
            printf ("MPD errors: none reported\n");
    }

    if ( server_name.contains("socket") )
        config->mpd_socket_conn = true;

    // First get the musicpath
    switch (config->profile_current)
    {
    case 0:
    {
        config->mpd_musicpath = config->profile_mdir_0;
        break;
    }
    case 1:
    {
        config->mpd_musicpath = config->profile_mdir_1;
        break;
    }
    case 2:
    {
        config->mpd_musicpath = config->profile_mdir_2;
        break;
    }
    case 3:
    {
        config->mpd_musicpath = config->profile_mdir_3;
        break;
    }
    case 4:
    {
        config->mpd_musicpath = config->profile_mdir_4;
        break;
    }
    default:
    {
        config->mpd_musicpath = get_musicPath();
        break;
    }
    }

    // Next verify the musicpath
    if (!config->mpd_musicpath.isEmpty())
    {
        if (!config->mpd_musicpath.startsWith("/"))
            config->mpd_musicpath.prepend("/");

        if (config->mpd_musicpath.endsWith("/"))
            config->mpd_musicpath.chop(1);

        QDir dir(config->mpd_musicpath);
        if (!dir.exists())
        {
            printf ("MPD's music dir was not found\n");
            config->mpd_musicdir_status = MDIR_NOTFOUND;
        }
        else
            if (!dir.isReadable())
            {
                printf ("MPD's music dir is not readable\n");
                config->mpd_musicdir_status = MDIR_NOTREADABLE;
            }
            else
            {
                printf ("MPD's music dir is accessible\n");
                config->mpd_musicdir_status = MDIR_ACCESSIBLE;
            }
    }
    else
    {
        config->mpd_musicdir_status = MDIR_NOT_DETERMINED;
        if (b_remote_server)
            printf ("MPD's music dir is remote\n");
        else
            printf ("MPD's music dir was not determined\n");
    }

    mpd_socket_fds = mpd_connection_get_fd(p_conn);

    // Next signal connected
    emit signal_connected(true, b_remote_server);

    // TODO: allow setting polling frequency in settings
    int msec = config->mpd_pollfreq;
    if (msec < 100) msec = 100;
    if (msec > 10000) msec = 10000;
    status_check();
    // poll MPD every msec miliseconds (default 500)
    statusLoop->start(msec);

    return true;
}


void qm_mpdCommand::on_workdone_sgnl(uchar result)
{
    /* don't do anything here that will (re)start connect_thread.
       Do it in on_threadfinished_sgnl() */

    if (result == MPD_ERROR_SUCCESS)
    {
        // bool is used in on_threadfinished_sgnl()
        b_try_connect_ok = true;
        if (config->cout_extensive)
            printf("MPD responds. Setting up the connection\n");
    }
    else
    {
        // bool is used in on_threadfinished_sgnl()
        b_try_connect_ok = false;
        printf("MPD does not respond. Retrying 10 more times at 2 sec intervals\n");
        emit signal_connected(false, b_remote_server);
    }

    if (connect_Thread != nullptr)
    {
        // trigger on_threadfinished_sgnl()
        connect_Thread->quit();
    }
}


void qm_mpdCommand::on_threadfinished_sgnl()
{
    if (b_shutting_down)
        return;

    // bool was set in on_workdone_sgnl
    if (b_try_connect_ok)
    {
        if (!mpd_do_connect())
            printf ("Connection failed!\n");
    }
    else
        mpd_re_connect();
}


void qm_mpdCommand::mpd_re_connect()
{
    if(config->auto_re_connect)
    {
        printf ("Reconnecting...\n");
        mpd_try_connect(false);
    }
    else
    {
        printf ("Not Reconnecting, as configured\n");
    }
}

void qm_mpdCommand::prev()
{
    if (p_conn == nullptr)
        return;
    mpd_send_previous(p_conn);
    mpd_response_finish(p_conn);
    error_check("prev");
}


void qm_mpdCommand::stop()
{
    if (p_conn == nullptr)
        return;
    mpd_send_stop(p_conn);
    mpd_response_finish(p_conn);
    error_check("stop");
}


void qm_mpdCommand::play(bool reload)
{
    if (p_conn == nullptr)
        return;
    if (reload)
        b_reload = true;
    mpd_send_play(p_conn);
    mpd_response_finish(p_conn);
    error_check("play");
}


void qm_mpdCommand::play_this(uint id)
{
    if (p_conn == nullptr)
        return;

    mpd_send_play_id(p_conn, id);
    mpd_response_finish(p_conn);
    error_check("play_this", id); // ID used when file doesn't exist.

}


void qm_mpdCommand::resume()
{
    if (p_conn == nullptr)
        return;
    mpd_send_pause(p_conn, 0);
    mpd_response_finish(p_conn);
    error_check("resume");
}


void qm_mpdCommand::pause()
{
    if (p_conn == nullptr)
        return;
    mpd_send_pause(p_conn, 1);
    mpd_response_finish(p_conn);
    error_check("pause");
}


void qm_mpdCommand::next()
{
    if (p_conn == nullptr)
        return;
    mpd_send_next(p_conn);
    mpd_response_finish(p_conn);
    error_check("next");
}


void qm_mpdCommand::set_random(bool status)
{
    if (p_conn == nullptr)
        return;
    mpd_send_random(p_conn, status);
    mpd_response_finish(p_conn);
    error_check("set_random");
}


void qm_mpdCommand::set_repeat(bool status)
{
    if (p_conn == nullptr)
        return;
    mpd_send_repeat(p_conn, status);
    mpd_response_finish(p_conn);
    error_check("set_repeat");
}


void qm_mpdCommand::set_single(bool status)
{
    if (p_conn == nullptr)
        return;
    mpd_send_single(p_conn, status);
    mpd_response_finish(p_conn);
    error_check("set_single");
}


void qm_mpdCommand::set_consume(bool status)
{
    if (p_conn == nullptr)
        return;
    mpd_send_consume(p_conn, status);
    mpd_response_finish(p_conn);
    error_check("set_consume");
}


void qm_mpdCommand::volume_up(int i)
{
    if (p_conn == nullptr || b_no_volume)
        return;
    int vol = current_volume + i;
    if (vol > 100)
        vol = 100;
    mpd_send_set_volume(p_conn, static_cast<uint>(vol));
    mpd_response_finish(p_conn);
    error_check("volume_up");
}


void qm_mpdCommand::volume_down(int i)
{
    if (p_conn == nullptr || b_no_volume)
        return;
    int vol = current_volume - i;
    if (vol < 0)
        vol = 0;
    mpd_send_set_volume(p_conn, static_cast<uint>(vol));
    mpd_response_finish(p_conn);
    error_check("volume_down");
}


void qm_mpdCommand::set_volume(int vol)
{
    if (p_conn == nullptr || b_no_volume)
        return;
    mpd_send_set_volume(p_conn, static_cast<uint>(vol));
    mpd_response_finish(p_conn);
    error_check("set_volume");
}


void qm_mpdCommand::set_seek(uint percent)
{
    if (p_conn == nullptr)
        return;
    mpd_send_seek_pos(p_conn, static_cast<uint>(current_song_pos),percent);
    mpd_response_finish(p_conn);
    error_check("set_seek");
}


bool qm_mpdCommand::error_check(QString action, uint item_id)
{
    if (p_conn == nullptr)
    {
        mpd_disconnect(false);
        emit signal_connected(false, b_remote_server);
        printf ("Error on \"%s\", connection is NULL\n", action.toUtf8().constData());
        // try to reconnect
        mpd_re_connect();
        return false;
    }

    int error = mpd_connection_get_error(p_conn);

    if (error == MPD_ERROR_SUCCESS)
    {
        mpd_connection_clear_error(p_conn);
        return true;
    }

    // Note: cannot get errormessage if MPD_ERROR_SUCCESS !
    QString errormsg = mpd_connection_get_error_message(p_conn);

    char NOTSHOWN = -1;
    char UNKNOWN = 0;
    char NONFATAL = 1;
    char FATAL = 2;
    char SRVERROR = 0;
    char result = NOTSHOWN;
    bool b_server_error = false;

    switch (error) // see libmpd's error.h
    {
        case MPD_ERROR_SERVER:
        {   SRVERROR = mpd_connection_get_server_error(p_conn);
            mpd_response_finish(p_conn);
            printf ("MPD: server error\n");
            b_server_error = true;
            result = NONFATAL;
            break;    }
        case MPD_ERROR_SYSTEM:
        {   printf ("MPD: system error\n");
            result = FATAL;
            break;    }
        case MPD_ERROR_CLOSED:
        {   printf ("MPD: connection closed\n");
            result = FATAL;
            break;    }
        case MPD_ERROR_OOM:
        {   printf ("MPD: out of memory\n");
            result = NONFATAL;
            break;    }
        case MPD_ERROR_ARGUMENT:
        {   printf ("MPD: function called with an invalid argument\n");
            result = NONFATAL;
            break;    }
        case MPD_ERROR_STATE:
        {   printf ("MPD: called function is not available\n");
            result = NONFATAL;
            break;    }
        case MPD_ERROR_TIMEOUT:
        {   printf ("MPD: command timed out\n");
            result = NONFATAL;
            break;    }
        case MPD_ERROR_RESOLVER:
        {   printf ("MPD: unknown host\n");
            result = NONFATAL;
            break;    }
        case MPD_ERROR_MALFORMED:
        {   printf ("MPD: malformed response received\n");
            result = NONFATAL;
            break;    }
        default:
        {   result = UNKNOWN;
            printf ("MPD: an unidentified error occurred\n"); // will be followed by the error
        }
    }

    if (b_server_error && config->cout_extensive)
    {
        switch (SRVERROR) // see libmpd's protocol.h
        {
            case MPD_SERVER_ERROR_NOT_LIST:
            {   printf ("MPD: not a list\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_ARG:
            {   printf ("MPD: bad command arguments\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_PASSWORD:
            {   printf ("MPD: invalid password\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_PERMISSION:
            {   printf ("MPD: insufficient permissions\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_UNK:
            {   printf ("MPD: unknown command\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_NO_EXIST:
            {   printf ("MPD: object doesn’t exist\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_PLAYLIST_MAX:
            {   printf ("MPD: maximum playlist size exceeded\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_SYSTEM:
            {   printf ("MPD: general system error\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_PLAYLIST_LOAD:
            {   printf ("MPD: error loading playlist\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_UPDATE_ALREADY:
            {   printf ("MPD: update database is already in progress\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_PLAYER_SYNC:
            {   printf ("MPD: player synchronization error\n");
                result = NONFATAL;
                break;        }
            case MPD_SERVER_ERROR_EXIST:
            {   printf ("MPD: object already exists\n");
                result = NONFATAL;
                break;        }
            default:
            {   result = UNKNOWN;
                printf ("MPD: an unidentified network error occurred\n"); // will be followed by the error
            }
        }
    }

    // Other fatal errors:

    // MPD_ERROR_TIMEOUT (network is down or stream fails),
    if ( errormsg.contains("Timeout", Qt::CaseInsensitive))
    {
        if (result == NOTSHOWN)
            printf ("MPD: Connection timed out\n");
        result = FATAL;
        // failing stream is caught below
    }
    else
        // "broken pipe" error means the connection is broken.
        if (errormsg.contains("Broken pipe", Qt::CaseInsensitive))
        {
            if (result == NOTSHOWN)
                printf ("MPD: Broken pipe (connection error)\n");
            result = FATAL;
        }
        else
            // "reset by peer" error means the user terminated mpd
            if (errormsg.contains("reset by peer", Qt::CaseInsensitive) )
            {
                if (result == NOTSHOWN)
                        printf ("MPD: Connection was reset by peer)\n");
                result = FATAL;
            }

    // stream timeout is not fatal
    if (errormsg.contains("Failed to decode http") || errormsg.contains("Failed to connect to"))
    {
        // MPD will move on to next track
        result = NONFATAL;
        if (config->cout_extensive)
        {
            QString msg;
            msg  = "MPD: Error on \"" + action + "\": " + errormsg  + "\n";
            printf ("%s", msg.toUtf8().constData());
        }
    }

    // An (external) file was not found
    if ( errormsg.contains("No such file or directory", Qt::CaseInsensitive))
    {
        if (result == NOTSHOWN)
        {
            printf ("Song with id %i was not found\n", item_id);
            printf ("Song is removed from the playlist\n");
            qm_mpd_command newCmd;
            newCmd.cmd = CMD_DEL;
            newCmd.song_id = item_id;
            execute_single_command(newCmd, true);
            mpd_response_finish(p_conn);
        }
        result = NONFATAL;
    }

    mpd_connection_clear_error(p_conn);

    if (config->cout_extensive)
    {
        QString msg;
        if (result == FATAL)
            msg  = "MPD: fatal error on \"" + action + "\": " + errormsg  + "\n";
        else
            if (result == NONFATAL)
                msg  = "MPD: non-fatal error on \"" + action + "\": " + errormsg  + "\n";
            else
                msg  = "MPD: unknown error on \"" + action + "\": " + errormsg  + "\n";

        printf ("%s", msg.toUtf8().constData());
    }

    if (result == FATAL)
    {
        // disconnnect & reconnect
        mpd_disconnect(false);
        emit signal_connected(false, b_remote_server);
        mpd_re_connect();
        // report the fatality
        return false;
    }

    // No need for action.
    return true;
}


void qm_mpdCommand::set_xfade(uint secs)
{
    if (p_conn == nullptr)
        return ;
    mpd_send_crossfade(p_conn, secs);
    mpd_response_finish(p_conn);
    error_check("set_xfade");
}


void qm_mpdCommand::set_timeout(uint secs)
{
    if (p_conn == nullptr)
        return ;
    // MPD wants msec
    mpd_connection_set_timeout(p_conn, secs * 1000);
    mpd_response_finish(p_conn);
    error_check("set_xfade");
}

// return "false" terminates the loop
bool qm_mpdCommand::status_check()
{
    if (!error_check("starting status_check"))
        return false;

    if (b_statcheck_busy)
        return true;

    b_statcheck_busy = true;

    /*  When connected with a socket we do not get an error when
        MPD is stopped: > mpd_run_status(p_conn) then segfaults.
        As a hack we first try to read 1 byte from the socket  */

    if (config->mpd_socket_conn)
    {
        char data;
        if ( ( recv(mpd_socket_fds, &data, MSG_PEEK, MSG_DONTWAIT) ) == 0)
        {
                printf ("MPD appears to have disappeared\n");
                mpd_disconnect(true);
                mpd_re_connect();
                return false;
        }
    }

    mpd_status *servStatus = nullptr;

    if (p_conn != nullptr)
    {
        servStatus = mpd_run_status(p_conn);

        if (!error_check("mpd_run_status") || servStatus == nullptr)
        {
                b_statcheck_busy = false;
                if (servStatus != nullptr)
                mpd_status_free(servStatus);
                return false;
        }
    }
    else
    {
        b_statcheck_busy = false;
        if (servStatus != nullptr)
                mpd_status_free(servStatus);
        mpd_response_finish(p_conn);
        error_check("mpd_send_status");
        return false;
    }

    // server has not found a supported mixer
    if (mpd_status_get_volume(servStatus) == -1)
    {
        if (!b_no_volume)
        {
                printf ("MPD: No supported mixer found\n");
                b_no_volume = true;
        }
    }
    else
        b_no_volume = false;


    // database update was requested && has finished
    if (b_dbase_updating && mpd_status_get_update_id(servStatus) == 0)
    {
        get_statistics();
        b_dbase_updating = false;
        printf ("Update/Rescan: ready\n");
        current_playlist = -1; // trigger list update
        current_song_id = -1;   // trigger reloading the song
        emit signal_update_ready();
    }

    // playlist was updated
    if (current_playlist < static_cast<int>(mpd_status_get_queue_version(servStatus)) )
    {
        current_playlist = static_cast<int>(mpd_status_get_queue_version(servStatus));
        printf ("Playlist: updated\n");
        get_playlist();
        // reload the song (streams use this to update song info)
        b_reload = true;
    }

    struct mpd_song *song = nullptr;
    // the song changed, or new playlist
    if( (current_song_id != mpd_status_get_song_id(servStatus))  || b_reload)
    {
        b_reload = false;
        // get the new song.
        song = mpd_run_current_song(p_conn);

        // empty list, last has finished or first hasn't started
        if (song == nullptr || !error_check("status_check: mpd_send_current_song"))
        {
                current_song_pos = -1;
                current_song_id = -1;
        }
        else
        {
                current_song_pos = mpd_status_get_song_pos(servStatus);
                current_song_id = mpd_status_get_song_id(servStatus);
        }

        // collect & send the song info
        get_info_for_song(current_songinfo, song);
        emit signal_new_song();
    }

    // send the status info
    emit signal_status_info(servStatus);

    current_status = mpd_status_get_state(servStatus);
    current_volume = mpd_status_get_volume(servStatus);

    // free memory
    if (servStatus != nullptr)
        mpd_status_free(servStatus);
    if (song != nullptr)
        mpd_song_free (song);

    // done
    b_statcheck_busy = false;
    return true;
}


bool qm_mpdCommand::get_album_art(QImage *art_image)
{
    bool result = false;

    const char *uri = static_cast<const char*>(current_songinfo->uri.toUtf8());

    if (uri == nullptr || uri[0] == '\0')
        return false;

    size_t chunk_size = 8192;

    if (config->mpd_readpic_allowed == true)
    {
        QByteArray qbA;
        bool b_error = false;
        char *bin_buffer =  new char[chunk_size];
        unsigned offset = 0;
        int recv_len = 0;

        while ( (recv_len = mpd_run_readpicture(p_conn, uri , offset, bin_buffer, chunk_size)) > 0)
        {
            if (recv_len < 0) // -1 error
            {
                b_error = true;
                break;
            }

            qbA.append(bin_buffer, recv_len);
            offset += (unsigned)recv_len;

            if (offset >= 5242880 - chunk_size) // 5MB - next chunk
            {
                printf ("Embedded albumart exceeds MPD's size limit\n");
                b_error = true;
                break;
            }
        }

        delete[] bin_buffer;

        if (!b_error && offset > 0) // something was found
         {
                result = art_image->loadFromData(qbA);
                if (!result)
                printf ("Loading art image from MPD failed\n");
        }
        else
            result = false;

        if (config->cout_extensive)
        {
            printf ("Album art (tag): received %i bytes from MPD\n", offset);
            // external song + a remote connection = MPD_ERROR_SERVER:
            error_check("mpd_run_readpicture");
        }
        else
            mpd_connection_clear_error(p_conn);
    }

    if (!result && config->mpd_albumart_allowed == true)
    {
        QByteArray qbA;
        bool b_error = false;
        char *bin_buffer =  new char[chunk_size];
        unsigned offset = 0;
        int recv_len = 0;

        while ( (recv_len = mpd_run_albumart(p_conn, uri , offset, bin_buffer, chunk_size)) > 0)
        {
            if (recv_len < 0) // -1 error
            {
                b_error = true;
                break;
            }

            qbA.append(bin_buffer, recv_len);
            offset += (unsigned)recv_len;

            if (offset >= 5242880 - chunk_size) // 5MB - next chunk
            {
                printf ("Albumart from file exceeds MPD's size limit\n");
                b_error = true;
                break;
            }
        }

        delete[] bin_buffer;

        if (!b_error && offset > 0) // something was found
        {
            result = art_image->loadFromData(qbA);
            if (!result)
                printf ("Loading art image from MPD failed\n");
        }
        else
            result = false;

        if (config->cout_extensive)
        {
            printf ("Album art (file): received %i bytes from MPD\n", offset);
            // external song + a remote connection = MPD_ERROR_SERVER:
            error_check("mpd_run_albumart");
        }
        else
            mpd_connection_clear_error(p_conn);
    }

    return result;
}


void qm_mpdCommand::get_info_for_song(qm_songInfo *sInfo, mpd_song *the_song)
{
    if (the_song == nullptr)
        sInfo->type = TP_NOSONG;
    else
    {
        sInfo->uri = QString::fromUtf8(mpd_song_get_uri(the_song));

        if (sInfo->uri.startsWith("http:") || sInfo->uri.startsWith("https:"))
        {
            sInfo->type = TP_STREAM;
            sInfo->time = 0;
        }
        else
        {
            if (sInfo->uri.startsWith("/")) // URL relative to MPD music dir doesn't have a leading '/'
                sInfo->type = TP_SONGX;
            else
                sInfo->type = TP_SONG;

            sInfo->time = static_cast<int>(mpd_song_get_duration(the_song));
        }

        sInfo->song_id = static_cast<int>(mpd_song_get_id(the_song));
        sInfo->song_pos = static_cast<int>(mpd_song_get_pos(the_song));
        if (mpd_song_get_tag(the_song, MPD_TAG_NAME, 0)) // always null
            sInfo->name = QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_NAME, 0));
        else
            sInfo->name =  sInfo->uri.section("/",-1);
        sInfo->artist  = QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_ARTIST, 0));
        sInfo->title  = QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_TITLE, 0));
        sInfo->album  = QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_ALBUM, 0));
        sInfo->track  = fix_track_number(QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_TRACK, 0)) );
        sInfo->disc  = QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_DISC, 0));
        sInfo->date  = QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_DATE, 0));
        // convert xxxx-xx-xx format to xxxx
        sInfo->date = sInfo->date.left(4);
        sInfo->genre  = QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_GENRE, 0));
        if (sInfo->type != TP_STREAM)
            sInfo->comment = QString::fromUtf8(mpd_song_get_tag(the_song, MPD_TAG_COMMENT, 0));
        else
            sInfo->comment = "Online audio stream";

        // No mpd_response_finish() here. Commands do not involve p_conn!!
    }
}


QString qm_mpdCommand::get_version(bool showmsg)
{
    QString version = "";
    if (p_conn == nullptr)
        return version;

    uint a  = mpd_connection_get_server_version(p_conn)[0];
    uint b  = mpd_connection_get_server_version(p_conn)[1];
    mpd_response_finish(p_conn);

    version += QString::number(a) + ".";
    version += QString::number(b) ;

    if (showmsg && a == 0 && b < 22)
        show_messagebox( tr("Connected to MPD ") + " " + version, "\n" + tr("Protocol 0.22 or above is required.") + "\n" + tr("Some functions will not work") );

    return version;
}


void qm_mpdCommand::reset_playlist()
{
    current_playlist--;
}


void qm_mpdCommand::get_playlist()
{
    qm_songinfo newPlayList;
    plist_lenght = 0;

    if (p_conn == nullptr)
    {
        emit signal_playlist_update(newPlayList, -10);
        return;
    }

    struct mpd_song *song = nullptr;
    mpd_entity *entity;
    mpd_send_list_queue_meta(p_conn);
    while((entity = mpd_recv_entity(p_conn)))
    {
        if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
        {
            song = mpd_song_dup (mpd_entity_get_song(entity));
            qm_songInfo *new_song_info = new qm_songInfo();
            get_info_for_song(new_song_info, song);
            newPlayList.push_back(*new_song_info);
            plist_lenght++;
        }
        mpd_entity_free(entity);
    }
    mpd_response_finish(p_conn);
    error_check("get_playlist");
    if (song != nullptr)
        mpd_song_free (song);

    int changedID = get_listchanger();
    // signal the library window
    emit signal_playlist_update(newPlayList, changedID);
    newPlayList.clear();
}


bool qm_mpdCommand::execute_single_command(qm_mpd_command command, bool resetlist)
{
    bool result = false;
    if (p_conn == nullptr)
        return result;
    /*
       the mpd_run commands include mpd_response_finish and return 0 on non-fatal
       errors, like when trying to add a non-audio file.
    */
    switch (command.cmd)
    {
        case CMD_ADD:
        {
            current_playlist++; // prevent list update
            if (mpd_run_add(p_conn, static_cast<const char*>(command.uri.toUtf8())))
            {
                plist_lenght++;
                result = true;
            }
            break;
        }

        case CMD_INS:
        {
            // add
            current_playlist++; // prevent list update
            if (mpd_run_add(p_conn, static_cast<const char*>(command.uri.toUtf8())))
            {
                plist_lenght++;
                result = true;
                // move into position
                current_playlist++; // prevent list update
                mpd_run_move(p_conn, plist_lenght-1, static_cast<uint>(command.moveto));
            }
            break;
        }

        case CMD_DEL:
        {
            // delete
            current_playlist++; // prevent list update
            if (mpd_run_delete_id(p_conn, static_cast<uint>(command.song_id)))
            {
                plist_lenght--;
                result = true;
            }
            break;
        }

    } // <- switch

    mpd_connection_clear_error(p_conn);
    // No error_check: we silently ignore txt files and such
    if (resetlist)
        current_playlist = -1;
    return result;
}

// cmdlist must start with the highest song_pos!
int qm_mpdCommand::execute_commands(qm_commandList cmdlist, bool resetlist)
{
    if(cmdlist.empty() ||p_conn == nullptr)
        return 0;

    int results = 0;
    mpd_command_list_begin(p_conn, true);

    QListIterator<qm_mpd_command> itr(cmdlist);
    while (itr.hasNext())
    {
        qm_mpd_command curCommand = itr.next();

        switch (curCommand.cmd)
        {
            case CMD_DPL: // delete stored playlist
            {
                if ( mpd_send_rm(p_conn, static_cast<const char*>(curCommand.uri.toUtf8())))
                    results++;
                break;
            }

            case CMD_ADD:
            {
                current_playlist++; // prevent list update
                if (mpd_send_add(p_conn, static_cast<const char*>(curCommand.uri.toUtf8())))
                {
                    plist_lenght++;
                    results++;
                }
                break;
            }

            case CMD_INS:
            {
                // add
                current_playlist++; // prevent list update
                if (mpd_send_add(p_conn, static_cast<const char*>(curCommand.uri.toUtf8())))
                {
                    plist_lenght++;
                    results++;
                    // move into position
                    current_playlist++; // prevent list update
                    mpd_send_move(p_conn, plist_lenght-1, static_cast<uint>(curCommand.moveto));
                }
                break;
            }

            case CMD_MOV: // move ID to pos
            {
                current_playlist++; // prevent list update
                if (mpd_send_move_id(p_conn,  static_cast<uint>(curCommand.song_id), static_cast<uint>(curCommand.moveto)))
                    results++;
                break;
            }

            case CMD_POS: // move from pos to pos
            {
                current_playlist++; // prevent list update
                if (mpd_send_move(p_conn,  static_cast<uint>(curCommand.song_pos), static_cast<uint>(curCommand.moveto)))
                    results++;
                break;
            }

            case CMD_DEL:
            {
                current_playlist++; // prevent list update
                if (mpd_send_delete_id(p_conn, static_cast<uint>(curCommand.song_id)))
                {
                    results++;
                    plist_lenght--;
                }
                break;
            }

            case CMD_SCN:
            {
                b_dbase_updating = true;
                current_playlist++; // prevent list update
                if (mpd_send_command(p_conn, "rescan", static_cast<const char*>(curCommand.uri.toUtf8()), nullptr))
                {
                    results++;
                }
                break;
            }

            case CMD_UPD:
            {
                b_dbase_updating = true;
                current_playlist++; // prevent list update
                if (mpd_send_command(p_conn, "update", static_cast<const char*>(curCommand.uri.toUtf8()), nullptr))
                {
                    results++;
                }
                break;
            }

            default:
                break;
        } // end switch
    }
    mpd_command_list_end(p_conn);
    mpd_response_finish(p_conn);
    error_check("execute_commands");

    cmdlist.clear();

    if (results > 0)
    {
        current_song_id = -1;
        current_song_pos = -1;
        current_status = -1;
        if (resetlist)
            current_playlist = -1;
    }
    return results;
}


void qm_mpdCommand::clear_list()
{
    if (p_conn == nullptr)
        return;
    mpd_send_clear(p_conn);
    mpd_response_finish(p_conn);
    error_check("clear_list");
    current_song_id = -1;
    current_song_pos = -1;
    current_status = -1;
    current_playlist = -1;
}


void qm_mpdCommand::shuffle_list()
{
    if (plist_lenght < 2 || p_conn == nullptr)
        return;

    mpd_send_shuffle(p_conn);
    mpd_response_finish(p_conn);
    error_check("shuffle_list 1");

    // move current song to the top
    if (current_song_id != -1)
    {
        current_playlist++; // prevent reloading
        mpd_send_move_id(p_conn, static_cast<uint>(current_song_id), 0);
        mpd_response_finish(p_conn);
        error_check("shuffle_list 2");
    }
    // trigger reloading the list
    current_playlist = -1;
}


void qm_mpdCommand::get_statistics()
{
    if (p_conn == nullptr)
    {
        emit signal_statistics(tr("Data not available"));
        return;
    }

    mpd_stats *sData = mpd_run_stats(p_conn);
    mpd_response_finish(p_conn);
    if (!error_check("mpd_run_stats") || sData == nullptr)
    {
        emit signal_statistics(tr("Data not available"));
        if (sData != nullptr)
            mpd_stats_free(sData);
        return;
    }

    QString statistics =  "<i>" + tr("Artists") + "</i> " + QString::number(mpd_stats_get_number_of_artists(sData)) +
                         "<i> &middot; " + tr("Albums") + "</i> "  + QString::number(mpd_stats_get_number_of_albums(sData)) +
                         "<i> &middot; " + tr("Songs")  + "</i> "  + QString::number(mpd_stats_get_number_of_songs(sData)) ;

    emit signal_statistics(statistics);

    if (sData != nullptr)
        mpd_stats_free(sData);
}


QString qm_mpdCommand::save_playlist(QString listname)
{
    if (p_conn == nullptr)
        return "Error: MPD is not connected.";

    mpd_send_save(p_conn, static_cast<const char*>(listname.toUtf8()) );
    mpd_response_finish(p_conn);

    emit signal_playlist_saved();

    if (!error_check("save_playlist"))
        return "Error: " + QString::fromUtf8(mpd_connection_get_error_message(p_conn));
    else
        return "Playlist \"" + listname + "\" was successfully saved.";
}


QString qm_mpdCommand::save_selection(QStringList tobeSaved, QString listname)
{
    if (p_conn == nullptr)
        return "Error: MPD is not connected.";

    mpd_command_list_begin(p_conn, true);
    for (int i = 0; i < tobeSaved.size(); ++i)
    {
        mpd_send_playlist_add(p_conn, static_cast<const char*>(listname.toUtf8()), static_cast<const char*>(tobeSaved.at(i).toUtf8()));
    }
    mpd_command_list_end(p_conn);
    mpd_response_finish(p_conn);

    emit signal_playlist_saved();

    if (!error_check("save_selection"))
        return "Error: " + QString::fromUtf8(mpd_connection_get_error_message(p_conn));
    else
        return "Playlist \"" + listname + "\" was successfully saved.";
}


void qm_mpdCommand::show_messagebox(QString message, QString info)
{
    // if true quimup closes when the dialog closes:
    QGuiApplication::setQuitOnLastWindowClosed(false);
    QMessageBox msgBox;
    msgBox.setWindowTitle(tr(" "));
    msgBox.setIcon(QMessageBox::Warning);
    msgBox.setText("<b>" + message + " </b>");
    msgBox.setInformativeText(info);
    msgBox.exec();
    QGuiApplication::setQuitOnLastWindowClosed(true);
}


qm_itemList qm_mpdCommand::get_yearlist()
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;
    QString year = NULL_STRING;
    QString newyear;
    QString album = NULL_STRING;
    QString newalbum;
    struct mpd_song *song;

    mpd_send_list_all_meta(p_conn, "");
    while ((song = mpd_recv_song (p_conn)))
    {
        if (mpd_song_get_tag (song, MPD_TAG_DATE, 0) != nullptr )
            newyear = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_DATE, 0));
        else
            newyear = "";

        // convert xxxx-xx-xx format to xxxx
        if (!newyear.isEmpty())
        newyear = newyear.left(4) + " ";

        if (mpd_song_get_tag (song, MPD_TAG_ALBUM, 0) != nullptr )
            newalbum = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ALBUM, 0));
        else
            newalbum = "?";

        if (album != newalbum || year != newyear)
        {
            qm_listItemInfo newItem;
            newItem.type = TP_ALBUM;
            newItem.album = newalbum;
            newItem.artist = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ARTIST, 0));
            if (newyear.isEmpty())
            {
                newItem.sorter = "0000" + newalbum + newItem.artist;
                newItem.year = "";
            }
            else
            {
                newItem.sorter = newyear + newalbum + newItem.artist;
                newItem.year = newyear;
            }

            itemlist.push_back(newItem);
            album = newalbum;
            year = newyear;
        }

        mpd_song_free (song);
    }
    mpd_response_finish (p_conn);

    error_check("get_yearlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_albumlist(bool sort_by_timestamp)
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;
    uint nowtime = (QDateTime::currentDateTime()).toSecsSinceEpoch();
    int nowtime_length = QString::number(nowtime).length();

    QString album = NULL_STRING;
    QString newalbum = "";
    QString artist = NULL_STRING;
    QString newartist = "";
    struct mpd_song *song;

    mpd_send_list_all_meta(p_conn, "");
    while ((song = mpd_recv_song (p_conn)))
    {
        if (mpd_song_get_tag (song, MPD_TAG_ALBUM, 0) != nullptr )
                newalbum = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ALBUM, 0));
        else
            newalbum = "";

        if (mpd_song_get_tag (song, MPD_TAG_ARTIST, 0) != nullptr )
            newartist = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ARTIST, 0));
        else
            newartist = "";

        if (album != newalbum || artist != newartist)
        {
            qm_listItemInfo newItem;
            newItem.type = TP_ALBUM;
            newItem.album = newalbum;
            newItem.artist = newartist;

            if (sort_by_timestamp)
            {
                QDateTime time;

                time.setSecsSinceEpoch(static_cast<uint>(mpd_song_get_last_modified(song)));
                newItem.moddate = time.toString("yy/MM/dd  hh:mm");

                // invert the sort order
                qint64 oldlast =  nowtime - time.toSecsSinceEpoch();
                QString sort  = QString::number( oldlast );

                 while (sort.length() < nowtime_length)
                    sort = "0" + sort;

                newItem.sorter = sort + newItem.sorter;
            }
            else
                newItem.sorter = newalbum + newartist;

            itemlist.push_back(newItem);
        }

        album = newalbum;
        artist = newartist;

        mpd_song_free (song);
    }

    mpd_response_finish (p_conn);
    error_check("get_albumlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_album_searchlist(QString searchfor)
{
    qm_itemList itemlist;
    if (p_conn == nullptr)
        return itemlist;

    if (searchfor.isEmpty())
        return get_albumlist(false);

    QString album = NULL_STRING;
    QString newalbum;
    QString artist = NULL_STRING;
    QString newartist;
    struct mpd_song *song;

    mpd_search_db_songs(p_conn, false);
    if (mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, static_cast<const char*>(searchfor.toUtf8()))
        && mpd_search_commit(p_conn))
    {
        while ((song = mpd_recv_song (p_conn)))
        {
            if (mpd_song_get_tag (song, MPD_TAG_ARTIST, 0) != nullptr )
                newartist = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ARTIST, 0));
            else
                newartist = "";

            if (mpd_song_get_tag (song, MPD_TAG_ALBUM, 0) != nullptr )
            {
                newalbum = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ALBUM, 0));

                if (album != newalbum || artist != newartist)
                {
                    qm_listItemInfo newItem;
                    newItem.type = TP_ALBUM;
                    newItem.album = newalbum;
                    newItem.artist = newartist;
                    newItem.sorter = newalbum + newartist;
                    itemlist.push_back(newItem);
                    album = newalbum;
                    artist = newartist;
                }
            }
            else
                if (searchfor.isEmpty())
                {
                    newalbum = "";

                    if (!album.isEmpty() || artist != newartist)
                    {
                        qm_listItemInfo newItem;
                        newItem.type = TP_ALBUM;
                        newItem.album = newalbum;
                        newItem.artist = newartist;
                        newItem.sorter = newalbum + newartist;
                        itemlist.push_back(newItem);
                        album = newalbum;
                        artist = newartist;
                    }
                }
            mpd_song_free (song);
        } // while ...
    }

    mpd_response_finish (p_conn);
    error_check("get_album_searchlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_albums_for_artist(QString artist)
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;

    QString album = NULL_STRING;
    QString newalbum;
    struct mpd_song *song;
    mpd_search_db_songs(p_conn, true);
    if (mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_ARTIST, static_cast<const char*>(artist.toUtf8()))
        && mpd_search_commit(p_conn))
    {
        while ((song = mpd_recv_song (p_conn)))
        {

            if (mpd_song_get_tag (song, MPD_TAG_ALBUM, 0) != nullptr )
            {
                newalbum = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ALBUM, 0));

                if (album != newalbum)
                {
                    qm_listItemInfo newItem;
                    newItem.type = TP_ALBUM;
                    newItem.album = newalbum;
                    newItem.artist = artist;
                    newItem.sorter = newalbum;
                    itemlist.push_back(newItem);
                    album = newalbum;
                }
            }
            else
            {
                newalbum = "";
                if (!album.isEmpty())
                {
                    qm_listItemInfo newItem;
                    newItem.type = TP_ALBUM;
                    newItem.album = newalbum;
                    newItem.artist = artist;
                    newItem.sorter = newalbum;
                    itemlist.push_back(newItem);
                    album = newalbum;
                }
            }
            mpd_song_free (song);
        } // while ...
    }

    mpd_response_finish (p_conn);
    error_check("get_album_for_artist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_artistlist()
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;

    QString artist = NULL_STRING;
    QString newartist;
    struct mpd_song *song;
    mpd_send_list_all_meta(p_conn, "");
    while ((song = mpd_recv_song (p_conn)))
    {
        if (mpd_song_get_tag (song, MPD_TAG_ARTIST, 0) != nullptr )
        {
            newartist = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ARTIST, 0));

            if (artist != newartist)
            {
                qm_listItemInfo newItem;
                newItem.type = TP_ARTIST;
                newItem.artist = newartist;
                newItem.sorter = newartist;
                if (config->ignore_leading_the && (newItem.sorter).startsWith("the ", Qt::CaseInsensitive) )
                    newItem.sorter = (newItem.sorter).right((newItem.sorter).size() - 4);
                itemlist.push_back(newItem);
                artist = newartist;
            }
        }
        else
        {
            newartist = "";

            if (!artist.isEmpty())
            {
                qm_listItemInfo newItem;
                newItem.type = TP_ARTIST;
                newItem.artist = newartist;
                newItem.sorter = newartist;
                if (config->ignore_leading_the && (newItem.sorter).startsWith("the ", Qt::CaseInsensitive) )
                    newItem.sorter = (newItem.sorter).right((newItem.sorter).size() - 4);
                itemlist.push_back(newItem);
                artist = newartist;
            }
        }
        mpd_song_free (song);
    }
    mpd_response_finish (p_conn);
    error_check("get_artistlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_artist_searchlist(QString searchfor)
{
    qm_itemList itemlist;
    if (p_conn == nullptr)
        return itemlist;

    if (searchfor.isEmpty())
        return get_artistlist();

    QString artist = NULL_STRING;
    QString newartist;
    struct mpd_song *song;
    mpd_search_db_songs(p_conn, false);
    if (mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_ARTIST, static_cast<const char*>(searchfor.toUtf8()))
        && mpd_search_commit(p_conn))
    {
        while ((song = mpd_recv_song (p_conn)))
        {

            if (mpd_song_get_tag (song, MPD_TAG_ARTIST, 0) != nullptr )
            {
                newartist = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_ARTIST, 0));

                if (artist != newartist)
                {
                    qm_listItemInfo newItem;
                    newItem.type = TP_ARTIST;
                    newItem.artist = newartist;
                    newItem.sorter = newartist;
                    if (config->ignore_leading_the && (newItem.sorter).startsWith("the ", Qt::CaseInsensitive) )
                        newItem.sorter = (newItem.sorter).right((newItem.sorter).size() - 4);
                    itemlist.push_back(newItem);
                    artist = newartist;
                }
            }
            else
                if (searchfor.isEmpty())
                {
                    newartist = "";

                    if (!artist.isEmpty())
                    {
                        qm_listItemInfo newItem;
                        newItem.type = TP_ARTIST;
                        newItem.artist = newartist;
                        newItem.sorter = newartist;
                        if (config->ignore_leading_the && (newItem.sorter).startsWith("the ", Qt::CaseInsensitive) )
                            newItem.sorter = (newItem.sorter).right((newItem.sorter).size() - 4);
                        itemlist.push_back(newItem);
                        artist = newartist;
                    }
                }
            mpd_song_free (song);
        } // while ...
    }

    mpd_response_finish (p_conn);
    error_check("get_artist_searchlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_playlistlist()
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;

    struct mpd_entity *ent;

    // get playlists in MPD's playlist directory
    mpd_send_list_meta (p_conn, "");
    while ((ent = mpd_recv_entity (p_conn)))
    {
        if (mpd_entity_get_type (ent) == MPD_ENTITY_TYPE_PLAYLIST)
        {
            qm_listItemInfo newItem;
            newItem.type = TP_PLAYLIST;
            const struct mpd_playlist *playlist = mpd_entity_get_playlist(ent);
            QString path = QString::fromUtf8(mpd_playlist_get_path (playlist));
            newItem.uri = path;
            // put only the list name in 'name'
            newItem.name = path.section("/",-1);
            newItem.sorter = newItem.name;
            itemlist.push_back(newItem);
        }
        mpd_entity_free (ent);
    }
    mpd_response_finish (p_conn);
    error_check("get_playlistlist-1");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_dir_searchlist(QString searchfor)
{
    dirsearchlist.clear();

    if (p_conn == nullptr)
        return dirsearchlist;

    if (searchfor.isEmpty())
        return get_dirlist("/");

    search_directory_tree("", searchfor);
    return dirsearchlist;
}

// this calls itself to check subdirs
void qm_mpdCommand::search_directory_tree(QString file_path, QString searchfor)
{
    if (p_conn == nullptr)
        return;

    qm_itemList itemlist;
    int num_dirs = 0;
    struct mpd_entity *entity;
    mpd_send_list_meta(p_conn, static_cast<const char*>(file_path.toUtf8()));
    while( (entity = mpd_recv_entity(p_conn)) != nullptr )
    {
        num_dirs++;
        if( mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_DIRECTORY )
        {
            qm_listItemInfo newItem;
            const struct mpd_directory *directory = mpd_entity_get_directory(entity);
            // mpd_directory_get_path returns the path of this directory,
            // relative to the MPD music directory. It does not begin with /
            newItem.uri = QString::fromUtf8(mpd_directory_get_path (directory));
            newItem.name = newItem.uri.section("/",-1);
            itemlist.push_back(newItem);
            if ((newItem.name).contains(searchfor, Qt::CaseInsensitive))
            {
                newItem.type = TP_DIRECTORY;
                newItem.sorter = newItem.name;
                dirsearchlist.push_back(newItem);
            }
        }
        mpd_entity_free(entity);
    } // end while
    mpd_response_finish (p_conn);
    error_check("get_directorysearchlist");

    // now search the subdirs.
    if (num_dirs > 0)
    {
        std::list<qm_listItemInfo>::iterator itr;
        for (itr = itemlist.begin(); itr != itemlist.end(); ++itr)
        {
                search_directory_tree(itr->uri, searchfor);
        }
    }
}


qm_itemList qm_mpdCommand::get_dirlist(QString file_path)
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;

    struct mpd_entity *entity;

    mpd_send_list_meta(p_conn, static_cast<const char*>(file_path.toUtf8()));
    while( (entity = mpd_recv_entity(p_conn)) != nullptr )
    {
        if( mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_DIRECTORY )
        {
            qm_listItemInfo newItem;
            const struct mpd_directory *directory = mpd_entity_get_directory(entity);
            // mpd_directory_get_path returns the path of this directory,
            // relative to the MPD music directory. It does not begin with /
            QString path = QString::fromUtf8(mpd_directory_get_path (directory));
            newItem.uri = path;
            newItem.name = path.section("/",-1);  // .name is also used for files and playlists
            newItem.type = TP_DIRECTORY;
            newItem.sorter = "0" + newItem.name; // "0" so dirs come first
            itemlist.push_back(newItem);
        }
        else
        if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
        {
            qm_listItemInfo newItem;
            const struct mpd_song *song = mpd_entity_get_song (entity);
            newItem.uri = QString::fromUtf8(mpd_song_get_uri(song));
            newItem.type = TP_SONG; // never a stream here
            newItem.time = static_cast<int>(mpd_song_get_duration(song));
            newItem.name = newItem.uri.section("/",-1);  // .name is also used for dirs and playlists
            newItem.sorter = "1" + newItem.name; // "1" so files come next
            newItem.artist = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
            newItem.album = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
            newItem.title = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TITLE, 0));
            newItem.track = fix_track_number(QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TRACK, 0)) );
            newItem.genre = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_GENRE, 0));
            itemlist.push_back(newItem);
        }
        else // .cue playlists only
        if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST)
        {
            qm_listItemInfo newItem;
            newItem.type = TP_PLAYLIST;
            const struct mpd_playlist *playlist = mpd_entity_get_playlist(entity);
            QString path = QString::fromUtf8(mpd_playlist_get_path (playlist));
            newItem.uri = path;
            // put only the list name in 'name'
            newItem.name = path.section("/",-1);  // .name is also used for dirs and files
            newItem.sorter =  "2" + newItem.name; // "2" so playlists come last
            // filter out m3u files?:
            //if (newItem.name.endsWith(".cue"))
            itemlist.push_back(newItem);
        }

        mpd_entity_free(entity);
    } // end while
    mpd_response_finish (p_conn);
    error_check("get_dirlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_songlist()
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;

    struct mpd_song *song;
     mpd_send_list_all_meta(p_conn, "");
    while ( (song = mpd_recv_song(p_conn)) )
    {
        qm_listItemInfo newItem;
        newItem.uri = QString::fromUtf8(mpd_song_get_uri(song));
        newItem.name = newItem.uri.section("/",-1);
        newItem.type = TP_SONG; // never a stream here
        newItem.time = static_cast<int>(mpd_song_get_duration(song));
        newItem.artist = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
        newItem.album = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
        newItem.title = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TITLE, 0));
        newItem.genre = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_GENRE, 0));
        newItem.sorter = newItem.artist + newItem.album;
        newItem.disc = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_DISC, 0));
        newItem.track = fix_track_number(QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TRACK, 0)) );

        if (newItem.title.isEmpty())
            newItem.sorter = "00000" + newItem.artist;
        else
            newItem.sorter = newItem.title + newItem.artist;

        itemlist.push_back(newItem);
        mpd_song_free(song);
    } // while ...

    mpd_response_finish (p_conn);
    error_check("get_songlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_songs_for_any(QString album, QString artist, QString genre)
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;
    struct mpd_song *song;
    mpd_search_db_songs(p_conn, true);
    if (artist != NULL_STRING)
        mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_ARTIST, static_cast<const char*>(artist.toUtf8()));
    if (album != NULL_STRING)
        mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, static_cast<const char*>(album.toUtf8()));
    if (genre != NULL_STRING)
        mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_GENRE, static_cast<const char*>(genre.toUtf8()));

    if (mpd_search_commit(p_conn))
    {
        while ( (song = mpd_recv_song(p_conn)) )
        {
            qm_listItemInfo newItem;
            newItem.uri = QString::fromUtf8(mpd_song_get_uri(song));
            newItem.name = newItem.uri.section("/",-1);
            newItem.type = TP_SONG; // never a stream here
            newItem.time = static_cast<int>(mpd_song_get_duration(song));
            newItem.artist = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
            newItem.album = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
            newItem.title = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TITLE, 0));
            newItem.genre = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_GENRE, 0));
            newItem.sorter = newItem.artist + newItem.album;
            newItem.disc = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_DISC, 0));
            if (!newItem.disc.isEmpty())
                newItem.sorter += newItem.disc;
            else
                newItem.sorter += "00000000"; // no disc tag comes 1st
            newItem.track = fix_track_number(QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TRACK, 0)) );
            // 0-999 tracks should be enough
            if (newItem.track.size() == 0)
                newItem.sorter += "000";
            else
            if (newItem.track.size() == 1)
                newItem.sorter += "00" + newItem.track;
            else
            if (newItem.track.size() == 2)
                newItem.sorter += "0" + newItem.track;
            else
                newItem.sorter += newItem.track;
            //
            if (newItem.title.isEmpty())
                newItem.sorter += newItem.uri;
            else
                newItem.sorter += newItem.title;

            itemlist.push_back(newItem);

            mpd_song_free(song);
        } // while ...
     }

    mpd_response_finish(p_conn);
    error_check("get_songs_for_any");
    return itemlist;
}


qm_itemList qm_mpdCommand::get_song_searchlist(QString searchfor)
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;

    if (searchfor.isEmpty())
        return get_songlist();

    struct mpd_song *song;
    mpd_search_db_songs(p_conn, false);
    if (mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_TITLE, static_cast<const char*>(searchfor.toUtf8()))
        && mpd_search_commit(p_conn))
    {
        while ((song = mpd_recv_song (p_conn)))
        {
            if (mpd_song_get_tag (song, MPD_TAG_TITLE, 0) != nullptr )
            {
                qm_listItemInfo newItem;
                newItem.uri = QString::fromUtf8(mpd_song_get_uri(song));
                newItem.name = newItem.uri.section("/",-1);
                newItem.type = TP_SONG; // never a stream here
                newItem.time = static_cast<int>(mpd_song_get_duration(song));
                newItem.artist = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
                newItem.album = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
                newItem.title = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TITLE, 0));
                newItem.genre = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_GENRE, 0));
                newItem.track = fix_track_number(QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TRACK, 0)) );

                if (newItem.title.isEmpty())
                    newItem.sorter = "00000" + newItem.artist;
                else
                    newItem.sorter = newItem.title + newItem.artist;

                itemlist.push_back(newItem);
            }
            mpd_song_free (song);
        } // while ...
    }
    mpd_response_finish (p_conn);
    error_check("get_song_searchlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_artists_for_genre(QString searchfor)
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;

    QString artist = NULL_STRING;
    QString newartist;
    QString album = NULL_STRING;
    QString newalbum;
    struct mpd_song *song;
    mpd_search_db_songs(p_conn, true);

    if (mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_GENRE, static_cast<const char*>(searchfor.toUtf8()))
        && mpd_search_commit(p_conn))
    {
        while ((song = mpd_recv_song (p_conn)))
        {
            QString genre = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_GENRE, 0));
            if ( genre == searchfor )
            {
                newalbum = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
                newartist = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
                if (artist != newartist || album != newalbum)
                {
                    qm_listItemInfo newItem;
                    newItem.genre = searchfor;
                    newItem.type = TP_ARTIST;
                    newItem.artist = newartist;
                    newItem.album = newalbum;
                    newItem.sorter = newItem.artist + newItem.album;
                    itemlist.push_back(newItem);
                    artist =newartist;
                    album = newalbum;
                }
            }

            mpd_song_free (song);
        } // while ...
    }

    mpd_response_finish (p_conn);
    error_check("get_artists_for_genre");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_genrelist()
{
    qm_itemList itemlist;
    if (p_conn == nullptr)
        return itemlist;

    QString newgenre;
    struct mpd_song *song;
    mpd_send_list_all_meta(p_conn, "");
    while ((song = mpd_recv_song (p_conn)))
    {
        if (mpd_song_get_tag (song, MPD_TAG_GENRE, 0) != nullptr )
        {
            newgenre = QString::fromUtf8(mpd_song_get_tag (song, MPD_TAG_GENRE, 0));
            qm_listItemInfo newItem;
            newItem.genre = newgenre;
            newItem.sorter = newgenre;
            itemlist.push_back(newItem);
        }
        mpd_song_free (song);
    }

    mpd_response_finish (p_conn);
    error_check("get_genresearchlist");

    return itemlist;
}


qm_itemList qm_mpdCommand::get_genre_searchlist(QString searchfor)
{
    qm_itemList itemlist;
    if (p_conn == nullptr)
        return itemlist;

    if (searchfor.isEmpty()) // get full list
        return get_genrelist();

    struct mpd_song *song;
    mpd_search_db_songs(p_conn, false);
    if (mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_GENRE, static_cast<const char*>(searchfor.toUtf8()))
            && mpd_search_commit(p_conn))
    {
        while ((song = mpd_recv_song (p_conn)))
        {
            if (mpd_song_get_tag (song, MPD_TAG_GENRE, 0) != nullptr )
            {
                qm_listItemInfo newItem;
                newItem.genre = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_GENRE, 0));
                newItem.sorter = newItem.genre; // not toLower() or libview mays show things like: Jazz jazz Jazz
                itemlist.push_back(newItem);
            }
            else
            if (searchfor.isEmpty())
            {
                qm_listItemInfo newItem;
                newItem.genre = "";
                newItem.sorter = "0";
                itemlist.push_back(newItem);
            }
            mpd_song_free (song);
        } // while ...
    }

    mpd_response_finish (p_conn);
    error_check("get_genre_searchlist");

    return itemlist;
}


QString qm_mpdCommand::fix_track_number(QString instring)
{
    // remove the x/xx format
    QString str = instring;
    str =  str.left(str.lastIndexOf("/"));
    // add leading zero, when required
    if (str.size() == 1)
        str = "0" + str;
    return str;
}


QString qm_mpdCommand::get_year_for_album(QString album, QString artist)
{
    QString year = "";

    if (p_conn == nullptr)
        return year;

    if (album.isEmpty() || artist.isEmpty())
        return year;

    struct mpd_pair *pair;
    mpd_search_db_tags(p_conn, MPD_TAG_DATE);
    if (mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_ARTIST, static_cast<const char*>(artist.toUtf8()))
        && mpd_search_add_tag_constraint (p_conn, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM,  static_cast<const char*>(album.toUtf8()))
        && mpd_search_commit(p_conn))
    {
        while ((pair = mpd_recv_pair_tag (p_conn, MPD_TAG_DATE)))
        {
            // take the first that comes along
            year = QString::fromUtf8(pair->value);
            mpd_return_pair(p_conn, pair);
            if (!year.isEmpty())
                break;
        }
    }
    mpd_response_finish (p_conn);
    error_check("get_year_for_album");

    // convert xxx-xx-xx format toi xxxx
    if (!year.isEmpty())
        year = year.left(4);

    return year;
}


void qm_mpdCommand::reset_current_song()
{
    current_song_id = -1;
}


qm_itemList qm_mpdCommand::get_items_for_playlist(QString playlist)
{
    qm_itemList itemlist;

    if (p_conn == nullptr)
        return itemlist;

    struct mpd_song *song;
    mpd_send_list_playlist_meta(p_conn, static_cast<const char*>(playlist.toUtf8()));

    while ((song = mpd_recv_song(p_conn)))
    {
        qm_listItemInfo newItem;
        newItem.uri = QString::fromUtf8(mpd_song_get_uri(song));
        if (newItem.uri.startsWith("http:") || newItem.uri.startsWith("https:"))
        {
            newItem.type = TP_STREAM;
            newItem.time = 0;
        }
        else
        {
            if (newItem.uri.startsWith("/")) // URL relative to MPD music dir doesn't have a leading '/'
                newItem.type = TP_SONGX;
            else
                newItem.type = TP_SONG;
            newItem.time = static_cast<int>(mpd_song_get_duration(song));
        }
        newItem.name = newItem.uri.section("/",-1);
        newItem.artist = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
        newItem.album = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
        newItem.title = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TITLE, 0));
        newItem.genre = QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_GENRE, 0));
        newItem.track = fix_track_number(QString::fromUtf8(mpd_song_get_tag(song, MPD_TAG_TRACK, 0)) );

        itemlist.push_back(newItem);
        mpd_song_free (song);
    }
    mpd_response_finish(p_conn);
    error_check("get_items_for_playlist");
    return itemlist;
}

// find out which song (ID) caused the playlist to update
int qm_mpdCommand::get_listchanger()
{
    int id = -1;
    if (p_conn == nullptr)
        return id;

    mpd_entity *entity = nullptr;
    mpd_send_queue_changes_meta(p_conn, static_cast<uint>(current_playlist-1));
    while (( entity = mpd_recv_entity(p_conn)) != nullptr)
    {
        // get the first that comes along
        if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
            id = static_cast<int>(mpd_song_get_id( mpd_entity_get_song(entity) ));
        mpd_entity_free(entity);
        if (id != -1)
            break;
    }
    mpd_response_finish(p_conn);
    error_check("get_listchanger");
    return id;
}


void qm_mpdCommand::set_replaygain_mode(int mode)
{
    if (p_conn == nullptr)
        return;
    const char *rg_mode;

    switch (mode)
    {
        case 0:
        {
            rg_mode = "off";
            break;
        }
        case 1:
        {
            rg_mode = "track";
            break;
        }
        case 2:
        {
            rg_mode = "album";
            break;
        }
        case 3:
        {
            rg_mode = "auto";
            break;
        }
        default:
            return;
    }
    mpd_send_command(p_conn, "replay_gain_mode", rg_mode, nullptr);
    mpd_response_finish(p_conn);
    error_check("set_replaygain_mode");
}


void qm_mpdCommand::mpd_disconnect(bool sendsignal)
{
    // stop the status loop
    if (statusLoop->isActive())
        statusLoop->stop();
    // disconnect if connected
    if (p_conn != nullptr)
    {
        mpd_connection_clear_error(p_conn);
        mpd_connection_free(p_conn);
        p_conn = nullptr;
    }

    current_playlist = -1;
    current_song_id = -1;
    current_song_pos = -1;
    current_status = -1;
    plist_lenght = 0;

    if (sendsignal)
    {
        current_songinfo->reset();
        config->reset_temps();

        emit signal_connected(false, b_remote_server);
    }
}


void qm_mpdCommand::set_songinfo(qm_songInfo *song_info)
{
    current_songinfo = song_info;
}


QString qm_mpdCommand::find_config_files(bool show_dialog)
{
/*  If not specified on the command-line or in /etc/default/mpd
    MPD searches
    1st at    ~/.config/mpd/mpd.conf
    2nd at     ~/.mpdconf
    3rd at     ~/.mpd/mpd.conf
    4th at     /etc/mpd.conf
 */

    printf ("MPD's config ");
    QString thispath;
    QString homeDir = QDir::homePath();
    if (!homeDir.endsWith("/"))
        homeDir.append("/");
    QFile file;

    thispath = homeDir + ".config/mpd/mpd.conf";
    file.setFileName(thispath);
    if ( file.exists() )
    {
        if (file.open(QIODevice::ReadOnly))
        {
            file.close();
            printf ("located: ~/.config/mpd/mpd.conf\n");
            return thispath;
        }
        else
        {
            if (config->cout_extensive)
                printf ("~/.config/mpd/mpd.conf, but it is not readable\n");
        }
    }

    thispath = homeDir + ".mpdconf";
    file.setFileName(thispath);
    if ( file.exists() )
    {
        if (file.open(QIODevice::ReadOnly))
        {
            file.close();
            printf ("located: ~/.mpdconf\n");
            return thispath;
        }
        else
        {
            if (config->cout_extensive)
                printf ("~/.mpdconf, but it is not readable\n");
        }
    }

    thispath = homeDir + ".mpd/mpd.conf";
    file.setFileName(thispath);
    if ( file.exists() )
    {
        if (file.open(QIODevice::ReadOnly))
        {
            file.close();
            printf ("located: ~/.mpd/mpd.conf\n");
            return thispath;
        }
        else
        {
            if (config->cout_extensive)
                printf ("~/.mpd/mpd.conf, but it is not readable\n");
        }
    }

    thispath = "/etc/mpd.conf";
    file.setFileName(thispath);
    if ( file.exists() )
    {
        if (file.open(QIODevice::ReadOnly))
        {
            file.close();
            printf ("located: /etc/mpd.conf\n");
            return thispath;
        }
        else
        {
            if (config->cout_extensive)
                printf ("/etc/mpd.conf, but it is not readable\n");
        }
    }

        printf ("not found! Is MPD installed?\n");
        if (show_dialog)
            show_messagebox(tr("Auto configuration did not succeed"),
                            tr("Please create a custom connection profile in the 'Connect' tab of the settings window.") );

        return thispath;
}


void qm_mpdCommand::get_autodetect_params()
{
        // reset
        int server_port = 0;
        QString server_name = "";
        QString server_password = "";
        QString musicdir = "";
        QString remark;

        // get
        if (b_mpdconf_found)
        {
            server_port = static_cast<uint>(get_port());
            if (!(server_port > 1024))
                server_port = 6600;
            server_name = get_host();
            if (server_name.isEmpty())
                server_name = "localhost";
            server_password = get_password();
            musicdir = get_musicPath();
            remark = tr("Detected");
        }
        else
            remark = tr("mpd.conf not found");

        config->profile_note_0 = remark;
        config->profile_port_0 = server_port;
        config->profile_host_0 = server_name;
        config->profile_pswd_0 = server_password;
        config->profile_mdir_0 = musicdir;
}


bool qm_mpdCommand::validate_ip_address(QString server_name)
{
        uchar validity = 0;
        // Regexpression for IPv4
        QRegularExpression ipv4("(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])");

        // Regexpression for IPv6
        QRegularExpression ipv6("((([0-9a-fA-F]){1,4})\\:){7}([0-9a-fA-F]){1,4}");
        int pos = 0;
        QValidator *vdt = new QRegularExpressionValidator(ipv4, this);

        validity = vdt->validate(server_name, pos);
        if (validity != QValidator::Acceptable)
        {
            QValidator *vdt = new QRegularExpressionValidator(ipv6, this);
            validity = vdt->validate(server_name, pos);
        }

        if ( validity == QValidator::Acceptable )
        {
            if (config->cout_extensive)
                printf ("Host \"%s\" is recognized as IP address\n", server_name.toStdString().c_str());
            return true;
        }
        else
        {
            if (config->cout_extensive)
                printf ("Host \"%s\" is not an IP address\n", server_name.toStdString().c_str());
            return false;
        }
}


bool qm_mpdCommand::start_mpd()
{
    if ( !config->onstart_mpd_command.isNull() && !config->onstart_mpd_command.isEmpty())
    {
        printf("Executing command: \"%s\" ... ", config->onstart_mpd_command.toStdString().c_str());
        QProcess *proc = nullptr;
        QString cmd = config->onstart_mpd_command.section(" ", 0, 0);
        QStringList args;
        args = config->onstart_mpd_command.split((" "));
        if (!args.at(0).isNull())
            args.remove(0); // only keep the argumens
        proc->startDetached(cmd, args);
    }

    // allow MPD to get ready
    int sleepcount = 0;
    bool success = false;
    while (sleepcount < 10) // 2 sec max
    {
        if (!is_mpd_running(false))
            usleep(200000);
        else
        {
            success = true;
            break;
        }
        sleepcount++;
    }

    if (success)
        printf ("succeeded!\n");
    else
    {
        printf ("failed!\n");
    }

    return success;
}


bool qm_mpdCommand::is_mpd_installed(bool showmsg)
{
    QProcess proc;
    proc.setProgram("which");
    proc.setArguments({"mpd"});
    proc.start();
    if (!proc.waitForFinished())
    {
        if (config->cout_extensive)
          printf ("Failed to determine if MPD is installed!\n");
        return false;
    }

    QString output = proc.readAllStandardOutput();
    output = output.trimmed();
    if (output.contains("/usr/bin/mpd") || output.contains("/usr/local/bin/mpd") || output.contains("/usr/sbin/mpd"))
    {
        if (showmsg)
            printf ("MPD is installed on this system\n");
        return true;
    }
    else
    {
        if (showmsg)
            printf ("MPD is not installed on this system\n");
        return false;
    }
}


bool qm_mpdCommand::is_mpd_running(bool showmsg)
{
    config->mpd_process_owner = ""; // empty equals not running

    QProcess proc;
    proc.setProgram("pidof");
    proc.setArguments({"mpd"});
    proc.start();
    if (!proc.waitForFinished())
        return false;

    QString pid = proc.readAllStandardOutput();
    pid = pid.trimmed();
    if (pid.isEmpty())
    {
        if (showmsg)
            printf ("MPD is not running on this sytem\n");
        return false;
    }
    // else
    if (showmsg)
        printf ("MPD is running on this sytem\n");

    proc.setProgram("ps");
    proc.setArguments({"-o", "user=", "-p", pid});
    proc.start();
    if (!proc.waitForFinished())
    {
        printf ("failed to identify owner of 'mpd' process\n");
    }
    else
    {
        QString owner = proc.readAllStandardOutput();
        owner = owner.trimmed();
         if (!owner.isEmpty())
        {
            if (showmsg)
                printf ("The 'mpd' process is owned by: %s\n", owner.toStdString().c_str());
            config->mpd_process_owner = owner;
        }
    }

    return true;
}

// called from player when shutting down
void qm_mpdCommand::stop_connect_thread()
{
    if (connect_Thread != nullptr)
    {
        // b_shutting_down blocks reconnecting in on_threadfinished_sgnl()
        b_shutting_down = true;

        if (connect_Thread->isRunning())
            connect_Task->abort_loop();

        connect_Thread->exit();

        while (!connect_Thread->wait())
        {
            if (config->cout_extensive)
                printf ("Waiting for reconnect thread to exit\n");
        }
    }
}

qm_mpdCommand::~qm_mpdCommand()
{}
