CPPMyth
Library to interoperate with MythTV server
mythrecordingplayback.cpp
1 /*
2  * Copyright (C) 2014 Jean-Luc Barriere
3  *
4  * This Program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This Program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; see the file COPYING. If not, write to
16  * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
17  * MA 02110-1301 USA
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21 
22 #include "mythrecordingplayback.h"
23 #include "private/debug.h"
24 #include "private/os/threads/mutex.h"
25 #include "private/builtin.h"
26 
27 #include <limits>
28 #include <cstdio>
29 
30 using namespace Myth;
31 
36 
37 RecordingPlayback::RecordingPlayback(EventHandler& handler)
38 : ProtoPlayback(handler.GetServer(), handler.GetPort()), EventSubscriber()
39 , m_eventHandler(handler)
40 , m_eventSubscriberId(0)
41 , m_transfer(NULL)
42 , m_recording(NULL)
43 , m_readAhead(false)
44 , m_chunk(MYTH_RECORDING_CHUNK_SIZE)
45 {
46  m_buffer.pos = 0;
47  m_buffer.len = 0;
48  m_buffer.data = new unsigned char[m_chunk];
49  m_eventSubscriberId = m_eventHandler.CreateSubscription(this);
50  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_UPDATE_FILE_SIZE);
51  Open();
52 }
53 
54 RecordingPlayback::RecordingPlayback(const std::string& server, unsigned port)
55 : ProtoPlayback(server, port), EventSubscriber()
56 , m_eventHandler(server, port)
57 , m_eventSubscriberId(0)
58 , m_transfer(NULL)
59 , m_recording(NULL)
60 , m_readAhead(false)
61 , m_chunk(MYTH_RECORDING_CHUNK_SIZE)
62 {
63  m_buffer.pos = 0;
64  m_buffer.len = 0;
65  m_buffer.data = new unsigned char[m_chunk];
66  // Private handler will be stopped and closed by destructor.
67  m_eventSubscriberId = m_eventHandler.CreateSubscription(this);
68  m_eventHandler.SubscribeForEvent(m_eventSubscriberId, EVENT_UPDATE_FILE_SIZE);
69  Open();
70 }
71 
72 RecordingPlayback::~RecordingPlayback()
73 {
74  if (m_eventSubscriberId)
75  m_eventHandler.RevokeSubscription(m_eventSubscriberId);
76  Close();
77  delete[] m_buffer.data;
78 }
79 
80 bool RecordingPlayback::Open()
81 {
82  // Begin critical section
83  OS::CLockGuard lock(*m_mutex);
84  if (ProtoPlayback::IsOpen())
85  return true;
86  if (ProtoPlayback::Open())
87  {
88  if (!m_eventHandler.IsRunning())
89  m_eventHandler.Start();
90  return true;
91  }
92  return false;
93 }
94 
95 void RecordingPlayback::Close()
96 {
97  // Begin critical section
98  OS::CLockGuard lock(*m_mutex);
99  CloseTransfer();
100  ProtoPlayback::Close();
101 }
102 
103 bool RecordingPlayback::OpenTransfer(ProgramPtr recording)
104 {
105  // Begin critical section
106  OS::CLockGuard lock(*m_mutex);
107  if (!ProtoPlayback::IsOpen())
108  return false;
109  CloseTransfer();
110  if (recording)
111  {
112  m_transfer.reset(new ProtoTransfer(m_server, m_port, recording->fileName, recording->recording.storageGroup));
113  if (m_transfer->Open())
114  {
115  m_recording.swap(recording);
116  m_recording->fileSize = m_transfer->GetSize();
117  return true;
118  }
119  m_transfer.reset();
120  }
121  return false;
122 }
123 
124 void RecordingPlayback::CloseTransfer()
125 {
126  // Begin critical section
127  OS::CLockGuard lock(*m_mutex);
128  m_recording.reset();
129  if (m_transfer)
130  {
131  TransferDone(*m_transfer);
132  m_transfer->Close();
133  m_transfer.reset();
134  }
135 }
136 
137 bool RecordingPlayback::TransferIsOpen()
138 {
139  ProtoTransferPtr transfer(m_transfer);
140  if (transfer)
141  return ProtoPlayback::TransferIsOpen(*transfer);
142  return false;
143 }
144 
145 void RecordingPlayback::SetChunk(unsigned size)
146 {
147  if (size < MYTH_RECORDING_CHUNK_MIN)
148  size = MYTH_RECORDING_CHUNK_MIN;
149  else if (size > MYTH_RECORDING_CHUNK_MAX)
150  size = MYTH_RECORDING_CHUNK_MAX;
151 
152  m_buffer.pos = m_buffer.len = 0;
153  delete[] m_buffer.data;
154  m_buffer.data = new unsigned char[size];
155  m_chunk = size;
156 }
157 
158 int64_t RecordingPlayback::GetSize() const
159 {
160  ProtoTransferPtr transfer(m_transfer);
161  if (transfer)
162  return transfer->GetSize();
163  return 0;
164 }
165 
166 int RecordingPlayback::Read(void* buffer, unsigned n)
167 {
168  int c = 0;
169  bool refill = true;
170  for (;;)
171  {
172  // all requested data are in the buffer
173  if (m_buffer.len >= n)
174  {
175  memcpy(static_cast<unsigned char*>(buffer) + c, m_buffer.data + m_buffer.pos, n);
176  c += n;
177  m_buffer.pos += n;
178  m_buffer.len -= n;
179  return c;
180  }
181  // fill with the rest of data before read a new chunk
182  if (m_buffer.len > 0)
183  {
184  memcpy(static_cast<unsigned char*>(buffer) + c, m_buffer.data + m_buffer.pos, m_buffer.len);
185  c += m_buffer.len;
186  n -= m_buffer.len;
187  m_buffer.len = 0;
188  }
189  if (!refill)
190  break;
191  m_buffer.pos = 0;
192  int r = _read(m_buffer.data, m_chunk);
193  if (r < 0)
194  return -1;
195  m_buffer.len += r;
196  refill = false; // won't read again
197  }
198  return c;
199 }
200 
201 int RecordingPlayback::_read(void *buffer, unsigned n)
202 {
203  ProtoTransferPtr transfer(m_transfer);
204  if (transfer)
205  {
206  if (!m_readAhead)
207  {
208  int64_t s = transfer->GetRemaining(); // Acceptable block size
209  if (s > 0)
210  {
211  if (s < (int64_t)n)
212  n = (unsigned)s;
213  // Request block data from transfer socket
214  return TransferRequestBlock(*transfer, buffer, n);
215  }
216  return 0;
217  }
218  else
219  {
220  // Request block data from transfer socket
221  return TransferRequestBlock(*transfer, buffer, n);
222  }
223  }
224  return -1;
225 }
226 
227 int64_t RecordingPlayback::Seek(int64_t offset, WHENCE_t whence)
228 {
229  if (whence == WHENCE_CUR)
230  {
231  if (offset == 0)
232  {
233  int64_t p = _seek(offset, whence);
234  // it returns the current position of the first byte in buffer
235  return (p >= m_buffer.len ? p - m_buffer.len : p);
236  }
237  // rebase to the first position in the buffer
238  offset -= m_buffer.len;
239  }
240  m_buffer.len = 0; // clear data in buffer
241  return _seek(offset, whence);
242 }
243 
244 int64_t RecordingPlayback::_seek(int64_t offset, WHENCE_t whence)
245 {
246  ProtoTransferPtr transfer(m_transfer);
247  if (transfer)
248  return TransferSeek(*transfer, offset, whence);
249  return -1;
250 }
251 
252 int64_t RecordingPlayback::GetPosition() const
253 {
254  ProtoTransferPtr transfer(m_transfer);
255  if (transfer)
256  {
257  // it returns the current position of first byte in buffer
258  return transfer->GetPosition() - m_buffer.len;
259  }
260  return 0;
261 }
262 
263 void RecordingPlayback::HandleBackendMessage(EventMessagePtr msg)
264 {
265  // First of all i hold shared resources using copies
266  ProgramPtr recording(m_recording);
267  ProtoTransferPtr transfer(m_transfer);
268  switch (msg->event)
269  {
270  case EVENT_UPDATE_FILE_SIZE:
271  if (msg->subject.size() >= 3 && recording && transfer)
272  {
273  int64_t newsize;
274  // Message contains chanid + starttime as recorded key
275  if (msg->subject.size() >= 4)
276  {
277  uint32_t chanid;
278  time_t startts;
279  if (string_to_uint32(msg->subject[1].c_str(), &chanid)
280  || string_to_time(msg->subject[2].c_str(), &startts)
281  || recording->channel.chanId != chanid
282  || recording->recording.startTs != startts
283  || string_to_int64(msg->subject[3].c_str(), &newsize))
284  break;
285  }
286  // Message contains recordedid as key
287  else
288  {
289  uint32_t recordedid;
290  if (string_to_uint32(msg->subject[1].c_str(), &recordedid)
291  || recording->recording.recordedId != recordedid
292  || string_to_int64(msg->subject[2].c_str(), &newsize))
293  break;
294  }
295  // The file grows. Allow reading ahead
296  m_readAhead = true;
297  transfer->SetSize(newsize);
298  recording->fileSize = newsize;
299  DBG(DBG_DEBUG, "%s: (%d) %s %" PRIi64 "\n", __FUNCTION__,
300  msg->event, recording->fileName.c_str(), newsize);
301  }
302  break;
303  //case EVENT_HANDLER_STATUS:
304  // if (msg->subject[0] == EVENTHANDLER_DISCONNECTED)
305  // closeTransfer();
306  // break;
307  default:
308  break;
309  }
310 }
This is the main namespace that encloses all public classes.
Definition: mythcontrol.h:29