diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/assembler.c | 125 | ||||
-rw-r--r-- | client/assembler.h | 32 | ||||
-rw-r--r-- | client/device.c | 185 | ||||
-rw-r--r-- | client/device.h | 58 | ||||
-rw-r--r-- | client/filter.c | 141 | ||||
-rw-r--r-- | client/filter.h | 64 | ||||
-rw-r--r-- | client/menu.c | 1047 | ||||
-rw-r--r-- | client/menu.h | 144 | ||||
-rw-r--r-- | client/remote.c | 475 | ||||
-rw-r--r-- | client/remote.h | 132 | ||||
-rw-r--r-- | client/setup.c | 83 | ||||
-rw-r--r-- | client/setup.h | 39 | ||||
-rw-r--r-- | client/socket.c | 591 | ||||
-rw-r--r-- | client/socket.h | 71 |
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 |