1 #include <lib/base/filepush.h>
2 #include <lib/base/eerror.h>
3 #include <lib/base/nconfig.h>
11 #include <sys/types.h>
17 #define MAJORMMCBLK 179
18 #define LIMIT_FILESIZE_NOHDD 2*1024*1024*1024LL // 2GBytes
20 //FILE *f = fopen("/log.ts", "wb");
21 static bool g_is_diskfull = false;
23 eFilePushThread::eFilePushThread(int io_prio_class, int io_prio_level, int blocksize)
24 :prio_class(io_prio_class), prio(io_prio_level), m_messagepump(eApp, 0)
28 m_send_pvr_commit = 0;
30 m_blocksize = blocksize;
33 CONNECT(m_messagepump.recv_msg, eFilePushThread::recvEvent);
34 m_is_timeshift = false;
35 m_hdd_connected = true;
38 static void signal_handler(int x)
42 void eFilePushThread::thread()
44 setIoPrio(prio_class, prio);
47 size_t bytes_read = 0;
49 off_t current_span_offset = 0;
50 size_t current_span_remaining = 0;
52 size_t written_since_last_sync = 0;
56 struct dirent *tsdir_entry;
57 tsdir_info = opendir("/sys/block");
58 if (tsdir_info != NULL) {
59 m_hdd_connected = false;
60 while (tsdir_entry = readdir(tsdir_info)) {
61 if (strncmp(tsdir_entry->d_name, "sd", 2) == 0) {
62 eDebug("HDD found: %s", tsdir_entry->d_name);
63 m_hdd_connected = true;
70 defaultTSPath(m_is_timeshift);
72 struct stat tspath_st;
73 if (stat(m_tspath.c_str(), &tspath_st) == 0) {
74 if (major(tspath_st.st_dev) == MAJORSD_) {
75 eDebug("%s location on HDD!", m_tspath.c_str());
76 m_hdd_connected = true;
77 } else if (major(tspath_st.st_dev) == MAJORMMCBLK) {
78 eDebug("%s location on eMMC!", m_tspath.c_str());
79 m_hdd_connected = false;
81 eDebug("%s location on other device", m_tspath.c_str());
84 eDebug("stat failed!");
88 eDebug("FILEPUSH THREAD START");
90 /* we set the signal to not restart syscalls, so we can detect our signal. */
92 act.sa_handler = signal_handler; // no, SIG_IGN doesn't do it. we want to receive the -EINTR
94 sigaction(SIGUSR1, &act, 0);
98 /* m_stop must be evaluated after each syscall. */
101 /* first try flushing the bufptr */
102 if (m_buf_start != m_buf_end)
104 /* filterRecordData wants to work on multiples of blocksize.
105 if it returns a negative result, it means that this many bytes should be skipped
106 *in front* of the buffer. Then, it will be called again. with the newer, shorter buffer.
107 if filterRecordData wants to skip more data then currently available, it must do that internally.
108 Skipped bytes will also not be output.
110 if it returns a positive result, that means that only these many bytes should be used
113 In either case, current_span_remaining is given as a reference and can be modified. (Of course it
114 doesn't make sense to decrement it to a non-zero value unless you return 0 because that would just
115 skip some data). This is probably a very special application for fast-forward, where the current
116 span is to be cancelled after a complete iframe has been output.
118 we always call filterRecordData with our full buffer (otherwise we couldn't easily strip from the end)
120 we filter data only once, of course, but it might not get immediately written.
121 that's what m_filter_end is for - it points to the start of the unfiltered data.
128 filter_res = filterRecordData(m_buffer + m_filter_end, m_buf_end - m_filter_end, current_span_remaining);
132 eDebug("[eFilePushThread] filterRecordData re-syncs and skips %d bytes", -filter_res);
133 m_buf_start = m_filter_end + -filter_res; /* this will also drop unwritten data */
134 ASSERT(m_buf_start <= m_buf_end); /* otherwise filterRecordData skipped more data than available. */
135 continue; /* try again */
138 /* adjust end of buffer to strip dropped tail bytes */
139 m_buf_end = m_filter_end + filter_res;
140 /* mark data as filtered. */
141 m_filter_end = m_buf_end;
144 ASSERT(m_filter_end == m_buf_end);
146 if (m_buf_start == m_buf_end)
149 /* now write out data. it will be 'aligned' (according to filterRecordData).
150 absolutely forbidden is to return EINTR and consume a non-aligned number of bytes.
152 int w = write(m_fd_dest, m_buffer + m_buf_start, m_buf_end - m_buf_start);
153 // fwrite(m_buffer + m_buf_start, 1, m_buf_end - m_buf_start, f);
154 // eDebug("wrote %d bytes", w);
157 if (w < 0 && (errno == EINTR || errno == EAGAIN || errno == EBUSY))
159 eDebug("eFilePushThread WRITE ERROR");
160 sendEvent(evtWriteError);
163 if (statfs(m_tspath.c_str(), &fs) < 0) {
164 eDebug("statfs failed!");
166 if ((off_t)fs.f_bavail < 1) {
167 eDebug("not enough diskspace!");
168 g_is_diskfull = true;
171 // ... we would stop the thread
174 written_since_last_sync += w;
176 if (written_since_last_sync >= 512*1024)
178 int toflush = written_since_last_sync > 2*1024*1024 ?
179 2*1024*1024 : written_since_last_sync &~ 4095; // write max 2MB at once
180 dest_pos = lseek(m_fd_dest, 0, SEEK_CUR);
182 posix_fadvise(m_fd_dest, dest_pos, toflush, POSIX_FADV_DONTNEED);
183 written_since_last_sync -= toflush;
186 // printf("FILEPUSH: wrote %d bytes\n", w);
191 if (!m_hdd_connected) {
192 struct stat limit_filesize;
193 if (fstat(m_fd_dest, &limit_filesize) == 0) {
194 if (limit_filesize.st_size > LIMIT_FILESIZE_NOHDD) {
195 eDebug("eFilePushThread %lld > %lld LIMIT FILESIZE", limit_filesize.st_size, LIMIT_FILESIZE_NOHDD);
196 sendEvent(evtWriteError);
198 g_is_diskfull = true;
204 /* now fill our buffer. */
206 if (m_sg && !current_span_remaining)
208 m_sg->getNextSourceSpan(m_current_position, bytes_read, current_span_offset, current_span_remaining);
209 ASSERT(!(current_span_remaining % m_blocksize));
210 m_current_position = current_span_offset;
214 size_t maxread = sizeof(m_buffer);
216 /* if we have a source span, don't read past the end */
217 if (m_sg && maxread > current_span_remaining)
218 maxread = current_span_remaining;
220 /* align to blocksize */
221 maxread -= maxread % m_blocksize;
228 m_buf_end = m_source->read(m_current_position, m_buffer, maxread);
233 if (errno == EINTR || errno == EBUSY || errno == EAGAIN)
235 if (errno == EOVERFLOW)
237 eWarning("OVERFLOW while recording");
240 eDebug("eFilePushThread *read error* (%m) - not yet handled");
243 /* a read might be mis-aligned in case of a short read. */
244 int d = m_buf_end % m_blocksize;
250 /* on EOF, try COMMITting once. */
251 if (m_send_pvr_commit)
256 switch (poll(&pfd, 1, 250)) // wait for 250ms
259 eDebug("wait for driver eof timeout");
262 eDebug("wait for driver eof ok");
265 eDebug("wait for driver eof aborted by signal");
270 /* in stream_mode, we are sending EOF events
271 over and over until somebody responds.
273 in stream_mode, think of evtEOF as "buffer underrun occured". */
278 eDebug("reached EOF, but we are in stream mode. delaying 1 second.");
285 m_current_position += m_buf_end;
286 bytes_read += m_buf_end;
288 current_span_remaining -= m_buf_end;
290 // printf("FILEPUSH: read %d bytes\n", m_buf_end);
293 sendEvent(evtUser+3);
294 g_is_diskfull = false;
297 fdatasync(m_fd_dest);
299 eDebug("FILEPUSH THREAD STOP");
302 void eFilePushThread::start(int fd, int fd_dest)
304 eRawFile *f = new eRawFile();
305 ePtr<iTsSource> source = f;
307 start(source, fd_dest);
310 int eFilePushThread::start(const char *file, int fd_dest)
312 eRawFile *f = new eRawFile();
313 ePtr<iTsSource> source = f;
314 if (f->open(file) < 0)
316 start(source, fd_dest);
320 void eFilePushThread::start(ePtr<iTsSource> &source, int fd_dest)
324 m_current_position = 0;
328 void eFilePushThread::stop()
330 /* if we aren't running, don't bother stopping. */
336 eDebug("stopping thread."); /* just do it ONCE. it won't help to do this more than once. */
341 void eFilePushThread::pause()
346 void eFilePushThread::resume()
352 void eFilePushThread::flush()
354 m_buf_start = m_buf_end = m_filter_end = 0;
357 void eFilePushThread::enablePVRCommit(int s)
359 m_send_pvr_commit = s;
362 void eFilePushThread::setStreamMode(int s)
367 void eFilePushThread::setTimeshift(bool s)
372 void eFilePushThread::setTSPath(const std::string s)
377 void eFilePushThread::setScatterGather(iFilePushScatterGather *sg)
382 void eFilePushThread::sendEvent(int evt)
384 m_messagepump.send(evt);
387 void eFilePushThread::recvEvent(const int &evt)
392 void eFilePushThread::defaultTSPath(bool s)
397 if (ePythonConfigQuery::getConfigValue("config.usage.timeshift_path", tspath) == -1)
398 eDebug("could not query ts path from config");
400 if (ePythonConfigQuery::getConfigValue("config.usage.instantrec_path", tspath) == -1) {
401 eDebug("could not query ts path from config");
403 if (tspath == "<default>") {
404 if (ePythonConfigQuery::getConfigValue("config.usage.default_path", tspath) == -1)
405 eDebug("could not query ts path from config");
406 } else if (tspath == "<current>") {
407 if (ePythonConfigQuery::getConfigValue("config.movielist.last_videodir", tspath) == -1)
408 eDebug("could not query ts path from config");
409 } else if (tspath == "<timer>") {
410 if (ePythonConfigQuery::getConfigValue("config.movielist.last_timer_videodir", tspath) == -1)
411 eDebug("could not query ts path from config");
422 int eFilePushThread::filterRecordData(const unsigned char *data, int len, size_t ¤t_span_remaining)