xaizek / d2if (License: GPLv2+) (since 2018-12-07)
Simple dzen2 input formatter, which is supposed to not waste CPU time for nothing and provides a number of builtin "widgets" for displaying system information.
<root> / src / MpdClient.cpp (fb8e8e2890f76278545a387c5d9fbb4a78e8aab9) (5,915B) (mode 100644) [raw]
// d2if
// Copyright (C) 2014 xaizek.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA

#include "MpdClient.hpp"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <netdb.h>
#include <unistd.h>

#include <cerrno>

#include <algorithm>
#include <iterator>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

namespace
{
    class PerLine
    {
    public:
        friend std::istream & operator>>(std::istream &is, PerLine &pl)
        {
            std::string str;
            std::getline(is, str);
            pl.str = str;
            return is;
        }

        operator std::string() const
        {
            return str;
        }

    private:
        std::string str;
    };
}

Sock::Sock()
    : sock(-1)
{
}

Sock::Sock(int sock)
    : sock(sock)
{
}

Sock::~Sock()
{
    reset();
}

Sock::operator int()
{
    return sock;
}

Sock & Sock::operator=(int sock)
{
    reset(sock);
    return *this;
}

void Sock::reset(int newSock)
{
    if (sock != -1) {
        ::close(sock);
    }
    sock = newSock;
}

MpdClient::MpdClient(const std::string &host, int port)
{
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == -1) {
        throw std::runtime_error("Can't create socket.");
    }

    sockaddr_in addr = { };

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(host.c_str());
    addr.sin_port = htons(port);

    if (::connect(sock, reinterpret_cast<sockaddr *>(&addr),
                  sizeof(addr)) != 0) {
        throw std::runtime_error("Can't connect socket.");
    }

    (void)receive();
}

std::string MpdClient::sendAndReceive(const std::string &request)
{
    send(request);
    return receive();
}

std::string MpdClient::getCurrentSong()
{
    colon_data_t metadata = makeRequest("currentsong");

    const std::string artist = metadata["Artist"];
    const std::string title = metadata["Title"];

    if (artist.empty() && title.empty()) {
        // use file name without extension if both artist and title tags are
        // empty

        const std::string file = metadata["file"];
        // relying on integer overflow when find_first_not_of() returns npos
        const int namePos = file.find_last_of('/') + 1;
        const int dotPos = file.find_last_of('.');
        return std::string(file, namePos, dotPos - namePos);
    }

    if (artist.empty() || title.empty()) {
        return artist.empty() ? title : artist;
    }

    return artist + " − " + title;
}

MpdState MpdClient::getState()
{
    colon_data_t metadata = makeRequest("status");

    const std::string stateStr = metadata["state"];
    if (stateStr == "play") {
        return MpdState::Play;
    } else if(stateStr == "pause") {
        return MpdState::Pause;
    } else {
        return MpdState::Stop;
    }
}

void MpdClient::waitForChanges()
{
    static_cast<void>(sendAndReceive("idle player"));
}

MpdClient::colon_data_t MpdClient::makeRequest(const std::string &cmd)
{
    std::istringstream response(sendAndReceive(cmd));

    std::vector<std::string> lines;
    typedef std::istream_iterator<PerLine> isIt;
    std::copy(isIt(response), isIt(), std::back_inserter(lines));

    if (lines.empty()) {
        throw std::runtime_error("Got empty MPD.");
    }

    if (lines[lines.size() - 1] != "OK") {
        throw std::runtime_error("Got bad MPD response.");
    }
    lines.pop_back();

    colon_data_t colonData;
    typedef std::vector<std::string>::const_iterator it_t;
    for (it_t it = lines.begin(); it != lines.end(); ++it) {
        const std::string &str = *it;

        const std::size_t colonPos = str.find(':');
        if (colonPos == std::string::npos) {
            continue;
        }

        const std::size_t dataPos = str.find_first_not_of(' ', colonPos + 1);
        if (dataPos == std::string::npos) {
            continue;
        }

        const std::string tag(str, 0, colonPos);
        const std::string value(str, dataPos);

        if (colonData.find(tag) != colonData.cend()) {
            colonData[tag] += ", " + value;
        } else {
            colonData[tag] = value;
        }
    }

    return colonData;
}

void MpdClient::send(std::string request)
{
    if (request.empty()) {
        throw std::invalid_argument("Request can't be empty.");
    }

    if (request[request.size() - 1] != '\n') {
        request += '\n';
    }

    const ssize_t n = request.size();
    if (::send(sock, request.data(), n, 0) != n) {
        throw std::runtime_error("Can't send.");
    }
}

std::string MpdClient::receive()
{
    std::string response;

    int flags = 0;

    while (true) {
        std::string buf(4096, '\0');

        const ssize_t n = ::recv(sock, &buf[0], buf.size(), flags);

        if (n == 0) {
            // mpd exited
            break;
        }

        if (n == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                break;
            }

            throw std::runtime_error("Can't receive: " +
                                     std::string(::strerror(errno)));
        }

        response.append(buf.data(), n);

        flags = MSG_DONTWAIT;
    }

    return response;
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/d2if

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/d2if

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master