summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorlordjaxom <lordjaxom>2004-12-30 22:43:55 +0000
committerlordjaxom <lordjaxom>2004-12-30 22:43:55 +0000
commit302fa2e67276bd0674e81e2a9a01b9e91dd45d8c (patch)
treea454884a16e0ffa48b5ce3e4ce1a66eb874a9de0 /client
downloadvdr-plugin-streamdev-302fa2e67276bd0674e81e2a9a01b9e91dd45d8c.tar.gz
vdr-plugin-streamdev-302fa2e67276bd0674e81e2a9a01b9e91dd45d8c.tar.bz2
Initial revision
Diffstat (limited to 'client')
-rw-r--r--client/assembler.c125
-rw-r--r--client/assembler.h32
-rw-r--r--client/device.c185
-rw-r--r--client/device.h58
-rw-r--r--client/filter.c141
-rw-r--r--client/filter.h64
-rw-r--r--client/menu.c1047
-rw-r--r--client/menu.h144
-rw-r--r--client/remote.c475
-rw-r--r--client/remote.h132
-rw-r--r--client/setup.c83
-rw-r--r--client/setup.h39
-rw-r--r--client/socket.c591
-rw-r--r--client/socket.h71
14 files changed, 3187 insertions, 0 deletions
diff --git a/client/assembler.c b/client/assembler.c
new file mode 100644
index 0000000..9e36204
--- /dev/null
+++ b/client/assembler.c
@@ -0,0 +1,125 @@
+/*
+ * $Id: assembler.c,v 1.1 2004/12/30 22:44:04 lordjaxom Exp $
+ */
+
+#include "client/assembler.h"
+#include "common.h"
+
+#include "tools/socket.h"
+#include "tools/select.h"
+
+#include <vdr/tools.h>
+#include <vdr/device.h>
+#include <vdr/ringbuffer.h>
+
+#include <unistd.h>
+
+cStreamdevAssembler::cStreamdevAssembler(cTBSocket *Socket)
+#if VDRVERSNUM >= 10300
+ :cThread("Streamdev: UDP-TS Assembler")
+#endif
+{
+ m_Socket = Socket;
+ if (pipe(m_Pipe) != 0) {
+ esyslog("streamdev-client: Couldn't open assembler pipe: %m");
+ return;
+ }
+ fcntl(m_Pipe[0], F_SETFL, O_NONBLOCK);
+ fcntl(m_Pipe[1], F_SETFL, O_NONBLOCK);
+ m_Mutex.Lock();
+ Start();
+}
+
+cStreamdevAssembler::~cStreamdevAssembler() {
+ if (m_Active) {
+ m_Active = false;
+ WakeUp();
+ Cancel(3);
+ }
+ close(m_Pipe[0]);
+ close(m_Pipe[1]);
+}
+
+void cStreamdevAssembler::Action(void) {
+ cTBSelect sel;
+ uchar buffer[2048];
+ bool fillup = true;
+
+ const int rbsize = TS_SIZE * 5600;
+ const int rbmargin = TS_SIZE * 2;
+ const int rbminfill = rbmargin * 50;
+ cRingBufferLinear ringbuf(rbsize, rbmargin, true);
+
+#if VDRVERSNUM < 10300
+ isyslog("streamdev-client: UDP-TS Assembler thread started (pid=%d)",
+ getpid());
+#endif
+
+ m_Mutex.Lock();
+
+ m_Active = true;
+ while (m_Active) {
+ sel.Clear();
+
+ if (ringbuf.Available() < rbsize * 80 / 100)
+ sel.Add(*m_Socket, false);
+ if (ringbuf.Available() > rbminfill) {
+ if (fillup) {
+ Dprintf("giving signal\n");
+ m_WaitFill.Broadcast();
+ m_Mutex.Unlock();
+ fillup = false;
+ }
+ sel.Add(m_Pipe[1], true);
+ }
+
+ if (sel.Select(1500) < 0) {
+ if (!m_Active) // Exit was requested
+ break;
+ esyslog("streamdev-client: Fatal error: %m");
+ Dprintf("streamdev-client: select failed (%m)\n");
+ m_Active = false;
+ break;
+ }
+
+ if (sel.CanRead(*m_Socket)) {
+ int b;
+ if ((b = m_Socket->Read(buffer, sizeof(buffer))) < 0) {
+ esyslog("streamdev-client: Couldn't read from server: %m");
+ Dprintf("streamdev-client: read failed (%m)\n");
+ m_Active = false;
+ break;
+ }
+ if (b == 0)
+ m_Active = false;
+ else
+ ringbuf.Put(buffer, b);
+ }
+
+ if (sel.CanWrite(m_Pipe[1])) {
+ int recvd;
+ const uchar *block = ringbuf.Get(recvd);
+ if (block && recvd > 0) {
+ int result;
+ if (recvd > ringbuf.Available() - rbminfill)
+ recvd = ringbuf.Available() - rbminfill;
+ if ((result = write(m_Pipe[1], block, recvd)) == -1) {
+ esyslog("streamdev-client: Couldn't write to VDR: %m"); // TODO
+ Dprintf("streamdev-client: write failed (%m)\n");
+ m_Active = false;
+ break;
+ }
+ ringbuf.Del(result);
+ }
+ }
+ }
+
+#if VDRVERSNUM < 10300
+ isyslog("streamdev-client: UDP-TS Assembler thread stopped", getpid());
+#endif
+}
+
+void cStreamdevAssembler::WaitForFill(void) {
+ m_WaitFill.Wait(m_Mutex);
+ m_Mutex.Unlock();
+}
diff --git a/client/assembler.h b/client/assembler.h
new file mode 100644
index 0000000..a4b0747
--- /dev/null
+++ b/client/assembler.h
@@ -0,0 +1,32 @@
+/*
+ * $Id: assembler.h,v 1.1 2004/12/30 22:44:04 lordjaxom Exp $
+ */
+
+#ifndef VDR_STREAMDEV_ASSEMBLER_H
+#define VDR_STREAMDEV_ASSEMBLER_H
+
+#include <vdr/config.h>
+#include <vdr/thread.h>
+
+class cTBSocket;
+
+class cStreamdevAssembler: public cThread {
+private:
+ cTBSocket *m_Socket;
+ cMutex m_Mutex;
+ cCondVar m_WaitFill;
+ int m_Pipe[2];
+ bool m_Active;
+protected:
+ virtual void Action(void);
+
+public:
+ cStreamdevAssembler(cTBSocket *Socket);
+ virtual ~cStreamdevAssembler();
+
+ int ReadPipe(void) const { return m_Pipe[0]; }
+ void WaitForFill(void);
+};
+
+#endif // VDR_STREAMDEV_ASSEMBLER_H
+
diff --git a/client/device.c b/client/device.c
new file mode 100644
index 0000000..50510e2
--- /dev/null
+++ b/client/device.c
@@ -0,0 +1,185 @@
+/*
+ * $Id: device.c,v 1.1 2004/12/30 22:44:00 lordjaxom Exp $
+ */
+
+#include "client/device.h"
+#include "client/setup.h"
+#include "client/assembler.h"
+#include "client/filter.h"
+
+#include "tools/select.h"
+#include "tools/string.h"
+
+#include <vdr/channels.h>
+#include <vdr/ringbuffer.h>
+#include <vdr/eit.h>
+#include <vdr/timers.h>
+
+#include <time.h>
+#include <iostream>
+
+using namespace std;
+
+#define VIDEOBUFSIZE MEGABYTE(3)
+
+cStreamdevDevice *cStreamdevDevice::m_Device = NULL;
+
+cStreamdevDevice::cStreamdevDevice(void) {
+ m_Channel = NULL;
+ m_TSBuffer = NULL;
+ m_Assembler = NULL;
+
+#if VDRVERSNUM < 10300
+# if defined(HAVE_AUTOPID)
+ (void)new cSIProcessor(new cSectionsScanner(""));
+# else
+ (void)new cSIProcessor("");
+# endif
+ cSIProcessor::Read();
+#else
+ m_Filters = new cStreamdevFilters;
+ StartSectionHandler();
+ cSchedules::Read();
+#endif
+
+ m_Device = this;
+
+ if (StreamdevClientSetup.SyncEPG)
+ ClientSocket.SynchronizeEPG();
+}
+
+cStreamdevDevice::~cStreamdevDevice() {
+ Dprintf("Device gets destructed\n");
+ m_Device = NULL;
+ delete m_TSBuffer;
+ delete m_Assembler;
+#if VDRVERSNUM >= 10300
+ delete m_Filters;
+#endif
+}
+
+bool cStreamdevDevice::ProvidesSource(int Source) const {
+ Dprintf("ProvidesSource, Source=%d\n", Source);
+ return false;
+}
+
+bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority,
+ bool *NeedsDetachReceivers) const {
+ bool res = false;
+ bool prio = Priority < 0 || Priority > this->Priority();
+ bool ndr = false;
+ Dprintf("ProvidesChannel, Channel=%s, Prio=%d\n", Channel->Name(), Priority);
+
+ if (ClientSocket.DataSocket(siLive) != NULL
+ && TRANSPONDER(Channel, m_Channel))
+ res = true;
+ else {
+ res = prio && ClientSocket.ProvidesChannel(Channel, Priority);
+ ndr = true;
+ }
+
+ if (NeedsDetachReceivers)
+ *NeedsDetachReceivers = ndr;
+ Dprintf("prov res = %d, ndr = %d\n", res, ndr);
+ return res;
+}
+
+bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel,
+ bool LiveView) {
+ Dprintf("SetChannelDevice Channel: %s, LiveView: %s\n", Channel->Name(),
+ LiveView ? "true" : "false");
+
+ if (LiveView)
+ return false;
+
+ if (ClientSocket.DataSocket(siLive) != NULL
+ && TRANSPONDER(Channel, m_Channel))
+ return true;
+
+ m_Channel = Channel;
+ return ClientSocket.SetChannelDevice(m_Channel);
+}
+
+bool cStreamdevDevice::SetPid(cPidHandle *Handle, int Type, bool On) {
+ Dprintf("SetPid, Pid=%d, Type=%d, On=%d, used=%d\n", Handle->pid, Type, On,
+ Handle->used);
+ if (Handle->pid && (On || !Handle->used))
+ return ClientSocket.SetPid(Handle->pid, On);
+ return true;
+}
+
+bool cStreamdevDevice::OpenDvr(void) {
+ Dprintf("OpenDvr\n");
+ if (ClientSocket.CreateDataConnection(siLive)) {
+ m_Assembler = new cStreamdevAssembler(ClientSocket.DataSocket(siLive));
+ m_TSBuffer = new cTSBuffer(m_Assembler->ReadPipe(), MEGABYTE(2),
+ CardIndex() + 1);
+ Dprintf("waiting\n");
+ m_Assembler->WaitForFill();
+ Dprintf("resuming\n");
+ return true;
+ }
+ return false;
+}
+
+void cStreamdevDevice::CloseDvr(void) {
+ Dprintf("CloseDvr\n");
+
+ ClientSocket.CloseDvr();
+ DELETENULL(m_TSBuffer);
+ DELETENULL(m_Assembler);
+}
+
+bool cStreamdevDevice::GetTSPacket(uchar *&Data) {
+ if (m_TSBuffer) {
+ int r;
+ while ((r = m_TSBuffer->Read()) >= 0) {
+ Data = m_TSBuffer->Get();
+#if VDRVERSNUM >= 10300
+ if (Data != NULL) {
+ u_short pid = (((u_char)Data[1] & PID_MASK_HI) << 8) | Data[2];
+ u_char tid = Data[3];
+ if (m_Filters->Matches(pid, tid)) {
+ m_Filters->Put(Data);
+ continue;
+ }
+ }
+#endif
+ return true;
+ }
+ if (FATALERRNO) {
+ LOG_ERROR;
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+#if VDRVERSNUM >= 10300
+int cStreamdevDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask) {
+ Dprintf("OpenFilter\n");
+ if (StreamdevClientSetup.StreamFilters
+ && ClientSocket.SetFilter(Pid, Tid, Mask, true)) {
+ return m_Filters->OpenFilter(Pid, Tid, Mask);
+ } else
+ return -1;
+}
+#endif
+
+bool cStreamdevDevice::Init(void) {
+ if (m_Device == NULL && StreamdevClientSetup.StartClient)
+ new cStreamdevDevice;
+ return true;
+}
+
+bool cStreamdevDevice::ReInit(void) {
+ ClientSocket.Quit();
+ ClientSocket.Reset();
+ if (m_Device != NULL) {
+ DELETENULL(m_Device->m_TSBuffer);
+ DELETENULL(m_Device->m_Assembler);
+ }
+ return StreamdevClientSetup.StartClient ? Init() : true;
+}
+
diff --git a/client/device.h b/client/device.h
new file mode 100644
index 0000000..b35dabe
--- /dev/null
+++ b/client/device.h
@@ -0,0 +1,58 @@
+/*
+ * $Id: device.h,v 1.1 2004/12/30 22:44:00 lordjaxom Exp $
+ */
+
+#ifndef VDR_STREAMDEV_DEVICE_H
+#define VDR_STREAMDEV_DEVICE_H
+
+#include <vdr/device.h>
+
+#include "client/socket.h"
+#include "client/assembler.h"
+#include "client/filter.h"
+
+class cTBString;
+
+#define CMD_LOCK_OBJ(x) cMutexLock CmdLock((cMutex*)&(x)->m_Mutex)
+
+class cStreamdevDevice: public cDevice {
+ friend class cRemoteRecordings;
+
+private:
+ const cChannel *m_Channel;
+ cTSBuffer *m_TSBuffer;
+ cStreamdevAssembler *m_Assembler;
+#if VDRVERSNUM >= 10307
+ cStreamdevFilters *m_Filters;
+#endif
+
+ static cStreamdevDevice *m_Device;
+
+protected:
+ virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
+ virtual bool HasLock(void) { return m_TSBuffer != NULL; }
+
+ virtual bool SetPid(cPidHandle *Handle, int Type, bool On);
+ virtual bool OpenDvr(void);
+ virtual void CloseDvr(void);
+ virtual bool GetTSPacket(uchar *&Data);
+
+#if VDRVERSNUM >= 10300
+ virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
+#endif
+
+public:
+ cStreamdevDevice(void);
+ virtual ~cStreamdevDevice();
+
+ virtual bool ProvidesSource(int Source) const;
+ virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1,
+ bool *NeedsDetachReceivers = NULL) const;
+
+ static bool Init(void);
+ static bool ReInit(void);
+
+ static cStreamdevDevice *GetDevice(void) { return m_Device; }
+};
+
+#endif // VDR_STREAMDEV_DEVICE_H
diff --git a/client/filter.c b/client/filter.c
new file mode 100644
index 0000000..dad86f3
--- /dev/null
+++ b/client/filter.c
@@ -0,0 +1,141 @@
+/*
+ * $Id: filter.c,v 1.1 2004/12/30 22:44:04 lordjaxom Exp $
+ */
+
+#include "client/filter.h"
+#include "client/socket.h"
+#include "tools/select.h"
+#include "common.h"
+
+#include <vdr/ringbuffer.h>
+
+#if VDRVERSNUM >= 10300
+
+cStreamdevFilter::cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask) {
+ m_Used = 0;
+ m_Pid = Pid;
+ m_Tid = Tid;
+ m_Mask = Mask;
+
+ if (pipe(m_Pipe) != 0 || fcntl(m_Pipe[0], F_SETFL, O_NONBLOCK) != 0) {
+ esyslog("streamev-client: coudln't open section filter pipe: %m");
+ m_Pipe[0] = m_Pipe[1] = -1;
+ }
+}
+
+cStreamdevFilter::~cStreamdevFilter() {
+ Dprintf("~cStreamdevFilter %p\n", this);
+ if (m_Pipe[0] >= 0)
+ close(m_Pipe[0]);
+ if (m_Pipe[1] >= 0)
+ close(m_Pipe[1]);
+}
+
+bool cStreamdevFilter::PutSection(const uchar *Data, int Length) {
+ if (m_Used + Length >= (int)sizeof(m_Buffer)) {
+ esyslog("ERROR: Streamdev: Section handler buffer overflow (%d bytes lost)",
+ Length);
+ m_Used = 0;
+ return true;
+ }
+ memcpy(m_Buffer + m_Used, Data, Length);
+ m_Used += Length;
+
+ if (m_Used > 3) {
+ int length = (((m_Buffer[1] & 0x0F) << 8) | m_Buffer[2]) + 3;
+ if (m_Used == length) {
+ if (write(m_Pipe[1], m_Buffer, length) < 0)
+ return false;
+ m_Used = 0;
+ }
+ }
+ return true;
+}
+
+cStreamdevFilters::cStreamdevFilters(void):
+ cThread("streamdev-client: sections assembler") {
+ m_Active = false;
+ m_RingBuffer = new cRingBufferLinear(MEGABYTE(1), TS_SIZE * 2, true);
+ Start();
+}
+
+cStreamdevFilters::~cStreamdevFilters() {
+ if (m_Active) {
+ m_Active = false;
+ Cancel(3);
+ }
+ delete m_RingBuffer;
+}
+
+int cStreamdevFilters::OpenFilter(u_short Pid, u_char Tid, u_char Mask) {
+ cStreamdevFilter *f = new cStreamdevFilter(Pid, Tid, Mask);
+ Add(f);
+ return f->ReadPipe();
+}
+
+cStreamdevFilter *cStreamdevFilters::Matches(u_short Pid, u_char Tid) {
+ for (cStreamdevFilter *f = First(); f; f = Next(f)) {
+ if (f->Matches(Pid, Tid))
+ return f;
+ }
+ return NULL;
+}
+
+void cStreamdevFilters::Put(const uchar *Data) {
+ static time_t firsterr = 0;
+ static int errcnt = 0;
+ static bool showerr = true;
+
+ int p = m_RingBuffer->Put(Data, TS_SIZE);
+ if (p != TS_SIZE) {
+ ++errcnt;
+ if (showerr) {
+ if (firsterr == 0)
+ firsterr = time_ms();
+ else if (firsterr + BUFOVERTIME > time_ms() && errcnt > BUFOVERCOUNT) {
+ esyslog("ERROR: too many buffer overflows, logging stopped");
+ showerr = false;
+ firsterr = time_ms();
+ }
+ } else if (firsterr + BUFOVERTIME < time_ms()) {
+ showerr = true;
+ firsterr = 0;
+ errcnt = 0;
+ }
+
+ if (showerr)
+ esyslog("ERROR: ring buffer overflow (%d bytes dropped)", TS_SIZE - p);
+ else
+ firsterr = time_ms();
+ }
+}
+
+void cStreamdevFilters::Action(void) {
+ m_Active = true;
+ while (m_Active) {
+ int recvd;
+ const uchar *block = m_RingBuffer->Get(recvd);
+
+ if (block && recvd > 0) {
+ cStreamdevFilter *f;
+ u_short pid = (((u_short)block[1] & PID_MASK_HI) << 8) | block[2];
+ u_char tid = block[3];
+
+ if ((f = Matches(pid, tid)) != NULL) {
+ int len = block[4];
+ if (!f->PutSection(block + 5, len)) {
+ if (errno != EPIPE) {
+ esyslog("streamdev-client: couldn't send section packet: %m");
+ Dprintf("FATAL ERROR: %m\n");
+ }
+ ClientSocket.SetFilter(f->Pid(), f->Tid(), f->Mask(), false);
+ Del(f);
+ }
+ }
+ m_RingBuffer->Del(TS_SIZE);
+ } else
+ usleep(1);
+ }
+}
+
+#endif // VDRVERSNUM >= 10300
diff --git a/client/filter.h b/client/filter.h
new file mode 100644
index 0000000..679b2b5
--- /dev/null
+++ b/client/filter.h
@@ -0,0 +1,64 @@
+/*
+ * $Id: filter.h,v 1.1 2004/12/30 22:44:04 lordjaxom Exp $
+ */
+
+#ifndef VDR_STREAMDEV_FILTER_H
+#define VDR_STREAMDEV_FILTER_H
+
+#include <vdr/config.h>
+
+# if VDRVERSNUM >= 10300
+
+#include <vdr/tools.h>
+#include <vdr/thread.h>
+
+class cRingBufferFrame;
+class cRingBufferLinear;
+
+class cStreamdevFilter: public cListObject {
+private:
+ uchar m_Buffer[4096];
+ int m_Used;
+ int m_Pipe[2];
+ u_short m_Pid;
+ u_char m_Tid;
+ u_char m_Mask;
+ cRingBufferFrame *m_RingBuffer;
+
+public:
+ cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask);
+ virtual ~cStreamdevFilter();
+
+ bool Matches(u_short Pid, u_char Tid);
+ bool PutSection(const uchar *Data, int Length);
+ int ReadPipe(void) const { return m_Pipe[0]; }
+
+ u_short Pid(void) const { return m_Pid; }
+ u_char Tid(void) const { return m_Tid; }
+ u_char Mask(void) const { return m_Mask; }
+
+};
+
+inline bool cStreamdevFilter::Matches(u_short Pid, u_char Tid) {
+ return m_Pid == Pid && m_Tid == (Tid & m_Mask);
+}
+
+class cStreamdevFilters: public cList<cStreamdevFilter>, public cThread {
+private:
+ bool m_Active;
+ cRingBufferLinear *m_RingBuffer;
+
+protected:
+ virtual void Action(void);
+
+public:
+ cStreamdevFilters(void);
+ virtual ~cStreamdevFilters();
+
+ int OpenFilter(u_short Pid, u_char Tid, u_char Mask);
+ cStreamdevFilter *Matches(u_short Pid, u_char Tid);
+ void Put(const uchar *Data);
+};
+
+# endif // VDRVERSNUM >= 10300
+#endif // VDR_STREAMDEV_FILTER_H
diff --git a/client/menu.c b/client/menu.c
new file mode 100644
index 0000000..4499268
--- /dev/null
+++ b/client/menu.c
@@ -0,0 +1,1047 @@
+/*
+ * $Id: menu.c,v 1.1 2004/12/30 22:44:01 lordjaxom Exp $
+ */
+
+#include <vdr/menuitems.h>
+#include <vdr/interface.h>
+
+#include "client/menu.h"
+#include "client/socket.h"
+#include "i18n.h"
+
+#define CHNUMWIDTH (numdigits(Channels.MaxNumber()) + 1)
+
+// --- cMenuText -------------------------------------------------------------
+
+class cMenuText : public cOsdMenu {
+public:
+ cMenuText(const char *Title, const char *Text, eDvbFont Font = fontOsd);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+// --- cStreamdevMenu --------------------------------------------------------
+
+cStreamdevMenu::cStreamdevMenu(void):
+ cOsdMenu(tr("Streaming Control")) {
+ SetHasHotkeys();
+ Add(new cOsdItem(hk(tr("Remote Schedule")), (eOSState)subSchedule));
+ Add(new cOsdItem(hk(tr("Remote Timers")), (eOSState)subTimers));
+ Add(new cOsdItem(hk(tr("Remote Recordings")), (eOSState)subRecordings));
+ Add(new cOsdItem(hk(tr("Suspend Server")), (eOSState)subSuspend));
+ Add(new cOsdItem(hk(tr("Synchronize EPG")), (eOSState)subSyncEPG));
+}
+
+cStreamdevMenu::~cStreamdevMenu() {
+}
+
+eOSState cStreamdevMenu::ProcessKey(eKeys Key) {
+ eOSState state = cOsdMenu::ProcessKey(Key);
+ switch (state) {
+ case subSchedule: return AddSubMenu(new cStreamdevMenuSchedule);
+ case subTimers: return AddSubMenu(new cStreamdevMenuTimers);
+ case subRecordings: return AddSubMenu(new cStreamdevMenuRecordings);
+ case subSuspend: SuspendServer(); return osEnd;
+ case subSyncEPG: ClientSocket.SynchronizeEPG(); return osEnd;
+ default: return state;
+ }
+}
+
+void cStreamdevMenu::SuspendServer(void) {
+ cTBString buffer;
+
+ if (ClientSocket.SuspendServer())
+ INFO(tr("Server is suspended"));
+ else
+ ERROR(tr("Couldn't suspend Server!"));
+}
+
+#if VDRVERSNUM < 10307
+// --- cMenuEditChanItem -----------------------------------------------------
+
+class cMenuEditChanItem : public cMenuEditIntItem {
+protected:
+ virtual void Set(void);
+public:
+ cMenuEditChanItem(const char *Name, int *Value);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+// --- cMenuEditDateItem -----------------------------------------------------
+
+class cMenuEditDateItem : public cMenuEditItem {
+protected:
+ time_t *value;
+ virtual void Set(void);
+public:
+ cMenuEditDateItem(const char *Name, time_t *Value);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+// --- cMenuEditDayItem ------------------------------------------------------
+
+class cMenuEditDayItem : public cMenuEditIntItem {
+protected:
+ static int days[];
+ int d;
+ virtual void Set(void);
+public:
+ cMenuEditDayItem(const char *Name, int *Value);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+// --- cMenuEditTimeItem -----------------------------------------------------
+
+class cMenuEditTimeItem : public cMenuEditItem {
+protected:
+ int *value;
+ int hh, mm;
+ int pos;
+ virtual void Set(void);
+public:
+ cMenuEditTimeItem(const char *Name, int *Value);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+#endif // VDRVERSNUM < 10307
+
+// --- cStreamdevMenuEditTimer -----------------------------------------------
+
+class cStreamdevMenuEditTimer : public cOsdMenu {
+private:
+ int m_Channel;
+ bool m_AddIfConfirmed;
+ cRemoteTimer *m_Timer;
+ cRemoteTimer m_Data;
+ cMenuEditDateItem *m_FirstDay;
+
+protected:
+ void SetFirstDayItem(void);
+
+public:
+ cStreamdevMenuEditTimer(cRemoteTimer *Timer, bool New = false);
+ virtual ~cStreamdevMenuEditTimer();
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+cStreamdevMenuEditTimer::cStreamdevMenuEditTimer(cRemoteTimer *Timer, bool New):
+ cOsdMenu(tr("Edit remote timer"), 12) {
+ m_FirstDay = NULL;
+ m_Timer = Timer;
+ m_AddIfConfirmed = New;
+
+ if (m_Timer) {
+ m_Data = *m_Timer;
+ if (New)
+ m_Data.m_Active = 1;
+ m_Channel = m_Data.Channel()->Number();
+#if VDRVERSNUM < 10300
+ Add(new cMenuEditBoolItem(tr("Active"), &m_Data.m_Active));
+#else
+ Add(new cMenuEditBitItem( tr("Active"), &m_Data.m_Active, tfActive));
+#endif
+ Add(new cMenuEditChanItem(tr("Channel"), &m_Channel));
+ Add(new cMenuEditDayItem( tr("Day"), &m_Data.m_Day));
+ Add(new cMenuEditTimeItem(tr("Start"), &m_Data.m_Start));
+ Add(new cMenuEditTimeItem(tr("Stop"), &m_Data.m_Stop));
+#if VDRVERSNUM >= 10300
+ Add(new cMenuEditBitItem( tr("VPS"), &m_Data.m_Active, tfVps));
+#endif
+ Add(new cMenuEditIntItem( tr("Priority"), &m_Data.m_Priority, 0,
+ MAXPRIORITY));
+ Add(new cMenuEditIntItem( tr("Lifetime"), &m_Data.m_Lifetime, 0,
+ MAXLIFETIME));
+ Add(new cMenuEditStrItem( tr("File"), m_Data.m_File,
+ sizeof(m_Data.m_File), tr(FileNameChars)));
+ SetFirstDayItem();
+ }
+}
+
+cStreamdevMenuEditTimer::~cStreamdevMenuEditTimer() {
+ if (m_Timer && m_AddIfConfirmed) {
+ Dprintf("SOMETHING GETS DELETED\n");
+ delete m_Timer; // apparently it wasn't confirmed
+ }
+}
+
+void cStreamdevMenuEditTimer::SetFirstDayItem(void) {
+ if (!m_FirstDay && !m_Data.IsSingleEvent()) {
+ Add(m_FirstDay = new cMenuEditDateItem(tr("First day"),&m_Data.m_FirstDay));
+ Display();
+ } else if (m_FirstDay && m_Data.IsSingleEvent()) {
+ Del(m_FirstDay->Index());
+ m_FirstDay = NULL;
+ m_Data.m_FirstDay = 0;
+ Display();
+ }
+}
+
+eOSState cStreamdevMenuEditTimer::ProcessKey(eKeys Key) {
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kOk:
+ {
+ cChannel *ch = Channels.GetByNumber(m_Channel);
+ if (ch)
+ m_Data.m_Channel = ch;
+ else {
+ ERROR(tr("*** Invalid Channel ***"));
+ break;
+ }
+ if (!*m_Data.m_File)
+ strcpy(m_Data.m_File, m_Data.Channel()->Name());
+ if (m_Timer) {
+ bool success = true;
+ if (m_Data != *m_Timer) {
+ // Timer has changed
+ if ((success = ClientSocket.SaveTimer(m_Timer, m_Data))) {
+ *m_Timer = m_Data;
+ if (m_Timer->m_Active)
+ m_Timer->m_Active = 1;
+ // allows external programs to mark active timers with
+ // values > 1 and recognize if the user has modified them
+ }
+ }
+ if (success) {
+ if (m_AddIfConfirmed)
+ RemoteTimers.Add(m_Timer);
+ isyslog("timer %d %s (%s)", m_Timer->Index() + 1,
+ m_AddIfConfirmed ? "added" : "modified",
+ m_Timer->m_Active ? "active" : "inactive");
+ m_AddIfConfirmed = false;
+ }
+ }
+ }
+ return osBack;
+
+ case kRed:
+ case kGreen:
+ case kYellow:
+ case kBlue: return osContinue;
+ default: break;
+ }
+ }
+ if (Key != kNone)
+ SetFirstDayItem();
+ return state;
+}
+
+// --- cMenuWhatsOnItem ------------------------------------------------------
+
+#if VDRVERSNUM < 10300
+class cMenuWhatsOnItem : public cOsdItem {
+public:
+ const cEventInfo *eventInfo;
+# ifdef HAVE_BEAUTYPATCH
+ cMenuWhatsOnItem(const cEventInfo *EventInfo, bool ShowProgressBar);
+ ~cMenuWhatsOnItem();
+ virtual void Display(int Offset= -1, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBackground);
+protected:
+ cBitmap *progressBar;
+ bool showProgressBar;
+ float percent;
+private:
+ void DrawProgressBar(eDvbColor FgColor, eDvbColor BgColor);
+# else
+ cMenuWhatsOnItem(const cEventInfo *EventInfo);
+# endif
+};
+#else
+class cMenuWhatsOnItem : public cOsdItem {
+public:
+ const cEvent *event;
+ const cChannel *channel;
+ cMenuWhatsOnItem(const cEvent *Event, cChannel *Channel);
+};
+#endif
+
+// --- cMenuEvent ------------------------------------------------------------
+
+#if VDRVERSNUM < 10300
+class cMenuEvent : public cOsdMenu {
+private:
+ const cEventInfo *eventInfo;
+public:
+ cMenuEvent(const cEventInfo *EventInfo, bool CanSwitch = false);
+ cMenuEvent(bool Now);
+ virtual eOSState ProcessKey(eKeys Key);
+};
+#elif VDRVERSNUM < 10307
+class cMenuEvent : public cOsdMenu {
+private:
+ const cEvent *event;
+public:
+ cMenuEvent(const cEvent *Event, bool CanSwitch = false);
+ cMenuEvent(bool Now);
+ virtual eOSState ProcessKey(eKeys Key);
+};
+#else
+class cMenuEvent : public cOsdMenu {
+private:
+ const cEvent *event;
+public:
+ cMenuEvent(const cEvent *Event, bool CanSwitch = false);
+ virtual void Display(void);
+ virtual eOSState ProcessKey(eKeys Key);
+};
+#endif
+
+// --- cStreamdevMenuWhatsOn -------------------------------------------------
+
+int cStreamdevMenuWhatsOn::m_CurrentChannel = 0;
+#if VDRVERSNUM < 10300
+const cEventInfo *cStreamdevMenuWhatsOn::m_ScheduleEventInfo = NULL;
+#else
+const cEvent *cStreamdevMenuWhatsOn::m_ScheduleEventInfo = NULL;
+#endif
+
+#if VDRVERSNUM < 10300
+static int CompareEventChannel(const void *p1, const void *p2) {
+ return (int)((*(const cEventInfo**)p1)->GetChannelNumber()
+ - (*(const cEventInfo**)p2)->GetChannelNumber());
+}
+#endif
+
+cStreamdevMenuWhatsOn::cStreamdevMenuWhatsOn(const cSchedules *Schedules,
+ bool Now, int CurrentChannel):
+ cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH,
+ 7, 6) {
+#if VDRVERSNUM < 10300
+ const cSchedule *Schedule = Schedules->First();
+ const cEventInfo **pArray = NULL;
+ int num = 0;
+
+ while (Schedule) {
+ pArray=(const cEventInfo**)realloc(pArray, (num + 1) * sizeof(cEventInfo*));
+ pArray[num] = Now ? Schedule->GetPresentEvent()
+ : Schedule->GetFollowingEvent();
+ if (pArray[num]) {
+ cChannel *channel
+ = Channels.GetByChannelID(pArray[num]->GetChannelID(), true);
+ if (channel)
+ pArray[num++]->SetChannelNumber(channel->Number());
+ }
+ Schedule = Schedules->Next(Schedule);
+ }
+
+ qsort(pArray, num, sizeof(cEventInfo*), CompareEventChannel);
+ for (int a = 0; a < num; ++a) {
+ int channelnr = pArray[a]->GetChannelNumber();
+# ifdef HAVE_BEAUTYPATCH
+ Add(new cMenuWhatsOnItem(pArray[a],Now), channelnr == CurrentChannel);
+# else
+ Add(new cMenuWhatsOnItem(pArray[a]), channelnr == CurrentChannel);
+# endif
+ }
+
+ free(pArray);
+#else
+ for (cChannel *Channel = Channels.First(); Channel;
+ Channel = Channels.Next(Channel)) {
+ if (!Channel->GroupSep()) {
+ const cSchedule *Schedule
+ = Schedules->GetSchedule(Channel->GetChannelID());
+ if (Schedule) {
+ const cEvent *Event = Now ? Schedule->GetPresentEvent()
+ : Schedule->GetFollowingEvent();
+ if (Event)
+ Add(new cMenuWhatsOnItem(Event, Channel),
+ Channel->Number() == CurrentChannel);
+ }
+ }
+ }
+#endif
+ m_CurrentChannel = CurrentChannel;
+ SetHelp(Count() ? tr("Record") : NULL, Now ? tr("Next") : tr("Now"),
+ tr("Schedule"), tr("Switch"));
+}
+
+#if VDRVERSNUM < 10300
+const cEventInfo *cStreamdevMenuWhatsOn::ScheduleEventInfo(void) {
+ const cEventInfo *ei = m_ScheduleEventInfo;
+ m_ScheduleEventInfo = NULL;
+ return ei;
+}
+#else
+const cEvent *cStreamdevMenuWhatsOn::ScheduleEventInfo(void) {
+ const cEvent *ei = m_ScheduleEventInfo;
+ m_ScheduleEventInfo = NULL;
+ return ei;
+}
+#endif
+
+eOSState cStreamdevMenuWhatsOn::Switch(void) {
+ cMenuWhatsOnItem *item = (cMenuWhatsOnItem*)Get(Current());
+ if (item) {
+ cChannel *channel
+#if VDRVERSNUM < 10300
+ = Channels.GetByChannelID(item->eventInfo->GetChannelID(), true);
+#else
+ = Channels.GetByChannelID(item->event->ChannelID(), true);
+#endif
+ if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true))
+ return osEnd;
+ }
+ ERROR(tr("Can't switch channel!"));
+ return osContinue;
+}
+
+eOSState cStreamdevMenuWhatsOn::Record(void) {
+ cMenuWhatsOnItem *item = (cMenuWhatsOnItem*)Get(Current());
+ if (item) {
+ cRemoteTimer *timer
+#if VDRVERSNUM < 10300
+ = new cRemoteTimer(item->eventInfo);
+#else
+ = new cRemoteTimer(item->event);
+#endif
+ return AddSubMenu(new cStreamdevMenuEditTimer(timer));
+ // Load remote timers and see if timer exists before editing
+ }
+ return osContinue;
+}
+
+eOSState cStreamdevMenuWhatsOn::ProcessKey(eKeys Key) {
+ eOSState state = cOsdMenu::ProcessKey(Key);
+ if (state == osUnknown) {
+ switch (Key) {
+ case kRecord:
+ case kRed:
+ return Record();
+
+ case kYellow:
+ state = osBack;
+ case kGreen:
+ {
+ cMenuWhatsOnItem *mi = (cMenuWhatsOnItem*)Get(Current());
+ if (mi) {
+#if VDRVERSNUM < 10300
+ m_ScheduleEventInfo = mi->eventInfo;
+ m_CurrentChannel = mi->eventInfo->GetChannelNumber();
+#else
+ m_ScheduleEventInfo = mi->event;
+ m_CurrentChannel = mi->channel->Number();
+#endif
+ }
+ }
+ break;
+
+ case kBlue:
+ return Switch();
+
+ case kOk:
+ if (Count())
+#if VDRVERSNUM < 10300
+ return AddSubMenu(new cMenuEvent(
+ ((cMenuWhatsOnItem*)Get(Current()))->eventInfo, true));
+#else
+ return AddSubMenu(new cMenuEvent(
+ ((cMenuWhatsOnItem*)Get(Current()))->event, true));
+#endif
+ break;
+
+ default:
+ break;
+ }
+ }
+ return state;
+}
+
+// --- cMenuScheduleItem -----------------------------------------------------
+
+#if VDRVERSNUM < 10300
+class cMenuScheduleItem : public cOsdItem {
+public:
+ const cEventInfo *eventInfo;
+ cMenuScheduleItem(const cEventInfo *EventInfo);
+};
+#else
+class cMenuScheduleItem : public cOsdItem {
+public:
+ const cEvent *event;
+ cMenuScheduleItem(const cEvent *Event);
+};
+#endif
+
+// --- cStreamdevMenuSchedule ------------------------------------------------
+
+cStreamdevMenuSchedule::cStreamdevMenuSchedule(void):
+#if VDRVERSNUM < 10300
+ cOsdMenu("", 6, 6)
+#else
+ cOsdMenu("", 7, 6, 4)
+#endif
+{
+ m_Now = false;
+ m_Next = false;
+ m_OtherChannel = -1;
+ m_Schedules = NULL;
+
+ cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
+ if (channel) {
+#if VDRVERSNUM < 10300
+ m_Schedules = cSIProcessor::Schedules(m_Lock);
+#else
+ m_Schedules = cSchedules::Schedules(m_Lock);
+#endif
+ PrepareSchedule(channel);
+ SetHelp(Count() ? tr("Record") : NULL, tr("Now"), tr("Next"));
+ }
+}
+
+cStreamdevMenuSchedule::~cStreamdevMenuSchedule() {
+}
+
+#if VDRVERSNUM < 10307
+static int CompareEventTime(const void *p1, const void *p2) {
+#if VDRVERSNUM < 10300
+ return (int)((*(cEventInfo **)p1)->GetTime()
+ - (*(cEventInfo **)p2)->GetTime());
+#else
+ return (int)((*(cEvent**)p1)->StartTime()
+ - (*(cEvent**)p2)->StartTime());
+#endif
+}
+#endif
+
+void cStreamdevMenuSchedule::PrepareSchedule(cChannel *Channel) {
+#if VDRVERSNUM < 10300
+ cTBString buffer;
+ Clear();
+ buffer.Format(tr("Schedule - %s"), Channel->Name());
+ SetTitle(buffer);
+ if (m_Schedules) {
+ const cSchedule *Schedule=m_Schedules->GetSchedule(Channel->GetChannelID());
+ if (Schedule) {
+ int num = Schedule->NumEvents();
+ const cEventInfo **pArray = MALLOC(const cEventInfo*, num);
+ if (pArray) {
+ time_t now = time(NULL);
+ int numreal = 0;
+ for (int a = 0; a < num; ++a) {
+ const cEventInfo *EventInfo = Schedule->GetEventNumber(a);
+ if (EventInfo->GetTime() + EventInfo->GetDuration() > now)
+ pArray[numreal++] = EventInfo;
+ }
+
+ qsort(pArray, numreal, sizeof(cEventInfo*), CompareEventTime);
+ for (int a = 0; a < numreal; ++a)
+ Add(new cMenuScheduleItem(pArray[a]));
+ free(pArray);
+ }
+ }
+ }
+#else
+ Clear();
+ char *buffer = NULL;
+ asprintf(&buffer, tr("Schedule - %s"), Channel->Name());
+ SetTitle(buffer);
+ free(buffer);
+ if (m_Schedules) {
+ const cSchedule *Schedule = m_Schedules->GetSchedule(Channel->GetChannelID());
+ if (Schedule) {
+ const cEvent *PresentEvent = Schedule->GetPresentEvent(Channel->Number() == cDevice::CurrentChannel());
+ time_t now = time(NULL) - Setup.EPGLinger * 60;
+ for (const cEvent *Event = Schedule->Events()->First(); Event; Event = Schedule->Events()->Next(Event)) {
+ if (Event->EndTime() > now || Event == PresentEvent)
+ Add(new cMenuScheduleItem(Event), Event == PresentEvent);
+ }
+ }
+ }
+#endif
+}
+
+eOSState cStreamdevMenuSchedule::Switch(void) {
+ if (m_OtherChannel) {
+ if (Channels.SwitchTo(m_OtherChannel))
+ return osEnd;
+ }
+ ERROR(tr("Can't switch channel!"));
+ return osContinue;
+}
+
+eOSState cStreamdevMenuSchedule::Record(void) {
+ cMenuScheduleItem *item = (cMenuScheduleItem*)Get(Current());
+ if (item) {
+ cRemoteTimer *timer
+#if VDRVERSNUM < 10300
+ = new cRemoteTimer(item->eventInfo);
+#else
+ = new cRemoteTimer(item->event);
+#endif
+ return AddSubMenu(new cStreamdevMenuEditTimer(timer));
+ // Load remote timers and see if timer exists before editing
+ }
+ return osContinue;
+}
+
+eOSState cStreamdevMenuSchedule::ProcessKey(eKeys Key) {
+ eOSState state = cOsdMenu::ProcessKey(Key);
+ if (state == osUnknown) {
+ switch (Key) {
+ case kRecord:
+ case kRed:
+ return Record();
+
+ case kGreen:
+ if (m_Schedules) {
+ if (!m_Now && !m_Next) {
+ int channelnr = 0;
+ if (Count()) {
+ cChannel *channel
+#if VDRVERSNUM < 10300
+ = Channels.GetByChannelID(
+ ((cMenuScheduleItem*)Get(Current()))->eventInfo->GetChannelID(),
+ true);
+#else
+ = Channels.GetByChannelID(
+ ((cMenuScheduleItem*)Get(Current()))->event->ChannelID(), true);
+#endif
+ if (channel)
+ channelnr = channel->Number();
+ }
+ m_Now = true;
+ return AddSubMenu(new cStreamdevMenuWhatsOn(m_Schedules, true,
+ channelnr));
+ }
+ m_Now = !m_Now;
+ m_Next = !m_Next;
+ return AddSubMenu(new cStreamdevMenuWhatsOn(m_Schedules, m_Now,
+ cStreamdevMenuWhatsOn::CurrentChannel()));
+ }
+
+ case kYellow:
+ if (m_Schedules)
+ return AddSubMenu(new cStreamdevMenuWhatsOn(m_Schedules, false,
+ cStreamdevMenuWhatsOn::CurrentChannel()));
+ break;
+
+ case kBlue:
+ if (Count())
+ return Switch();
+ break;
+
+ case kOk:
+ if (Count())
+#if VDRVERSNUM < 10300
+ return AddSubMenu(new cMenuEvent(
+ ((cMenuScheduleItem*)Get(Current()))->eventInfo, m_OtherChannel));
+#else
+ return AddSubMenu(new cMenuEvent(
+ ((cMenuScheduleItem*)Get(Current()))->event, m_OtherChannel));
+#endif
+ break;
+
+ default:
+ break;
+ }
+ } else if (!HasSubMenu()) {
+ m_Now = false;
+ m_Next = false;
+#if VDRVERSNUM < 10300
+ const cEventInfo *ei
+#else
+ const cEvent *ei
+#endif
+ = cStreamdevMenuWhatsOn::ScheduleEventInfo();
+ if (ei) {
+ cChannel *channel
+#if VDRVERSNUM < 10300
+ = Channels.GetByChannelID(ei->GetChannelID(), true);
+#else
+ = Channels.GetByChannelID(ei->ChannelID(), true);
+#endif
+ if (channel) {
+ PrepareSchedule(channel);
+ if (channel->Number() != cDevice::CurrentChannel()) {
+ m_OtherChannel = channel->Number();
+ SetHelp(Count() ? tr("Record") : NULL, tr("Now"), tr("Next"),
+ tr("Switch"));
+ }
+ Display();
+ }
+ }
+ }
+ return state;
+}
+
+// --- cStreamdevMenuRecordingItem -------------------------------------------
+
+class cStreamdevMenuRecordingItem: public cOsdItem {
+private:
+ int m_Total;
+ int m_New;
+ char *m_FileName;
+ char *m_Name;
+
+public:
+ cStreamdevMenuRecordingItem(cRemoteRecording *Recording, int Level);
+ virtual ~cStreamdevMenuRecordingItem();
+
+ void IncrementCounter(bool New);
+ const char *Name(void) const { return m_Name; }
+ const char *FileName(void) const { return m_FileName; }
+ bool IsDirectory(void) const { return m_Name != NULL; }
+};
+
+cStreamdevMenuRecordingItem::cStreamdevMenuRecordingItem(
+ cRemoteRecording *Recording, int Level) {
+ m_FileName = strdup(Recording->Name());
+ m_Name = NULL;
+ m_Total = m_New = 0;
+ SetText(Recording->Title('\t', true, Level));
+ if (*Text() == '\t')
+ m_Name = strdup(Text() + 2);
+}
+
+cStreamdevMenuRecordingItem::~cStreamdevMenuRecordingItem() {
+}
+
+void cStreamdevMenuRecordingItem::IncrementCounter(bool New) {
+ ++m_Total;
+ if (New) ++m_New;
+ char *buffer = NULL;
+ asprintf(&buffer, "%d\t%d\t%s", m_Total, m_New, m_Name);
+ SetText(buffer, false);
+}
+
+// --- cStreamdevMenuRecordings ----------------------------------------------
+
+cRemoteRecordings cStreamdevMenuRecordings::Recordings;
+int cStreamdevMenuRecordings::HelpKeys = -1;
+
+cStreamdevMenuRecordings::cStreamdevMenuRecordings(const char *Base, int Level,
+ bool OpenSubMenus):
+ cOsdMenu(Base ? Base : tr("Remote Recordings"), 6, 6) {
+ m_Base = Base ? strdup(Base) : NULL;
+ m_Level = Setup.RecordingDirs ? Level : -1;
+
+ Display(); // this keeps the higher level menus from showing up briefly when
+ // pressing 'Back' during replay
+
+ if (!Base) {
+ STATUS(tr("Fetching recordings..."));
+ FLUSH();
+ }
+
+ if (Base || Recordings.Load()) {
+ cStreamdevMenuRecordingItem *LastItem = NULL;
+ char *LastItemText = NULL;
+ for (cRemoteRecording *r = Recordings.First(); r; r = Recordings.Next(r)) {
+ if (!Base || (strstr(r->Name(), Base) == r->Name()
+ && r->Name()[strlen(Base)] == '~')) {
+ cStreamdevMenuRecordingItem *Item = new cStreamdevMenuRecordingItem(r,
+ m_Level);
+ if (*Item->Text() && (!LastItem || strcmp(Item->Text(), LastItemText)
+ != 0)) {
+ Add(Item);
+ LastItem = Item;
+ free(LastItemText);
+ LastItemText = strdup(LastItem->Text());
+ } else
+ delete Item;
+
+ if (LastItem) {
+ if (LastItem->IsDirectory())
+ LastItem->IncrementCounter(r->IsNew());
+ }
+ }
+ }
+ free(LastItemText);
+ if (Current() < 0)
+ SetCurrent(First());
+ else if (OpenSubMenus && Open(true))
+ return;
+ }
+
+#if VDRVERSNUM >= 10307
+ STATUS(NULL);
+ FLUSH();
+#endif
+
+ SetHelpKeys();
+}
+
+cStreamdevMenuRecordings::~cStreamdevMenuRecordings() {
+ if (m_Base != NULL) free(m_Base);
+ HelpKeys = -1;
+}
+
+void cStreamdevMenuRecordings::SetHelpKeys(void) {
+ cStreamdevMenuRecordingItem *ri =(cStreamdevMenuRecordingItem*)Get(Current());
+ int NewHelpKeys = HelpKeys;
+ if (ri) {
+ if (ri->IsDirectory())
+ NewHelpKeys = 1;
+ else {
+ NewHelpKeys = 2;
+ cRemoteRecording *recording = GetRecording(ri);
+ if (recording && recording->Summary())
+ NewHelpKeys = 3;
+ }
+ }
+ if (NewHelpKeys != HelpKeys) {
+ switch (NewHelpKeys) {
+ case 0: SetHelp(NULL); break;
+ case 1: SetHelp(tr("Open")); break;
+ case 2:
+ case 3: SetHelp(NULL, NULL, tr("Delete"), NewHelpKeys == 3 ? tr("Summary") : NULL);
+ //SetHelp(tr("Play"), tr("Rewind"), tr("Delete"), NewHelpKeys == 3 ? tr("Summary") : NULL); XXX
+ }
+ HelpKeys = NewHelpKeys;
+ }
+}
+
+cRemoteRecording *cStreamdevMenuRecordings::GetRecording(
+ cStreamdevMenuRecordingItem *Item) {
+ Dprintf("looking for %s\n", Item->FileName());
+ cRemoteRecording *recording = Recordings.GetByName(Item->FileName());
+ if (!recording)
+ ERROR(tr("Error while accessing recording!"));
+ return recording;
+}
+
+bool cStreamdevMenuRecordings::Open(bool OpenSubMenus) {
+ cStreamdevMenuRecordingItem *ri
+ = (cStreamdevMenuRecordingItem*)Get(Current());
+
+ if (ri && ri->IsDirectory()) {
+ const char *t = ri->Name();
+ char *buffer = NULL;
+ if (m_Base) {
+ asprintf(&buffer, "%s~%s", m_Base, t);
+ t = buffer;
+ }
+ AddSubMenu(new cStreamdevMenuRecordings(t, m_Level + 1, OpenSubMenus));
+ if (buffer != NULL) free(buffer);
+ return true;
+ }
+ return false;
+}
+
+eOSState cStreamdevMenuRecordings::Select(void) {
+ cStreamdevMenuRecordingItem *ri
+ = (cStreamdevMenuRecordingItem*)Get(Current());
+
+ if (ri) {
+ if (ri->IsDirectory())
+ Open();
+ /*else {
+ cControl::Launch(new cStreamdevPlayerControl(ri->FileName()));
+ return osEnd;
+ } XXX */
+ }
+ return osContinue;
+}
+
+eOSState cStreamdevMenuRecordings::Delete(void) {
+ if (HasSubMenu() || Count() == 0)
+ return osContinue;
+ cStreamdevMenuRecordingItem *ri
+ = (cStreamdevMenuRecordingItem*)Get(Current());
+ if (ri && !ri->IsDirectory()) {
+ if (Interface->Confirm(tr("Delete recording?"))) {
+ cRemoteRecording *recording = GetRecording(ri);
+ if (recording) {
+ if (ClientSocket.DeleteRecording(recording)) {
+ cOsdMenu::Del(Current());
+ Recordings.Del(recording);
+ Display();
+ if (!Count())
+ return osBack;
+ }
+ }
+ }
+ }
+ return osContinue;
+}
+
+eOSState cStreamdevMenuRecordings::Summary(void) {
+ if (HasSubMenu() || Count() == 0)
+ return osContinue;
+ cStreamdevMenuRecordingItem *ri=(cStreamdevMenuRecordingItem *)Get(Current());
+ if (ri && !ri->IsDirectory()) {
+ cRemoteRecording *recording = GetRecording(ri);
+ if (recording && recording->Summary() && *recording->Summary())
+ return AddSubMenu(new cMenuText(tr("Summary"), recording->Summary()));
+ }
+ return osContinue;
+}
+
+eOSState cStreamdevMenuRecordings::ProcessKey(eKeys Key) {
+ bool HadSubMenu = HasSubMenu();
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kOk:
+ case kRed: return Select();
+ case kYellow: return Delete();
+ case kBlue: return Summary();
+ default: break;
+ }
+ }
+
+ if (Key == kYellow && HadSubMenu && !HasSubMenu()) {
+ cOsdMenu::Del(Current());
+ if (!Count())
+ return osBack;
+ Display();
+ }
+
+ if (!HasSubMenu() && Key != kNone)
+ SetHelpKeys();
+ return state;
+}
+
+// --- cStreamdevMenuTimerItem -----------------------------------------------
+
+class cStreamdevMenuTimerItem: public cOsdItem {
+private:
+ cRemoteTimer *m_Timer;
+
+public:
+ cStreamdevMenuTimerItem(cRemoteTimer *Timer);
+ virtual ~cStreamdevMenuTimerItem();
+
+ virtual void Set(void);
+
+ cRemoteTimer *Timer(void) const { return m_Timer; }
+};
+
+cStreamdevMenuTimerItem::cStreamdevMenuTimerItem(cRemoteTimer *Timer) {
+ m_Timer = Timer;
+ Set();
+}
+
+cStreamdevMenuTimerItem::~cStreamdevMenuTimerItem() {
+}
+
+void cStreamdevMenuTimerItem::Set(void) {
+ char *buffer = NULL;
+ asprintf(&buffer, "%c\t%d\t%s\t%02d:%02d\t%02d:%02d\t%s",
+ !m_Timer->Active() ? ' ' :
+ m_Timer->FirstDay() ? '!' :
+ /*m_Timer->Recording() ? '#' :*/ '>',
+ m_Timer->Channel()->Number(),
+ m_Timer->PrintDay(m_Timer->Day()),
+ m_Timer->Start() / 100,
+ m_Timer->Start() % 100,
+ m_Timer->Stop() / 100,
+ m_Timer->Stop() % 100,
+ m_Timer->File());
+ SetText(buffer, false);
+}
+
+// --- cStreamdevMenuTimers --------------------------------------------------
+
+cStreamdevMenuTimers::cStreamdevMenuTimers(void):
+ cOsdMenu(tr("Remote Timers"), 2, CHNUMWIDTH, 10, 6, 6) {
+ Refresh();
+ SetHelp(tr("Edit"), tr("New"), tr("Delete"), tr("On/Off"));
+}
+
+cStreamdevMenuTimers::~cStreamdevMenuTimers() {
+}
+
+eOSState cStreamdevMenuTimers::ProcessKey(eKeys Key) {
+ int timerNum = HasSubMenu() ? Count() : -1;
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kOk: return Summary();
+ case kRed: return Edit();
+ case kGreen: return New();
+ case kYellow: return Delete();
+ case kBlue: OnOff(); break;
+ default: break;
+ }
+ }
+
+ if (timerNum >= 0 && !HasSubMenu()) {
+ Refresh();
+ Display();
+ }
+ return state;
+}
+
+eOSState cStreamdevMenuTimers::Edit(void) {
+ if (HasSubMenu() || Count() == 0)
+ return osContinue;
+ isyslog("Streamdev: Editing remote timer %d", CurrentTimer()->Index() + 1);
+ return AddSubMenu(new cStreamdevMenuEditTimer(CurrentTimer()));
+}
+
+eOSState cStreamdevMenuTimers::New(void) {
+ if (HasSubMenu())
+ return osContinue;
+ return AddSubMenu(new cStreamdevMenuEditTimer(new cRemoteTimer, true));
+}
+
+eOSState cStreamdevMenuTimers::Delete(void) {
+ cRemoteTimer *ti = CurrentTimer();
+ if (ti) {
+ if (Interface->Confirm(tr("Delete timer?"))) {
+ int idx = ti->Index();
+ if (ClientSocket.DeleteTimer(ti)) {
+ RemoteTimers.Del(ti);
+ cOsdMenu::Del(Current());
+ isyslog("Streamdev: Remote timer %d deleted", idx + 1);
+ }
+ Refresh();
+ Display();
+ }
+ }
+ return osContinue;
+}
+
+eOSState cStreamdevMenuTimers::OnOff(void) {
+ cRemoteTimer *timer = CurrentTimer();
+ if (timer) {
+ cRemoteTimer data = *timer;
+ data.OnOff();
+ if (data.FirstDay())
+ isyslog("Streamdev: Remote timer %d first day set to %s",
+ data.Index() + 1, data.PrintFirstDay());
+ else
+ isyslog("Streamdev: Remote timer %d %sactivated", data.Index() + 1,
+ data.Active() ? "" : "de");
+
+ if (ClientSocket.SaveTimer(timer, data)) {
+ *timer = data;
+ RefreshCurrent();
+ DisplayCurrent(true);
+ } else {
+ Refresh();
+ Display();
+ }
+ }
+ return osContinue;
+}
+
+eOSState cStreamdevMenuTimers::Summary(void) {
+ if (HasSubMenu() || Count() == 0)
+ return osContinue;
+
+ cRemoteTimer *ti = CurrentTimer();
+ if (ti && !ti->Summary().IsNull())
+ return AddSubMenu(new cMenuText(tr("Summary"), ti->Summary()));
+
+ return osContinue;
+}
+
+cRemoteTimer *cStreamdevMenuTimers::CurrentTimer(void) {
+ cStreamdevMenuTimerItem *item = (cStreamdevMenuTimerItem*)Get(Current());
+ return item ? item->Timer() : NULL;
+}
+
+void cStreamdevMenuTimers::Refresh(void) {
+ Clear();
+ if (RemoteTimers.Load()) {
+ for (cRemoteTimer *t = RemoteTimers.First(); t; t = RemoteTimers.Next(t)) {
+ Add(new cStreamdevMenuTimerItem(t));
+ }
+ }
+}
diff --git a/client/menu.h b/client/menu.h
new file mode 100644
index 0000000..17d91ae
--- /dev/null
+++ b/client/menu.h
@@ -0,0 +1,144 @@
+/*
+ * $Id: menu.h,v 1.1 2004/12/30 22:44:02 lordjaxom Exp $
+ */
+
+#ifndef VDR_STREAMDEV_MENU_H
+#define VDR_STREAMDEV_MENU_H
+
+#include <vdr/osd.h>
+
+#include "client/remote.h"
+
+class cStreamdevMenuRecordingItem;
+
+// --- cStreamdevMenu --------------------------------------------------------
+
+class cStreamdevMenu: public cOsdMenu {
+private:
+ enum eSubmenus {
+ sub_Start = os_User,
+ subSchedule,
+ subTimers,
+ subRecordings,
+ subSuspend,
+ subSyncEPG
+ };
+
+protected:
+ void SuspendServer(void);
+
+public:
+ cStreamdevMenu(void);
+ virtual ~cStreamdevMenu(void);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+// --- cStreamdevMenuSchedule ------------------------------------------------
+
+class cStreamdevMenuSchedule: public cOsdMenu {
+private:
+ bool m_Now;
+ bool m_Next;
+ int m_OtherChannel;
+ const cSchedules *m_Schedules;
+#if VDRVERSNUM < 10300
+ cMutexLock m_Lock;
+#else
+ cSchedulesLock m_Lock;
+#endif
+
+protected:
+ void PrepareSchedule(cChannel *Channel);
+
+ eOSState Switch(void);
+ eOSState Record(void);
+
+public:
+ cStreamdevMenuSchedule(void);
+ virtual ~cStreamdevMenuSchedule(void);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+// --- cStreamdevMenuWhatsOn -------------------------------------------------
+
+class cStreamdevMenuWhatsOn: public cOsdMenu {
+private:
+ static int m_CurrentChannel;
+#if VDRVERSNUM < 10300
+ static const cEventInfo *m_ScheduleEventInfo;
+#else
+ static const cEvent *m_ScheduleEventInfo;
+#endif
+
+protected:
+ eOSState Switch(void);
+ eOSState Record(void);
+
+public:
+ cStreamdevMenuWhatsOn(const cSchedules *Schedules, bool Now,
+ int CurrentChannel);
+
+ static int CurrentChannel(void) { return m_CurrentChannel; }
+ static void SetCurrentChannel(int Channel) { m_CurrentChannel = Channel; }
+#if VDRVERSNUM < 10300
+ static const cEventInfo *ScheduleEventInfo(void);
+#else
+ static const cEvent *ScheduleEventInfo(void);
+#endif
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+// --- cStreamdevMenuRecordings ----------------------------------------------
+
+class cStreamdevMenuRecordings: public cOsdMenu {
+private:
+ char *m_Base;
+ int m_Level;
+
+ static int HelpKeys;
+ static cRemoteRecordings Recordings;
+
+protected:
+ bool Open(bool OpenSubMenus = false);
+ void SetHelpKeys();
+ cRemoteRecording *cStreamdevMenuRecordings::GetRecording(
+ cStreamdevMenuRecordingItem *Item);
+
+ eOSState Select(void);
+ eOSState Delete(void);
+ eOSState Summary(void);
+
+public:
+ cStreamdevMenuRecordings(const char *Base = NULL, int Level = 0,
+ bool OpenSubMenus = false);
+ virtual ~cStreamdevMenuRecordings();
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+// --- cStreamdevMenuTimers --------------------------------------------------
+
+class cStreamdevMenuTimers: public cOsdMenu {
+protected:
+ eOSState Edit(void);
+ eOSState New(void);
+ eOSState Delete(void);
+ eOSState OnOff(void);
+ eOSState Summary(void);
+
+ cRemoteTimer *CurrentTimer(void);
+
+ void Refresh(void);
+
+public:
+ cStreamdevMenuTimers(void);
+ virtual ~cStreamdevMenuTimers();
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+#endif // VDR_STREAMDEV_MENU_H
+
diff --git a/client/remote.c b/client/remote.c
new file mode 100644
index 0000000..4cc7cfe
--- /dev/null
+++ b/client/remote.c
@@ -0,0 +1,475 @@
+/*
+ * $Id: remote.c,v 1.1 2004/12/30 22:44:02 lordjaxom Exp $
+ */
+
+#include "client/remote.h"
+#include "client/device.h"
+#include "common.h"
+
+cRemoteTimers RemoteTimers;
+
+// --- cRemoteRecording ------------------------------------------------------
+
+cRemoteRecording::cRemoteRecording(const char *Text) {
+ m_IsValid = false;
+ m_Index = -1;
+ m_IsNew = false;
+ m_TitleBuffer = NULL;
+
+ char *ptr;
+ char *timestr;
+ int idx;
+
+ Dprintf("text: %s\n", Text);
+
+ m_Index = strtoul(Text, &ptr, 10);
+ Dprintf("index: %d\n", m_Index);
+ if (*ptr == '\0' || *++ptr == '\0' ) return;
+ timestr = ptr;
+ while (*ptr != '\0' && !isspace(*ptr)) ++ptr;
+ if (*ptr == '\0' || *++ptr == '\0') return;
+ while (*ptr != '\0' && *ptr != '*' && !isspace(*ptr)) ++ptr;
+ if (*ptr == '*') m_IsNew = true;
+ Dprintf("new: %d\n", m_IsNew);
+ *(ptr++) = '\0';
+ m_StartTime = timestr;
+ idx = -1;
+ while ((idx = m_StartTime.Find(' ', idx + 1)) != -1) m_StartTime[idx] = '\t';
+ Dprintf("m_Start: %s\n", (const char*)m_StartTime);
+ if (*ptr == 0) return;
+ if (isspace(*ptr)) ++ptr;
+ if (*ptr == 0) return;
+ m_Name = ptr;
+ Dprintf("file: %s\n", (const char*)m_Name);
+ m_IsValid = true;
+}
+
+cRemoteRecording::~cRemoteRecording(void) {
+}
+
+bool cRemoteRecording::operator==(const cRemoteRecording &Recording) {
+ return m_IsValid == Recording.m_IsValid
+ && m_Index == Recording.m_Index
+ && m_StartTime == Recording.m_StartTime
+ && m_Name == Recording.m_Name;
+}
+
+void cRemoteRecording::ParseInfo(const char *Text) {
+ m_Summary = strreplace(strdup(Text), '|', '\n');
+}
+
+const char *cRemoteRecording::Title(char Delimiter, bool NewIndicator,
+ int Level) {
+ char New = NewIndicator && IsNew() ? '*' : ' ';
+
+ if (m_TitleBuffer != NULL) {
+ free(m_TitleBuffer);
+ m_TitleBuffer = NULL;
+ }
+
+ if (Level < 0 || Level == HierarchyLevels()) {
+ char *s;
+ const char *t;
+ if (Level > 0 && (t = strrchr(m_Name, '~')) != NULL)
+ t++;
+ else
+ t = (const char*)m_Name;
+
+ asprintf(&m_TitleBuffer, "%s%c%c%s", (const char*)m_StartTime, New,
+ Delimiter, t);
+ // let's not display a trailing '~':
+ stripspace(m_TitleBuffer);
+ s = &m_TitleBuffer[strlen(m_TitleBuffer) - 1];
+ if (*s == '~')
+ *s = 0;
+ } else if (Level < HierarchyLevels()) {
+ const char *s = m_Name;
+ const char *p = s;
+ while (*++s) {
+ if (*s == '~') {
+ if (Level--)
+ p = s + 1;
+ else
+ break;
+ }
+ }
+ m_TitleBuffer = MALLOC(char, s - p + 3);
+ *m_TitleBuffer = Delimiter;
+ *(m_TitleBuffer + 1) = Delimiter;
+ strn0cpy(m_TitleBuffer + 2, p, s - p + 1);
+ } else
+ return "";
+ return m_TitleBuffer;
+}
+
+int cRemoteRecording::HierarchyLevels(void)
+{
+ const char *s = m_Name;
+ int level = 0;
+ while (*++s) {
+ if (*s == '~') ++level;
+ }
+ return level;
+}
+
+// --- cRemoteRecordings -----------------------------------------------------
+
+bool cRemoteRecordings::Load(void) {
+ Clear();
+ return ClientSocket.LoadRecordings(*this);
+}
+
+cRemoteRecording *cRemoteRecordings::GetByName(const char *Name) {
+ for (cRemoteRecording *r = First(); r; r = Next(r))
+ if (strcmp(r->Name(), Name) == 0)
+ return r;
+ return NULL;
+}
+
+// --- cRemoteTimer ----------------------------------------------------------
+
+cRemoteTimer::cRemoteTimer(const char *Text) {
+ m_IsValid = false;
+ m_Index = -1;
+ m_Active = -1;
+ m_Day = -1;
+ m_Start = -1;
+ m_Stop = -1;
+ m_StartTime = 0;
+ m_StopTime = 0;
+ m_Priority = -1;
+ m_Lifetime = -1;
+ m_File[0] = '\0';
+ m_FirstDay = 0;
+ m_Buffer = NULL;
+ m_Channel = NULL;
+
+ char *tmpbuf;
+ char *ptr;
+
+ Dprintf("text: %s\n", Text);
+
+ m_Index = strtoul(Text, &ptr, 10);
+ Dprintf("index: %d\n", m_Index);
+ if (*ptr == '\0' || *++ptr == '\0') return;
+ m_Active = strtoul(ptr, &ptr, 10);
+ Dprintf("m_Active: %d\n", m_Active);
+ if (*ptr == '\0' || *++ptr == '\0') return;
+
+ tmpbuf = ptr;
+ while (*ptr != '\0' && *ptr != ':') ++ptr;
+ if (*ptr == '\0') return;
+ *(ptr++)= '\0';
+ if (isnumber(tmpbuf))
+ m_Channel = Channels.GetByNumber(strtoul(tmpbuf, NULL, 10));
+ else
+ m_Channel = Channels.GetByChannelID(tChannelID::FromString(tmpbuf));
+ Dprintf("channel no.: %d\n", m_Channel->Number());
+
+ tmpbuf = ptr;
+ while (*ptr != '\0' && *ptr != ':') ++ptr;
+ if (*ptr == '\0') return;
+ *(ptr++) = '\0';
+ m_Day = ParseDay(tmpbuf, &m_FirstDay);
+ Dprintf("Day: %d\n", m_Day);
+ m_Start = strtoul(ptr, &ptr, 10);
+ Dprintf("Start: %d\n", m_Start);
+ if (*ptr == '\0' || *++ptr == '\0') return;
+ m_Stop = strtoul(ptr, &ptr, 10);
+ Dprintf("Stop: %d\n", m_Stop);
+ if (*ptr == '\0' || *++ptr == '\0') return;
+ m_Priority = strtoul(ptr, &ptr, 10);
+ Dprintf("Prio: %d\n", m_Priority);
+ if (*ptr == '\0' || *++ptr == '\0') return;
+ m_Lifetime = strtoul(ptr, &ptr, 10);
+ Dprintf("Lifetime: %d\n", m_Lifetime);
+ if (*ptr == '\0' || *++ptr == '\0') return;
+ tmpbuf = ptr;
+ while (*ptr != '\0' && *ptr != ':') ++ptr;
+ if (*ptr == '\0') return;
+ *(ptr++) = '\0';
+ strncpy(m_File, tmpbuf, MaxFileName);
+ Dprintf("file: %s\n", m_File);
+ if (*ptr != '\0') m_Summary = ptr;
+ Dprintf("summary: %s\n", (const char*)m_Summary);
+ m_IsValid = true;
+}
+
+#if VDRVERSNUM < 10300
+cRemoteTimer::cRemoteTimer(const cEventInfo *EventInfo) {
+ time_t tstart = EventInfo->GetTime();
+ time_t tstop = tstart + EventInfo->GetDuration() + Setup.MarginStop * 60;
+ tstart -= Setup.MarginStart * 60;
+ struct tm tm_r;
+ struct tm *time = localtime_r(&tstart, &tm_r);
+ const char *title = EventInfo->GetTitle();
+ cChannel *channel = Channels.GetByChannelID(EventInfo->GetChannelID(), true);
+#else
+cRemoteTimer::cRemoteTimer(const cEvent *Event) {
+ time_t tstart = Event->StartTime();
+ time_t tstop = tstart + Event->Duration() + Setup.MarginStop * 60;
+ tstart -= Setup.MarginStart * 60;
+ struct tm tm_r;
+ struct tm *time = localtime_r(&tstart, &tm_r);
+ const char *title = Event->Title();
+ cChannel *channel = Channels.GetByChannelID(Event->ChannelID(), true);
+#endif
+
+ m_IsValid = true;
+ m_Index = -1;
+ m_Active = true;
+ m_Day = time->tm_mday;
+ m_Start = time->tm_hour * 100 + time->tm_min;
+ time = localtime_r(&tstop, &tm_r);
+ m_Stop = time->tm_hour * 100 + time->tm_min;
+ m_StartTime = 0;
+ m_StopTime = 0;
+ if (m_Stop >= 2400) m_Stop -= 2400;
+ m_Priority = Setup.DefaultPriority;
+ m_Lifetime = Setup.DefaultLifetime;
+ m_File[0] = '\0';
+ if (!isempty(title))
+ strn0cpy(m_File, title, sizeof(m_File));
+ m_FirstDay = 0;
+ m_Channel = channel;
+}
+
+cRemoteTimer::cRemoteTimer(void) {
+ time_t t = time(NULL);
+ struct tm tm_r;
+ struct tm *now = localtime_r(&t, &tm_r);
+
+ m_IsValid = true;
+ m_Index = -1;
+ m_Active = -1;
+ m_Day = now->tm_mday;
+ m_Start = now->tm_hour * 100 + now->tm_min;
+ m_Stop = now->tm_hour * 60 + now->tm_min + Setup.InstantRecordTime;
+ m_Stop = (m_Stop / 60) * 100 + (m_Stop % 60);
+ if (m_Stop >= 2400) m_Stop -= 2400;
+ m_StartTime = 0;
+ m_StopTime = 0;
+ m_Priority = Setup.DefaultPriority;
+ m_Lifetime = Setup.DefaultLifetime;
+ m_File[0] = '\0';
+ m_FirstDay = 0;
+ m_Buffer = NULL;
+ m_Channel = Channels.GetByNumber(cDevice::CurrentChannel());
+}
+
+cRemoteTimer::~cRemoteTimer() {
+ if (m_Buffer != NULL) free(m_Buffer);
+}
+
+cRemoteTimer &cRemoteTimer::operator=(const cRemoteTimer &Timer) {
+ Dprintf("\n\n\n\nOPÜERATHVBDÖLJVG\n\n\n");
+ m_IsValid = Timer.m_IsValid;
+ m_Index = Timer.m_Index;
+ m_Active = Timer.m_Active;
+ m_Day = Timer.m_Day;
+ m_Start = Timer.m_Start;
+ m_Stop = Timer.m_Stop;
+ m_Priority = Timer.m_Priority;
+ m_Lifetime = Timer.m_Lifetime;
+ m_FirstDay = Timer.m_FirstDay;
+ m_Channel = Timer.m_Channel;
+ m_Summary = Timer.m_Summary;
+ return *this;
+}
+
+bool cRemoteTimer::operator==(const cRemoteTimer &Timer) {
+ return m_IsValid == Timer.m_IsValid
+ && m_Index == Timer.m_Index
+ && m_Active == Timer.m_Active
+ && m_Day == Timer.m_Day
+ && m_Start == Timer.m_Start
+ && m_Stop == Timer.m_Stop
+ && m_Priority == Timer.m_Priority
+ && m_Lifetime == Timer.m_Lifetime
+ && m_FirstDay == Timer.m_FirstDay
+ && m_Channel == Timer.m_Channel
+ && strcmp(m_File, Timer.m_File) == 0
+ && m_Summary == Timer.m_Summary;
+}
+
+int cRemoteTimer::ParseDay(const char *s, time_t *FirstDay) {
+ char *tail;
+ int d = strtol(s, &tail, 10);
+ if (FirstDay)
+ *FirstDay = 0;
+ if (tail && *tail) {
+ d = 0;
+ if (tail == s) {
+ const char *first = strchr(s, '@');
+ int l = first ? first - s : strlen(s);
+ if (l == 7) {
+ for (const char *p = s + 6; p >= s; p--) {
+ d <<= 1;
+ d |= (*p != '-');
+ }
+ d |= 0x80000000;
+ }
+ if (FirstDay && first) {
+ ++first;
+ if (strlen(first) == 10) {
+ struct tm tm_r;
+ if (3 == sscanf(first, "%d-%d-%d", &tm_r.tm_year, &tm_r.tm_mon, &tm_r.tm_mday)) {
+ tm_r.tm_year -= 1900;
+ tm_r.tm_mon--;
+ tm_r.tm_hour = tm_r.tm_min = tm_r.tm_sec = 0;
+ tm_r.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
+ *FirstDay = mktime(&tm_r);
+ }
+ }
+ else
+ d = 0;
+ }
+ }
+ }
+ else if (d < 1 || d > 31)
+ d = 0;
+ return d;
+}
+
+const char *cRemoteTimer::PrintDay(int d, time_t FirstDay) {
+#define DAYBUFFERSIZE 32
+ static char buffer[DAYBUFFERSIZE];
+ if ((d & 0x80000000) != 0) {
+ char *b = buffer;
+ const char *w = tr("MTWTFSS");
+ while (*w) {
+ *b++ = (d & 1) ? *w : '-';
+ d >>= 1;
+ w++;
+ }
+ if (FirstDay) {
+ struct tm tm_r;
+ localtime_r(&FirstDay, &tm_r);
+ b += strftime(b, DAYBUFFERSIZE - (b - buffer), "@%Y-%m-%d", &tm_r);
+ }
+ *b = 0;
+ }
+ else
+ sprintf(buffer, "%d", d);
+ return buffer;
+}
+
+const char *cRemoteTimer::PrintFirstDay(void) const {
+ if (m_FirstDay) {
+ const char *s = PrintDay(m_Day, m_FirstDay);
+ if (strlen(s) == 18)
+ return s + 8;
+ }
+ return ""; // not NULL, so the caller can always use the result
+}
+
+void cRemoteTimer::OnOff(void) {
+ if (IsSingleEvent())
+ m_Active = !m_Active;
+ else if (m_FirstDay) {
+ m_FirstDay = 0;
+ m_Active = false;
+ }
+ else if (m_Active)
+ Skip();
+ else
+ m_Active = true;
+ Matches(); // refresh m_Start and end time
+}
+
+time_t cRemoteTimer::SetTime(time_t t, int SecondsFromMidnight) {
+ struct tm tm_r;
+ tm tm = *localtime_r(&t, &tm_r);
+ tm.tm_hour = SecondsFromMidnight / 3600;
+ tm.tm_min = (SecondsFromMidnight % 3600) / 60;
+ tm.tm_sec = SecondsFromMidnight % 60;
+ tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
+ return mktime(&tm);
+}
+
+bool cRemoteTimer::Matches(time_t t) {
+ m_StartTime = m_StopTime = 0;
+ if (t == 0)
+ t = time(NULL);
+
+ int begin = TimeToInt(m_Start); // seconds from midnight
+ int length = TimeToInt(m_Stop) - begin;
+ if (length < 0)
+ length += SECSINDAY;
+
+ int DaysToCheck = IsSingleEvent() ? 61 : 7; // 61 to handle months with 31/30/31
+ for (int i = -1; i <= DaysToCheck; i++) {
+ time_t t0 = IncDay(t, i);
+ if (DayMatches(t0)) {
+ time_t a = SetTime(t0, begin);
+ time_t b = a + length;
+ if ((!m_FirstDay || a >= m_FirstDay) && t <= b) {
+ m_StartTime = a;
+ m_StopTime = b;
+ break;
+ }
+ }
+ }
+ if (!m_StartTime)
+ m_StartTime = m_FirstDay; // just to have something that's more than a week in the future
+ else if (t > m_StartTime || t > m_FirstDay + SECSINDAY + 3600) // +3600 in case of DST change
+ m_FirstDay = 0;
+ return m_Active && m_StartTime <= t && t < m_StopTime; // must m_Stop *before* m_StopTime to allow adjacent timers
+}
+
+bool cRemoteTimer::DayMatches(time_t t) {
+ return IsSingleEvent()
+ ? GetMDay(t) == m_Day
+ : (m_Day & (1 << GetWDay(t))) != 0;
+}
+
+int cRemoteTimer::GetMDay(time_t t)
+{
+ struct tm tm_r;
+ return localtime_r(&t, &tm_r)->tm_mday;
+}
+
+int cRemoteTimer::GetWDay(time_t t)
+{
+ struct tm tm_r;
+ int weekday = localtime_r(&t, &tm_r)->tm_wday;
+ return weekday == 0 ? 6 : weekday - 1; // we start with monday==0!
+}
+
+time_t cRemoteTimer::IncDay(time_t t, int Days) {
+ struct tm tm_r;
+ tm tm = *localtime_r(&t, &tm_r);
+ tm.tm_mday += Days; // now tm_mday may be out of its valid range
+ int h = tm.tm_hour; // save original hour to compensate for DST change
+ tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
+ t = mktime(&tm); // normalize all values
+ tm.tm_hour = h; // compensate for DST change
+ return mktime(&tm); // calculate final result
+}
+
+const char *cRemoteTimer::ToText(void) {
+ char *summary = NULL;
+
+ if (m_Buffer != NULL) free(m_Buffer);
+
+ strreplace(m_File, ':', '|');
+ if (!m_Summary.IsNull())
+ summary = strreplace(strdup(m_Summary), ':', '|');
+
+ asprintf(&m_Buffer, "%d:%s:%s:%04d:%04d:%d:%d:%s:%s", m_Active,
+ Channel()->GetChannelID().ToString(), PrintDay(m_Day, m_FirstDay),
+ m_Start, m_Stop, m_Priority, m_Lifetime, m_File, summary ? summary : "");
+
+ if (summary != NULL)
+ free(summary);
+ strreplace(m_File, '|', ':');
+ return m_Buffer;
+}
+
+// --- cRemoteTimers ---------------------------------------------------------
+
+bool cRemoteTimers::Load(void) {
+ Clear();
+ return ClientSocket.LoadTimers(*this);
+}
+
diff --git a/client/remote.h b/client/remote.h
new file mode 100644
index 0000000..24a7067
--- /dev/null
+++ b/client/remote.h
@@ -0,0 +1,132 @@
+/*
+ * $Id: remote.h,v 1.1 2004/12/30 22:44:03 lordjaxom Exp $
+ */
+
+#ifndef VDR_STREAMDEV_REMOTE_H
+#define VDR_STREAMDEV_REMOTE_H
+
+#include <vdr/config.h>
+
+#include "tools/string.h"
+
+#if VDRVERSNUM < 10300
+class cEventInfo;
+#else
+class cEvent;
+#endif
+class cChannel;
+
+class cRemoteRecording: public cListObject {
+private:
+ bool m_IsValid;
+ int m_Index;
+ bool m_IsNew;
+ char *m_TitleBuffer;
+ cTBString m_StartTime;
+ cTBString m_Name;
+ cTBString m_Summary;
+
+public:
+ cRemoteRecording(const char *Text);
+ ~cRemoteRecording();
+
+ bool operator==(const cRemoteRecording &Recording);
+ bool operator!=(const cRemoteRecording &Recording);
+
+ void ParseInfo(const char *Text);
+
+ bool IsValid(void) const { return m_IsValid; }
+ int Index(void) const { return m_Index; }
+ const char *StartTime(void) const { return m_StartTime; }
+ bool IsNew(void) const { return m_IsNew; }
+ const char *Name(void) const { return m_Name; }
+ const char *Summary(void) const { return m_Summary; }
+ const char *Title(char Delimiter, bool NewIndicator, int Level);
+ int HierarchyLevels(void);
+};
+
+inline bool cRemoteRecording::operator!=(const cRemoteRecording &Recording) {
+ return !operator==(Recording);
+}
+
+class cRemoteRecordings: public cList<cRemoteRecording> {
+public:
+ bool Load(void);
+ cRemoteRecording *GetByName(const char *Name);
+};
+
+class cRemoteTimer: public cListObject {
+ friend class cStreamdevMenuEditTimer;
+
+private:
+ bool m_IsValid;
+ int m_Index;
+ int m_Active;
+ int m_Day;
+ int m_Start;
+ int m_Stop;
+ time_t m_StartTime;
+ time_t m_StopTime;
+ int m_Priority;
+ int m_Lifetime;
+ char m_File[MaxFileName];
+ time_t m_FirstDay;
+ cTBString m_Summary;
+ char *m_Buffer;
+ const cChannel *m_Channel;
+
+public:
+ cRemoteTimer(const char *Text);
+#if VDRVERSNUM < 10300
+ cRemoteTimer(const cEventInfo *EventInfo);
+#else
+ cRemoteTimer(const cEvent *Event);
+#endif
+ cRemoteTimer(void);
+ ~cRemoteTimer();
+
+ cRemoteTimer &operator=(const cRemoteTimer &Timer);
+ bool operator==(const cRemoteTimer &Timer);
+ bool operator!=(const cRemoteTimer &Timer) { return !operator==(Timer); }
+
+ static int ParseDay(const char *s, time_t *FirstDay);
+ static const char *PrintDay(int d, time_t FirstDay = 0);
+ static time_t SetTime(time_t t, int SecondsFromMidnight);
+ static time_t IncDay(time_t t, int Days);
+ static int TimeToInt(int t) { return (t / 100 * 60 + t % 100) * 60; }
+
+ const char *PrintFirstDay(void) const;
+ void OnOff(void);
+ bool IsSingleEvent(void) const { return (m_Day & 0x80000000) == 0; }
+ void Skip(void) { m_FirstDay = IncDay(SetTime(StartTime(), 0), 1); }
+ bool Matches(time_t t = 0);
+ bool DayMatches(time_t t = 0);
+ int GetMDay(time_t t);
+ int GetWDay(time_t t);
+
+ bool IsValid(void) const { return m_IsValid; }
+ int Index(void) const { return m_Index; }
+ int Active(void) const { return m_Active; }
+ int Day(void) const { return m_Day; }
+ int Start(void) const { return m_Start; }
+ int Stop(void) const { return m_Stop; }
+ time_t StartTime(void) { if (!m_StartTime) Matches(); return m_StartTime; }
+ time_t StopTime(void) { if (!m_StopTime) Matches(); return m_StopTime; }
+ int Priority(void) const { return m_Priority; }
+ int Lifetime(void) const { return m_Lifetime; }
+ const char *File(void) const { return m_File; }
+ time_t FirstDay(void) const { return m_FirstDay; }
+ const cTBString &Summary(void) const { return m_Summary; }
+ const cChannel *Channel(void) const { return m_Channel; }
+
+ const char *ToText(void);
+};
+
+class cRemoteTimers: public cList<cRemoteTimer> {
+public:
+ bool Load(void);
+};
+
+extern cRemoteTimers RemoteTimers;
+
+#endif // VDR_STREAMDEV_REMOTE_H
diff --git a/client/setup.c b/client/setup.c
new file mode 100644
index 0000000..f8f53db
--- /dev/null
+++ b/client/setup.c
@@ -0,0 +1,83 @@
+/*
+ * $Id: setup.c,v 1.1 2004/12/30 22:44:03 lordjaxom Exp $
+ */
+
+#include <vdr/menuitems.h>
+
+#include "client/setup.h"
+#include "client/device.h"
+#include "i18n.h"
+
+cStreamdevClientSetup StreamdevClientSetup;
+
+cStreamdevClientSetup::cStreamdevClientSetup(void) {
+ StartClient = false;
+ RemotePort = 2004;
+ StreamPIDS = true;
+#if VDRVERSNUM >= 10300
+ StreamFilters = false;
+#endif
+ SyncEPG = false;
+ strcpy(RemoteIp, "");
+}
+
+bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) {
+ if (strcmp(Name, "StartClient") == 0) StartClient = atoi(Value);
+ else if (strcmp(Name, "RemoteIp") == 0) {
+ if (strcmp(Value, "-none-") == 0)
+ strcpy(RemoteIp, "");
+ else
+ strcpy(RemoteIp, Value);
+ }
+ else if (strcmp(Name, "RemotePort") == 0) RemotePort = atoi(Value);
+ else if (strcmp(Name, "StreamPIDS") == 0) StreamPIDS = atoi(Value);
+#if VDRVERSNUM >= 10300
+ else if (strcmp(Name, "StreamFilters") == 0) StreamFilters = atoi(Value);
+#endif
+ else if (strcmp(Name, "SyncEPG") == 0) SyncEPG = atoi(Value);
+ else return false;
+ return true;
+}
+
+cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(void) {
+ m_NewSetup = StreamdevClientSetup;
+
+ AddBoolEdit (tr("Start Client"), m_NewSetup.StartClient);
+ AddIpEdit (tr("Remote IP"), m_NewSetup.RemoteIp);
+ AddShortEdit(tr("Remote Port"), m_NewSetup.RemotePort);
+ AddBoolEdit (tr("MultiPID Streaming"), m_NewSetup.StreamPIDS);
+#if VDRVERSNUM >= 10300
+ AddBoolEdit (tr("Filter Streaming"), m_NewSetup.StreamFilters);
+#endif
+ AddBoolEdit (tr("Synchronize EPG"), m_NewSetup.SyncEPG);
+ SetCurrent(Get(0));
+}
+
+cStreamdevClientMenuSetupPage::~cStreamdevClientMenuSetupPage() {
+}
+
+void cStreamdevClientMenuSetupPage::Store(void) {
+ if (m_NewSetup.StartClient != StreamdevClientSetup.StartClient) {
+ if (m_NewSetup.StartClient)
+ cStreamdevDevice::Init();
+ else
+ INFO(tr("Please restart VDR to activate changes"));
+ }
+
+ SetupStore("StartClient", m_NewSetup.StartClient);
+ if (strcmp(m_NewSetup.RemoteIp, "") == 0)
+ SetupStore("RemoteIp", "-none-");
+ else
+ SetupStore("RemoteIp", m_NewSetup.RemoteIp);
+ SetupStore("RemotePort", m_NewSetup.RemotePort);
+ SetupStore("StreamPIDS", m_NewSetup.StreamPIDS);
+#if VDRVERSNUM >= 10300
+ SetupStore("StreamFilters", m_NewSetup.StreamFilters);
+#endif
+ SetupStore("SyncEPG", m_NewSetup.SyncEPG);
+
+ StreamdevClientSetup = m_NewSetup;
+
+ cStreamdevDevice::ReInit();
+}
+
diff --git a/client/setup.h b/client/setup.h
new file mode 100644
index 0000000..fe8e975
--- /dev/null
+++ b/client/setup.h
@@ -0,0 +1,39 @@
+/*
+ * $Id: setup.h,v 1.1 2004/12/30 22:44:03 lordjaxom Exp $
+ */
+
+#ifndef VDR_STREAMDEV_SETUPCLIENT_H
+#define VDR_STREAMDEV_SETUPCLIENT_H
+
+#include "common.h"
+
+struct cStreamdevClientSetup {
+ cStreamdevClientSetup(void);
+
+ bool SetupParse(const char *Name, const char *Value);
+
+ int StartClient;
+ char RemoteIp[20];
+ int RemotePort;
+ int StreamPIDS;
+#if VDRVERSNUM >= 10300
+ int StreamFilters;
+#endif
+ int SyncEPG;
+};
+
+extern cStreamdevClientSetup StreamdevClientSetup;
+
+class cStreamdevClientMenuSetupPage: public cStreamdevMenuSetupPage {
+private:
+ cStreamdevClientSetup m_NewSetup;
+
+protected:
+ virtual void Store(void);
+
+public:
+ cStreamdevClientMenuSetupPage(void);
+ virtual ~cStreamdevClientMenuSetupPage();
+};
+
+#endif // VDR_STREAMDEV_SETUPCLIENT_H
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;
+}
diff --git a/client/socket.h b/client/socket.h
new file mode 100644
index 0000000..d4f360a
--- /dev/null
+++ b/client/socket.h
@@ -0,0 +1,71 @@
+/*
+ * $Id: socket.h,v 1.1 2004/12/30 22:44:04 lordjaxom Exp $
+ */
+
+#ifndef VDR_STREAMDEV_CLIENT_CONNECTION_H
+#define VDR_STREAMDEV_CLIENT_CONNECTION_H
+
+#include <tools/socket.h>
+
+#include "common.h"
+
+#define CMD_LOCK cMutexLock CmdLock((cMutex*)&m_Mutex)
+
+class cRemoteRecordings;
+class cRemoteRecording;
+class cRemoteTimers;
+class cRemoteTimer;
+class cPES2TSRemux;
+
+class cClientSocket: public cTBSocket {
+private:
+ bool m_StreamPIDS;
+ cTBSocket *m_DataSockets[si_Count];
+ cMutex m_Mutex;
+
+protected:
+ /* Send Command, and return true if the command results in Expected.
+ Returns false on failure, setting errno appropriately if it has been
+ a system failure. If Expected is zero, returns immediately after
+ sending the command. */
+ bool Command(const cTBString &Command, uint Expected = 0,
+ uint TimeoutMs = 1500);
+
+ /* Fetch results from an ongoing Command called with Expected == 0. Returns
+ true if the response has the code Expected, returning an internal buffer
+ in the array pointer pointed to by Result. Returns false on failure,
+ setting errno appropriately if it has been a system failure. */
+ bool Expect(uint Expected, cTBString *Result = NULL, uint TimeoutMs = 1500);
+
+public:
+ cClientSocket(void);
+ virtual ~cClientSocket();
+
+ void Reset(void);
+
+ bool CheckConnection(void);
+ bool ProvidesChannel(const cChannel *Channel, int Priority);
+ bool CreateDataConnection(eSocketId Id);
+ bool SetChannelDevice(const cChannel *Channel);
+ bool SetPid(int Pid, bool On);
+#if VDRVERSNUM >= 10300
+ bool SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On);
+#endif
+ bool CloseDvr(void);
+ bool SynchronizeEPG(void);
+ bool LoadRecordings(cRemoteRecordings &Recordings);
+ bool StartReplay(const char *Filename);
+ bool AbortReplay(void);
+ bool DeleteRecording(cRemoteRecording *Recording);
+ bool LoadTimers(cRemoteTimers &Timers);
+ bool SaveTimer(cRemoteTimer *Old, cRemoteTimer &New);
+ bool DeleteTimer(cRemoteTimer *Timer);
+ bool SuspendServer(void);
+ bool Quit(void);
+
+ cTBSocket *DataSocket(eSocketId Id) const;
+};
+
+extern class cClientSocket ClientSocket;
+
+#endif // VDR_STREAMDEV_CLIENT_CONNECTION_H