diff options
author | lordjaxom <lordjaxom> | 2004-12-30 22:43:55 +0000 |
---|---|---|
committer | lordjaxom <lordjaxom> | 2004-12-30 22:43:55 +0000 |
commit | 302fa2e67276bd0674e81e2a9a01b9e91dd45d8c (patch) | |
tree | a454884a16e0ffa48b5ce3e4ce1a66eb874a9de0 /client/socket.c | |
download | vdr-plugin-streamdev-302fa2e67276bd0674e81e2a9a01b9e91dd45d8c.tar.gz vdr-plugin-streamdev-302fa2e67276bd0674e81e2a9a01b9e91dd45d8c.tar.bz2 |
Initial revision
Diffstat (limited to 'client/socket.c')
-rw-r--r-- | client/socket.c | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/client/socket.c b/client/socket.c new file mode 100644 index 0000000..44af8db --- /dev/null +++ b/client/socket.c @@ -0,0 +1,591 @@ +/* + * $Id: socket.c,v 1.1 2004/12/30 22:44:04 lordjaxom Exp $ + */ + +#include <tools/select.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +#include "client/socket.h" +#include "client/setup.h" +#include "client/remote.h" +#include "common.h" +#include "i18n.h" + +cClientSocket ClientSocket; + +cClientSocket::cClientSocket(void) { + memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count); + Reset(); +} + +cClientSocket::~cClientSocket() { + Reset(); + if (IsOpen()) Quit(); +} + +void cClientSocket::Reset(void) { + m_StreamPIDS = false; + + for (int it = 0; it < si_Count; ++it) + if (m_DataSockets[it] != NULL) + DELETENULL(m_DataSockets[it]); +} + +cTBSocket *cClientSocket::DataSocket(eSocketId Id) const { + return m_DataSockets[Id]; +} + +bool cClientSocket::Command(const cTBString &Command, uint Expected, + uint TimeoutMs) { + cTBString pkt; + time_t st; + + errno = 0; + + pkt = Command + "\015\012"; + Dprintf("OUT: |%s|\n", (const char*)Command); + + st = time_ms(); + if (!TimedWrite((const char*)pkt, pkt.Length(), TimeoutMs)) { + esyslog("Streamdev: Lost connection to %s:%d: %s", + (const char*)RemoteIp(), RemotePort(), strerror(errno)); + Close(); + return false; + } + + if (Expected != 0) { + TimeoutMs -= time_ms() - st; + return Expect(Expected, NULL, TimeoutMs); + } + + return true; +} + +bool cClientSocket::Expect(uint Expected, cTBString *Result, uint TimeoutMs) { + char *buffer; + char *endptr; + int bufcount; + bool res; + + errno = 0; + + buffer = new char[BUFSIZ + 1]; + + if ((bufcount = ReadUntil(buffer, BUFSIZ, "\012", TimeoutMs)) + == -1) { + esyslog("Streamdev: Lost connection to %s:%d: %s", + (const char*)RemoteIp(), RemotePort(), strerror(errno)); + Close(); + delete[] buffer; + return false; + } + if (buffer[bufcount - 1] == '\015') + --bufcount; + buffer[bufcount] = '\0'; + Dprintf("IN: |%s|\n", buffer); + + if (Result != NULL) + *Result = buffer; + + res = strtoul(buffer, &endptr, 10) == Expected; + delete[] buffer; + return res; +} + +bool cClientSocket::CheckConnection(void) { + CMD_LOCK; + + if (IsOpen()) { + cTBSelect select; + + Dprintf("connection open\n"); + + // XXX+ check if connection is still alive (is there a better way?) + // There REALLY shouldn't be anything readable according to PROTOCOL here + // If there is, assume it's an eof signal (subseq. read would return 0) + select.Add(*this, false); + int res; + if ((res = select.Select(0)) == 0) { + Dprintf("select said nothing happened\n"); + return true; + } + Dprintf("closing connection (res was %d)", res); + Close(); + } + + if (!Connect(StreamdevClientSetup.RemoteIp, StreamdevClientSetup.RemotePort)){ + esyslog("ERROR: Streamdev: Couldn't connect to %s:%d: %s", + (const char*)StreamdevClientSetup.RemoteIp, + StreamdevClientSetup.RemotePort, strerror(errno)); + return false; + } + + if (!Expect(220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Didn't receive greeting from %s:%d", + (const char*)RemoteIp(), RemotePort()); + Close(); + return false; + } + + if (!Command((cTBString)"CAPS TS", 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't negotiate capabilities on %s:%d", + (const char*)RemoteIp(), RemotePort()); + Close(); + return false; + } + + if (StreamdevClientSetup.StreamPIDS) { + if (!Command("CAPS TSPIDS", 220)) { + if (errno != 0) { + Close(); + return false; + } + + esyslog("ERROR: Streamdev: Server %s:%d isn't capable of PID streaming", + (const char*)RemoteIp(), RemotePort()); + } else + m_StreamPIDS = true; + } + + isyslog("Streamdev: Connected to server %s:%d using capabilities TS%s", + (const char*)RemoteIp(), RemotePort(), m_StreamPIDS ? ", TSPIDS" : ""); + return true; +} + +bool cClientSocket::ProvidesChannel(const cChannel *Channel, int Priority) { + cTBString buffer; + + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command("PROV " + cTBString::Number(Priority) + " " + + Channel->GetChannelID().ToString())) + return false; + + if (!Expect(220, &buffer)) { + if (buffer.Left(3) != "560" && errno == 0) + esyslog("ERROR: Streamdev: Couldn't check if %s:%d provides channel %s", + (const char*)RemoteIp(), RemotePort(), Channel->Name()); + return false; + } + return true; +} + +bool cClientSocket::CreateDataConnection(eSocketId Id) { + int idx; + cTBSocket listen(SOCK_STREAM); + cTBString buffer; + + if (!CheckConnection()) return false; + + if (m_DataSockets[Id] != NULL) + DELETENULL(m_DataSockets[Id]); + + if (!listen.Listen((const char*)LocalIp(), 0, 1)) { + esyslog("ERROR: Streamdev: Couldn't create data connection: %s", + strerror(errno)); + return false; + } + + buffer.Format("PORT %d %s,%d,%d", Id, (const char*)LocalIp(), + (listen.LocalPort() >> 8) & 0xff, listen.LocalPort() & 0xff); + idx = 5; + while ((idx = buffer.Find('.', idx + 1)) != -1) + buffer[idx] = ','; + + CMD_LOCK; + + if (!Command(buffer, 220)) { + Dprintf("error: %m\n"); + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't establish data connection to %s:%d", + (const char*)RemoteIp(), RemotePort()); + return false; + } + + /* The server SHOULD do the following: + * - get PORT command + * - connect to socket + * - return 220 + */ + + m_DataSockets[Id] = new cTBSocket; + if (!m_DataSockets[Id]->Accept(listen)) { + esyslog("ERROR: Streamdev: Couldn't establish data connection to %s:%d%s%s", + (const char*)RemoteIp(), RemotePort(), errno == 0 ? "" : ": ", + errno == 0 ? "" : strerror(errno)); + DELETENULL(m_DataSockets[Id]); + return false; + } + + return true; +} + +bool cClientSocket::SetChannelDevice(const cChannel *Channel) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command((cTBString)"TUNE " + Channel->GetChannelID().ToString(), 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't tune %s:%d to channel %s", + (const char*)RemoteIp(), RemotePort(), Channel->Name()); + return false; + } + return true; +} + +bool cClientSocket::SetPid(int Pid, bool On) { + if (!CheckConnection()) return false; + + if (m_StreamPIDS) { + Dprintf("m_StreamPIDS is ON\n"); + CMD_LOCK; + + if (!Command((On ? "ADDP " : "DELP ") + cTBString::Number(Pid), 220)) { + if (errno == 0) + esyslog("Streamdev: Pid %d not available from %s:%d", Pid, + (const char*)LocalIp(), LocalPort()); + return false; + } + } + return true; +} + +#if VDRVERSNUM >= 10300 +bool cClientSocket::SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On) { + cTBString cmd; + if (!CheckConnection()) return false; + + CMD_LOCK; + cmd.Format("%s %hu %hhu %hhu", On ? "ADDF" : "DELF", Pid, Tid, Mask); + if (!Command(cmd, 220)) { + if (errno == 0) + esyslog("Streamdev: Filter %hu, %hhu, %hhu not available from %s:%d", + Pid, Tid, Mask, (const char*)LocalIp(), LocalPort()); + return false; + } + return true; +} +#endif + +bool cClientSocket::CloseDvr(void) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (m_DataSockets[siLive] != NULL) { + if (!Command("ABRT " + cTBString::Number(siLive), 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't cleanly close data connection"); + return false; + } + + DELETENULL(m_DataSockets[siLive]); + } + return true; +} + +bool cClientSocket::SynchronizeEPG(void) { + cTBString buffer; + bool res; + FILE *epgfd; + + if (!CheckConnection()) return false; + + isyslog("Streamdev: Synchronizing EPG from server\n"); + + CMD_LOCK; + + if (!Command("LSTE")) + return false; + + if ((epgfd = tmpfile()) == NULL) { + esyslog("ERROR: Streamdev: Error while processing EPG data: %s", + strerror(errno)); + return false; + } + + while ((res = Expect(215, &buffer))) { + if (buffer[3] == ' ') break; + fputs((const char*)buffer + 4, epgfd); + fputc('\n', epgfd); + } + + if (!res) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch EPG data from %s:%d", + (const char*)RemoteIp(), RemotePort()); + fclose(epgfd); + return false; + } + + rewind(epgfd); + if (cSchedules::Read(epgfd)) +#if VDRVERSNUM < 10300 + cSIProcessor::TriggerDump(); +#else + cSchedules::Cleanup(true); +#endif + else { + esyslog("ERROR: Streamdev: Parsing EPG data failed"); + fclose(epgfd); + return false; + } + fclose(epgfd); + return true; +} + +bool cClientSocket::Quit(void) { + bool res; + + if (!CheckConnection()) return false; + + if (!(res = Command("QUIT", 221))) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't quit command connection to %s:%d", + (const char*)RemoteIp(), RemotePort()); + } + Close(); + return res; +} + +bool cClientSocket::LoadRecordings(cRemoteRecordings &Recordings) { + cTBString buffer; + bool res; + + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command("LSTR")) + return false; + + while ((res = Expect(250, &buffer))) { + cRemoteRecording *rec = new cRemoteRecording((const char*)buffer + 4); + Dprintf("recording valid: %d\n", rec->IsValid()); + if (rec->IsValid()) + Recordings.Add(rec); + else + delete rec; + if (buffer[3] == ' ') break; + } + + if (!res && buffer.Left(3) != "550") { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch recordings from %s:%d", + (const char*)RemoteIp(), RemotePort()); + return false; + } + + for (cRemoteRecording *r = Recordings.First(); r; r = Recordings.Next(r)) { + if (!Command("LSTR " + cTBString::Number(r->Index()))) + return false; + + if (Expect(250, &buffer)) + r->ParseInfo((const char*)buffer + 4); + else if (buffer.Left(3) != "550") { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch details for recording from " + "%s:%d", (const char*)RemoteIp(), RemotePort()); + return false; + } + Dprintf("recording complete: %d\n", r->Index()); + } + return res; +} + +bool cClientSocket::StartReplay(const char *Filename) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command((cTBString)"PLAY " + Filename, 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't replay \"%s\" from %s:%d", + Filename, (const char*)RemoteIp(), RemotePort()); + return false; + } + return true; +} + +bool cClientSocket::AbortReplay(void) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (m_DataSockets[siReplay] != NULL) { + if (!Command("ABRT " + cTBString::Number(siReplay), 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't cleanly close data connection"); + return false; + } + + DELETENULL(m_DataSockets[siReplay]); + } + return true; +} + +bool cClientSocket::DeleteRecording(cRemoteRecording *Recording) { + bool res; + cTBString buffer; + cRemoteRecording *rec = NULL; + + if (!CheckConnection()) + return false; + + CMD_LOCK; + + if (!Command("LSTR")) + return false; + + while ((res = Expect(250, &buffer))) { + if (rec == NULL) { + rec = new cRemoteRecording((const char*)buffer + 4); + if (!rec->IsValid() || rec->Index() != Recording->Index()) + DELETENULL(rec); + } + if (buffer[3] == ' ') break; + } + + if (!res && buffer.Left(3) != "550") { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch recordings from %s:%d", + (const char*)RemoteIp(), RemotePort()); + if (rec != NULL) delete rec; + return false; + } + + if (rec == NULL || *rec != *Recording) { + ERROR(tr("Recordings not in sync! Try again...")); + return false; + } + + if (!Command("DELR " + cTBString::Number(Recording->Index()), 250)) { + ERROR(tr("Couldn't delete recording! Try again...")); + return false; + } + return true; +} + +bool cClientSocket::SuspendServer(void) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command("SUSP", 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't suspend server"); + return false; + } + return true; +} + +bool cClientSocket::LoadTimers(cRemoteTimers &Timers) { + cTBString buffer; + bool res; + + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command("LSTT")) + return false; + + while ((res = Expect(250, &buffer))) { + cRemoteTimer *timer = new cRemoteTimer((const char*)buffer + 4); + Dprintf("timer valid: %d\n", timer->IsValid()); + if (timer->IsValid()) + Timers.Add(timer); + if (buffer[3] == ' ') break; + } + + if (!res && buffer.Left(3) != "550") { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch recordings from %s:%d", + (const char*)RemoteIp(), RemotePort()); + return false; + } + return res; +} + +bool cClientSocket::SaveTimer(cRemoteTimer *Old, cRemoteTimer &New) { + cTBString buffer; + + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (New.Index() == -1) { // New timer + if (!Command((cTBString)"NEWT " + New.ToText(), 250)) { + ERROR(tr("Couldn't save timer! Try again...")); + return false; + } + } else { // Modified timer + if (!Command("LSTT " + cTBString::Number(New.Index()))) + return false; + + if (!Expect(250, &buffer)) { + if (errno == 0) + ERROR(tr("Timers not in sync! Try again...")); + else + ERROR(tr("Server error! Try again...")); + return false; + } + + cRemoteTimer oldstate((const char*)buffer + 4); + if (oldstate != *Old) { + /*Dprintf("old timer: %d,%d,%d,%d,%d,%d,%s,%d,%s,%d\n", oldstate.m_Index, + oldstate.m_Active,oldstate.m_Day,oldstate.m_Start,oldstate.m_StartTime,oldstate.m_Priority,oldstate.m_File,oldstate.m_FirstDay,(const char*)oldstate.m_Summary,oldstate.m_Channel->Number()); + Dprintf("new timer: %d,%d,%d,%d,%d,%d,%s,%d,%s,%d\n", Old->m_Index, + Old->m_Active,Old->m_Day,Old->m_Start,Old->m_StartTime,Old->m_Priority,Old->m_File,Old->m_FirstDay,(const char*)Old->m_Summary,Old->m_Channel->Number());*/ + ERROR(tr("Timers not in sync! Try again...")); + return false; + } + + if (!Command("MODT " + cTBString::Number(New.Index()) + " " + + New.ToText(), 250)) { + ERROR(tr("Couldn't save timer! Try again...")); + return false; + } + } + return true; +} + +bool cClientSocket::DeleteTimer(cRemoteTimer *Timer) { + cTBString buffer; + + if (!CheckConnection()) + return false; + + CMD_LOCK; + + if (!Command("LSTT " + cTBString::Number(Timer->Index()))) + return false; + + if (!Expect(250, &buffer)) { + if (errno == 0) + ERROR(tr("Timers not in sync! Try again...")); + else + ERROR(tr("Server error! Try again...")); + return false; + } + + cRemoteTimer oldstate((const char*)buffer + 4); + + if (oldstate != *Timer) { + ERROR(tr("Timers not in sync! Try again...")); + return false; + } + + if (!Command("DELT " + cTBString::Number(Timer->Index()), 250)) { + ERROR(tr("Couldn't delete timer! Try again...")); + return false; + } + return true; +} |