You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeaddons/kicker-applets/mediacontrol/mpdInterface.cpp

586 lines
15 KiB

/***************************************************************************
Interface to access mpd
-------------------
begin : Tue Apr 19 18:31:00 BST 2005
copyright : (C) 2005 by William Robinson
email : airbaggins@yahoo.co.uk
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "mpdInterface.h"
#include <cstring>
#include <tqregexp.h>
#include <tdemessagebox.h>
#include <kdebug.h>
#include <kurldrag.h>
MpdInterface::MpdInterface()
: PlayerInterface()
, sock()
, sock_mutex()
, messagebox_mutex()
, hostname("localhost")
, port(6600)
, slider_timer(0)
, reconnect_timer(0)
{
connect(&sock, TQ_SIGNAL(error(int)), this, TQ_SLOT(connectionError(int)));
connect(&sock, TQ_SIGNAL(error(int)), this, TQ_SLOT(stopSliderClock()));
connect(&sock, TQ_SIGNAL(connected()), this, TQ_SLOT(startSliderClock()));
connect(&sock, TQ_SIGNAL(connected()), this, TQ_SLOT(stopReconnectClock()));
connect(&sock, TQ_SIGNAL(connected()), this, TQ_SLOT(connected()));
connect(&sock, TQ_SIGNAL(connectionClosed()), this, TQ_SLOT(stopSliderClock()));
connect(&sock, TQ_SIGNAL(connectionClosed()), this, TQ_SLOT(startReconnectClock()));
connect(&sock, TQ_SIGNAL(connectionClosed()), this, TQ_SIGNAL(playerStopped()));
reconnect();
}
MpdInterface::~MpdInterface()
{
}
void MpdInterface::startSliderClock()
{
if (!slider_timer)
{
//kdDebug(90200) << "Starting slider clock\n";
slider_timer = startTimer(SLIDER_TIMER_INTERVAL);
}
}
void MpdInterface::stopSliderClock()
{
if (slider_timer)
{
//kdDebug(90200) << "Stopping slider clock\n";
killTimer(slider_timer);
slider_timer=0;
}
}
void MpdInterface::startReconnectClock()
{
if (!reconnect_timer)
{
//kdDebug(90200) << "Starting Reconnect clock\n";
reconnect_timer = startTimer(RECONNECT_TIMER_INTERVAL);
}
}
void MpdInterface::stopReconnectClock()
{
if (reconnect_timer)
{
//kdDebug(90200) << "Stopping Reconnect clock\n";
killTimer(reconnect_timer);
reconnect_timer=0;
}
}
void MpdInterface::timerEvent(TQTimerEvent* te)
{
if (te->timerId() == slider_timer) updateSlider();
else if (te->timerId() == reconnect_timer) reconnect();
}
void MpdInterface::reconnect() const
{
if (sock.state()==TQSocket::Idle)
{
sock_mutex.tryLock();
//kdDebug(90200) << "Connecting to " << hostname.latin1() << ":" << port << "...\n";
sock.connectToHost(hostname,port);
}
}
void MpdInterface::connected()
{
if (fetchOk()) // unlocks
{
//kdDebug(90200) << "Connected ok\n";
emit playerStarted();
emit playingStatusChanged(playingStatus());
}
else
{
//kdDebug(90200) << "Connection error\n";
emit playerStopped();
}
}
void MpdInterface::connectionError(int e)
{
sock_mutex.unlock();
emit playerStopped();
TQString message;
if (messagebox_mutex.tryLock())
{
switch (e)
{
case TQSocket::ErrConnectionRefused:
message=i18n("Connection refused to %1:%2.\nIs mpd running?").arg(hostname).arg(port);
break;
case TQSocket::ErrHostNotFound:
message=i18n("Host '%1' not found.").arg(hostname);
break;
case TQSocket::ErrSocketRead:
message=i18n("Error reading socket.");
break;
default:
message=i18n("Connection error");
break;
}
// :TODO: KSimpleConfig to prompt for hostname/port values ?
if (KMessageBox::warningContinueCancel( 0, message,
i18n("MediaControl MPD Error"),
i18n("Reconnect"))==KMessageBox::Continue)
{
startReconnectClock();
}
else
{
stopReconnectClock();
}
messagebox_mutex.unlock();
}
}
bool MpdInterface::dispatch(const char* cmd) const
{
if (sock.state()==TQSocket::Connected && sock_mutex.tryLock())
{
long cmd_len=strlen(cmd);
//kdDebug(90200) << "sending: " << cmd;
long written=sock.writeBlock(cmd,cmd_len);
if (written==cmd_len)
{
//kdDebug(90200) << "All bytes written\n";
sock.flush();
return true;
}
else
{
//kdDebug(90200) << written << '/' << cmd_len << " bytes written\n";
}
sock.flush();
}
return false;
}
bool MpdInterface::fetchLine(TQString& res) const
{
TQString errormessage;
while (sock.state()==TQSocket::Connected)
{
if (!sock.canReadLine())
{
sock.waitForMore(20);
continue;
}
res=sock.readLine().stripWhiteSpace();
//kdDebug(90200) << "received: " << res.latin1() << "\n";
if (res.startsWith("OK"))
{
sock_mutex.unlock();
// if theres a message and we clear it and there's no other messagebox
if (!errormessage.isEmpty()
&& dispatch("clearerror\n") && fetchOk()
&& messagebox_mutex.tryLock())
{
KMessageBox::error(0,errormessage,i18n("MediaControl MPD Error"));
messagebox_mutex.unlock();
}
return false;
}
else if (res.startsWith("ACK"))
{
sock_mutex.unlock();
return false;
}
else if (res.startsWith("error: "))
{
errormessage=i18n(res.latin1());
}
else
{
return true;
}
}
sock_mutex.unlock();
return false;
}
bool MpdInterface::fetchOk() const
{
TQString res;
while (fetchLine(res)) { }
if (res.startsWith("OK"))
return true;
else
return false;
}
void MpdInterface::updateSlider()
{
//kdDebug(90200) << "update slider\n";
if (!dispatch("status\n")) return;
TQString res;
TQRegExp time_re("time: (\\d+):(\\d+)");
while(fetchLine(res))
{
if (res.startsWith("state: "))
{
if (res.endsWith("play"))
{
emit playingStatusChanged(Playing);
}
else if (res.endsWith("pause"))
{
emit playingStatusChanged(Paused);
}
else
{
emit playingStatusChanged(Stopped);
}
}
else if (time_re.search(res)>=0)
{
TQStringList timeinfo=time_re.capturedTexts();
timeinfo.pop_front();
int elapsed_seconds=timeinfo.first().toInt();
timeinfo.pop_front();
int total_seconds=timeinfo.first().toInt();
emit newSliderPosition(total_seconds,elapsed_seconds);
}
}
}
void MpdInterface::sliderStartDrag()
{
stopSliderClock();
}
void MpdInterface::sliderStopDrag()
{
startSliderClock();
}
void MpdInterface::jumpToTime(int sec)
{
reconnect();
if (!dispatch("status\n")) return;
long songid=-1;
TQString res;
TQRegExp songid_re("songid: (\\d+)");
while(fetchLine(res))
{
if (songid_re.search(res)>=0)
{
TQStringList songidinfo=songid_re.capturedTexts();
songidinfo.pop_front();
songid=songidinfo.first().toInt();
}
}
if (songid>-1)
{
if (dispatch(TQString("seekid %1 %2\n").arg(songid).arg(sec).latin1()))
{
fetchOk(); // unlocks
}
}
}
void MpdInterface::playpause()
{
reconnect();
if (playingStatus()==Stopped ? dispatch("play\n") : dispatch("pause\n"))
{
fetchOk();
}
}
void MpdInterface::stop()
{
reconnect();
if (dispatch("stop\n")) fetchOk();
}
void MpdInterface::next()
{
reconnect();
if (dispatch("next\n")) fetchOk();
}
void MpdInterface::prev()
{
reconnect();
if (dispatch("previous\n")) fetchOk();
}
void MpdInterface::changeVolume(int delta)
{
reconnect();
if (!dispatch("status\n")) return;
int volume=-1;
TQString res;
TQRegExp volume_re("volume: (\\d+)");
while(fetchLine(res))
{
if (volume_re.search(res)>=0)
{
TQStringList info=volume_re.capturedTexts();
info.pop_front();
volume=info.first().toInt();
}
}
if (volume>-1)
{
volume+=delta;
if (volume<0) volume=0;
if (volume>100) volume=100;
if (dispatch(TQString("setvol %1\n").arg(volume).latin1()))
{
fetchOk();
}
}
}
void MpdInterface::volumeUp()
{
reconnect();
changeVolume(5);
}
void MpdInterface::volumeDown()
{
reconnect();
changeVolume(-5);
}
void MpdInterface::dragEnterEvent(TQDragEnterEvent* event)
{
event->accept( KURLDrag::canDecode(event) );
}
void MpdInterface::dropEvent(TQDropEvent* event)
{
reconnect();
KURL::List list;
if (KURLDrag::decode(event, list))
{
if (list.count()==1) // just one file dropped
{
// check to see if its in the playlist already
if (dispatch("playlistid\n"))
{
long songid=-1;
TQString file;
TQString res;
while(fetchLine(res))
{
TQRegExp file_re("file: (.+)");
TQRegExp id_re("Id: (.+)");
if (file.isEmpty() && file_re.search(res)>=0)
{
TQStringList info=file_re.capturedTexts();
info.pop_front();
// if the dropped file ends with the same name, record it
if (list.front().path().endsWith(info.first()))
{
file=info.first().toInt();
}
}
else if (!file.isEmpty() && id_re.search(res)>=0)
{
// when we have the file, pick up the id (file scomes first)
TQStringList info=id_re.capturedTexts();
info.pop_front();
songid=info.first().toInt();
fetchOk(); // skip to the end
break;
}
}
// found song, so lets play it
if (songid>-1)
{
if (dispatch((TQString("playid %1\n").arg(songid)).latin1()))
{
if (fetchOk()) list.pop_front();
return;
}
}
}
}
// now if we have got this far, just try to add any files
for (KURL::List::const_iterator i = list.constBegin(); i!=list.constEnd(); ++i)
{
if ((*i).isLocalFile())
{
TQStringList path=TQStringList::split("/",(*i).path());
while (!path.empty())
{
if (dispatch((TQString("add \"")
+path.join("/").replace("\"","\\\"")
+TQString("\"\n")).latin1()))
{
if (fetchOk()) break;
}
path.pop_front();
}
}
else
{
// :TODO: can handle http:// urls but maybe should check port or something
}
}
}
}
const TQString MpdInterface::getTrackTitle() const
{
TQString result;
reconnect();
if (!dispatch("status\n")) return result;
long songid=-1;
TQString res;
while(fetchLine(res))
{
TQRegExp songid_re("songid: (\\d+)");
if (songid_re.search(res)>=0)
{
TQStringList songidinfo=songid_re.capturedTexts();
songidinfo.pop_front();
songid=songidinfo.first().toInt();
}
}
if (!(songid>-1)) return result;
if (!dispatch(TQString("playlistid %1\n").arg(songid).latin1()))
return result;
TQString artist;
TQString album;
TQString title;
TQString track;
TQString file;
while(fetchLine(res))
{
TQRegExp artist_re("Artist: (.+)");
TQRegExp album_re("Album: (.+)");
TQRegExp track_re("Album: (.+)");
TQRegExp title_re("Title: (.+)");
TQRegExp file_re("file: (.+)");
if (artist_re.search(res)>=0)
{
TQStringList info=artist_re.capturedTexts();
info.pop_front();
artist=info.first();
}
else if (album_re.search(res)>=0)
{
TQStringList info=album_re.capturedTexts();
info.pop_front();
album=info.first();
}
else if (title_re.search(res)>=0)
{
TQStringList info=title_re.capturedTexts();
info.pop_front();
title=info.first();
}
else if (track_re.search(res)>=0)
{
TQStringList info=track_re.capturedTexts();
info.pop_front();
track=info.first();
}
else if (file_re.search(res)>=0)
{
TQStringList info=file_re.capturedTexts();
info.pop_front();
file=info.first();
}
}
if (!artist.isEmpty())
{
if (!title.isEmpty())
return artist.append(" - ").append(title);
else if (!album.isEmpty())
return artist.append(" - ").append(album);
}
else if (!title.isEmpty())
{
if (!album.isEmpty())
return album.append(" - ").append(title);
else
return title;
}
else if (!album.isEmpty())
{
if (!track.isEmpty())
return album.append(" - ").append(track);
else
return album;
}
return i18n("No tags: %1").arg(file);
}
int MpdInterface::playingStatus()
{
//kdDebug(90200) << "looking up playing status\n";
if (!dispatch("status\n")) return Stopped;
PlayingStatus status=Stopped;
TQString res;
while(fetchLine(res))
{
if (res.startsWith("state: "))
{
if (res.endsWith("play")) status=Playing;
else if (res.endsWith("pause")) status=Paused;
else status=Stopped;
}
}
return status;
}
#include "mpdInterface.moc"