MythTV  master
mythiowrapper.cpp
Go to the documentation of this file.
1 #if defined(_WIN32)
2 #include <windows.h>
3 #else
4 #include <dlfcn.h>
5 #endif
6 #include <cstdio>
7 #include <dirent.h>
8 #include <fcntl.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 
13 #include <QFile>
14 #include <QMap>
15 #include <QUrl>
16 #include <QReadWriteLock>
17 
18 #include "mythconfig.h"
19 #include "compat.h"
20 #include "mythcorecontext.h"
21 #include "mythlogging.h"
22 #include "remotefile.h"
23 #include "ringbuffer.h"
24 
25 #include "mythiowrapper.h"
26 
27 const int maxID = 1024 * 1024;
28 
29 QReadWriteLock m_fileWrapperLock;
30 QHash <int, RingBuffer *> m_ringbuffers;
31 QHash <int, RemoteFile *> m_remotefiles;
32 QHash <int, int> m_localfiles;
33 QHash <int, QString> m_filenames;
34 
35 QReadWriteLock m_dirWrapperLock;
36 QHash <int, QStringList> m_remotedirs;
37 QHash <int, int> m_remotedirPositions;
38 QHash <int, QString> m_dirnames;
39 QHash <int, DIR *> m_localdirs;
40 
41 class Callback
42 {
43  public:
44  Callback(void* object, callback_t callback)
45  : m_object(object), m_callback(callback) { }
46  void *m_object;
48 };
49 
51 QMultiHash<QString, Callback> m_fileOpenCallbacks;
52 
53 #define LOC QString("mythiowrapper: ")
54 
56 
57 extern "C" {
58 
59 static int getNextFileID(void)
60 {
61  int id = 100000;
62 
63  for (; id < maxID; ++id)
64  {
65  if ((!m_localfiles.contains(id)) &&
66  (!m_remotefiles.contains(id)) &&
67  (!m_ringbuffers.contains(id)))
68  break;
69  }
70 
71  if (id == maxID)
72  {
73  LOG(VB_GENERAL, LOG_ERR,
74  LOC + "getNextFileID(), too many files are open.");
75  }
76 
77  LOG(VB_FILE, LOG_DEBUG, LOC + QString("getNextFileID() = %1").arg(id));
78 
79  return id;
80 }
81 
82 void mythfile_open_register_callback(const char *pathname, void* object,
83  callback_t func)
84 {
85  m_callbackLock.lock();
86  QString path(pathname);
87  if (m_fileOpenCallbacks.contains(path))
88  {
89  // if we already have a callback registered for this path with this
90  // object then remove the callback and return (i.e. end callback)
91  QMutableHashIterator<QString,Callback> it(m_fileOpenCallbacks);
92  while (it.hasNext())
93  {
94  it.next();
95  if (object == it.value().m_object)
96  {
97  it.remove();
98  LOG(VB_PLAYBACK, LOG_INFO, LOC +
99  QString("Removing fileopen callback for %1").arg(path));
100  LOG(VB_PLAYBACK, LOG_INFO, LOC +
101  QString("%1 callbacks remaining")
102  .arg(m_fileOpenCallbacks.size()));
103  m_callbackLock.unlock();
104  return;
105  }
106  }
107  }
108 
109  Callback new_callback(object, func);
110  m_fileOpenCallbacks.insert(path, new_callback);
111  LOG(VB_PLAYBACK, LOG_INFO, LOC +
112  QString("Added fileopen callback for %1").arg(path));
113  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("%1 callbacks open")
114  .arg(m_fileOpenCallbacks.size()));
115 
116  m_callbackLock.unlock();
117 }
118 
119 int mythfile_check(int id)
120 {
121  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_check(%1)").arg(id));
122  int result = 0;
123 
124  m_fileWrapperLock.lockForWrite();
125  if ((m_localfiles.contains(id)) ||
126  (m_remotefiles.contains(id)) ||
127  (m_ringbuffers.contains(id)))
128  result = 1;
129  m_fileWrapperLock.unlock();
130 
131  return result;
132 }
133 
134 int mythfile_open(const char *pathname, int flags)
135 {
136  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_open('%1', %2)")
137  .arg(pathname).arg(flags));
138 
139  struct stat fileinfo {};
140  if (mythfile_stat(pathname, &fileinfo))
141  return -1;
142 
143  if (S_ISDIR( fileinfo.st_mode )) // libmythdvdnav tries to open() a dir
144  return errno = EISDIR, -1;
145 
146  int fileID = -1;
147  if (strncmp(pathname, "myth://", 7) != 0)
148  {
149  int lfd = open(pathname, flags);
150  if (lfd < 0)
151  return -1;
152 
153  m_fileWrapperLock.lockForWrite();
154  fileID = getNextFileID();
155  m_localfiles[fileID] = lfd;
156  m_filenames[fileID] = pathname;
157  m_fileWrapperLock.unlock();
158  }
159  else
160  {
161  RingBuffer *rb = nullptr;
162  RemoteFile *rf = nullptr;
163 
164  if ((fileinfo.st_size < 512) &&
165  (fileinfo.st_mtime < (time(nullptr) - 300)))
166  {
167  if (flags & O_WRONLY)
168  rf = new RemoteFile(pathname, true, false); // Writeable
169  else
170  rf = new RemoteFile(pathname, false, true); // Read-Only
171 
172  if (!rf)
173  return -1;
174  }
175  else
176  {
177  if (flags & O_WRONLY)
178  rb = RingBuffer::Create(
179  pathname, true, false,
180  RingBuffer::kDefaultOpenTimeout, true); // Writeable
181  else
182  rb = RingBuffer::Create(
183  pathname, false, true,
184  RingBuffer::kDefaultOpenTimeout, true); // Read-Only
185 
186  if (!rb)
187  return -1;
188 
189  rb->Start();
190  }
191 
192  m_fileWrapperLock.lockForWrite();
193  fileID = getNextFileID();
194 
195  if (rf)
196  m_remotefiles[fileID] = rf;
197  else if (rb)
198  m_ringbuffers[fileID] = rb;
199 
200  m_filenames[fileID] = pathname;
201  m_fileWrapperLock.unlock();
202  }
203 
204  m_callbackLock.lock();
205  if (!m_fileOpenCallbacks.isEmpty())
206  {
207  QString path(pathname);
208  QHashIterator<QString,Callback> it(m_fileOpenCallbacks);
209  while (it.hasNext())
210  {
211  it.next();
212  if (path.startsWith(it.key()))
213  it.value().m_callback(it.value().m_object);
214  }
215  }
216  m_callbackLock.unlock();
217 
218  return fileID;
219 }
220 
221 int mythfile_close(int fileID)
222 {
223  int result = -1;
224 
225  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_close(%1)").arg(fileID));
226 
227  m_fileWrapperLock.lockForRead();
228  if (m_ringbuffers.contains(fileID))
229  {
230  RingBuffer *rb = m_ringbuffers[fileID];
231  m_ringbuffers.remove(fileID);
232  delete rb;
233 
234  result = 0;
235  }
236  else if (m_remotefiles.contains(fileID))
237  {
238  RemoteFile *rf = m_remotefiles[fileID];
239  m_remotefiles.remove(fileID);
240  delete rf;
241 
242  result = 0;
243  }
244  else if (m_localfiles.contains(fileID))
245  {
246  close(m_localfiles[fileID]);
247  m_localfiles.remove(fileID);
248  result = 0;
249  }
250  m_fileWrapperLock.unlock();
251 
252  return result;
253 }
254 
255 #ifdef _WIN32
256 # undef lseek
257 # define lseek _lseeki64
258 # undef off_t
259 # define off_t off64_t
260 #endif
261 off_t mythfile_seek(int fileID, off_t offset, int whence)
262 {
263  off_t result = -1;
264 
265  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_seek(%1, %2, %3)")
266  .arg(fileID).arg(offset).arg(whence));
267 
268  m_fileWrapperLock.lockForRead();
269  if (m_ringbuffers.contains(fileID))
270  result = m_ringbuffers[fileID]->Seek(offset, whence);
271  else if (m_remotefiles.contains(fileID))
272  result = m_remotefiles[fileID]->Seek(offset, whence);
273  else if (m_localfiles.contains(fileID))
274  result = lseek(m_localfiles[fileID], offset, whence);
275  m_fileWrapperLock.unlock();
276 
277  return result;
278 }
279 
280 off_t mythfile_tell(int fileID)
281 {
282  off_t result = -1;
283 
284  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_tell(%1)").arg(fileID));
285 
286  m_fileWrapperLock.lockForRead();
287  if (m_ringbuffers.contains(fileID))
288  result = m_ringbuffers[fileID]->Seek(0, SEEK_CUR);
289  else if (m_remotefiles.contains(fileID))
290  result = m_remotefiles[fileID]->Seek(0, SEEK_CUR);
291  else if (m_localfiles.contains(fileID))
292  result = lseek(m_localfiles[fileID], 0, SEEK_CUR);
293  m_fileWrapperLock.unlock();
294 
295  return result;
296 }
297 #ifdef _WIN32
298 # undef lseek
299 # undef off_t
300 #endif
301 
302 ssize_t mythfile_read(int fileID, void *buf, size_t count)
303 {
304  ssize_t result = -1;
305 
306  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_read(%1, %2, %3)")
307  .arg(fileID) .arg((long long)buf).arg(count));
308 
309  m_fileWrapperLock.lockForRead();
310  if (m_ringbuffers.contains(fileID))
311  result = m_ringbuffers[fileID]->Read(buf, count);
312  else if (m_remotefiles.contains(fileID))
313  result = m_remotefiles[fileID]->Read(buf, count);
314  else if (m_localfiles.contains(fileID))
315  result = read(m_localfiles[fileID], buf, count);
316  m_fileWrapperLock.unlock();
317 
318  return result;
319 }
320 
321 ssize_t mythfile_write(int fileID, void *buf, size_t count)
322 {
323  ssize_t result = -1;
324 
325  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_write(%1, %2, %3)")
326  .arg(fileID) .arg((long long)buf).arg(count));
327 
328  m_fileWrapperLock.lockForRead();
329  if (m_ringbuffers.contains(fileID))
330  result = m_ringbuffers[fileID]->Write(buf, count);
331  else if (m_remotefiles.contains(fileID))
332  result = m_remotefiles[fileID]->Write(buf, count);
333  else if (m_localfiles.contains(fileID))
334  result = write(m_localfiles[fileID], buf, count);
335  m_fileWrapperLock.unlock();
336 
337  return result;
338 }
339 
340 int mythfile_stat(const char *path, struct stat *buf)
341 {
342  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_stat('%1', %2)")
343  .arg(path).arg((long long)buf));
344 
345  if (strncmp(path, "myth://", 7) == 0)
346  {
347  bool res = RemoteFile::Exists(path, buf);
348  if (res)
349  return 0;
350  }
351 
352  return stat(path, buf);
353 }
354 
355 int mythfile_stat_fd(int fileID, struct stat *buf)
356 {
357  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_stat_fd(%1, %2)")
358  .arg(fileID).arg((long long)buf));
359 
360  m_fileWrapperLock.lockForRead();
361  if (!m_filenames.contains(fileID))
362  {
363  m_fileWrapperLock.unlock();
364  return -1;
365  }
366  QString filename = m_filenames[fileID];
367  m_fileWrapperLock.unlock();
368 
369  return mythfile_stat(filename.toLocal8Bit().constData(), buf);
370 }
371 
372 /*
373  * This function exists for the use of dvd_reader.c, thus the return
374  * value of int instead of bool. C doesn't have a bool type.
375  */
376 int mythfile_exists(const char *path, const char *file)
377 {
378  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_exists('%1', '%2')")
379  .arg(path).arg(file));
380 
381  if (strncmp(path, "myth://", 7) == 0)
382  return RemoteFile::Exists(QString("%1/%2").arg(path).arg(file));
383 
384  return QFile::exists(QString("%1/%2").arg(path).arg(file));
385 }
386 
388 
389 static int getNextDirID(void)
390 {
391  int id = 100000;
392 
393  for (; id < maxID; ++id)
394  {
395  if (!m_localdirs.contains(id) && !m_remotedirs.contains(id))
396  break;
397  }
398 
399  if (id == maxID)
400  LOG(VB_GENERAL, LOG_ERR, "ERROR: mythiowrapper getNextDirID(), too "
401  "many files are open.");
402 
403  LOG(VB_FILE, LOG_DEBUG, LOC + QString("getNextDirID() = %1").arg(id));
404 
405  return id;
406 }
407 
408 int mythdir_check(int id)
409 {
410  LOG(VB_FILE, LOG_DEBUG, QString("mythdir_check(%1)").arg(id));
411  int result = 0;
412 
413  m_dirWrapperLock.lockForWrite();
414  if ((m_localdirs.contains(id)) ||
415  (m_remotedirs.contains(id)))
416  result = 1;
417  m_dirWrapperLock.unlock();
418 
419  return result;
420 }
421 
422 int mythdir_opendir(const char *dirname)
423 {
424  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_opendir(%1)").arg(dirname));
425 
426  int id = 0;
427  if (strncmp(dirname, "myth://", 7) != 0)
428  {
429  DIR *dir = opendir(dirname);
430  if (dir) {
431  m_dirWrapperLock.lockForWrite();
432  id = getNextDirID();
433  m_localdirs[id] = dir;
434  m_dirnames[id] = dirname;
435  m_dirWrapperLock.unlock();
436  }
437  }
438  else
439  {
440  QStringList list;
441  QUrl qurl(dirname);
442  QString storageGroup = qurl.userName();
443 
444  list.clear();
445 
446  if (storageGroup.isEmpty())
447  storageGroup = "Default";
448 
449  list << "QUERY_SG_GETFILELIST";
450  list << qurl.host();
451  list << storageGroup;
452 
453  QString path = qurl.path();
454  if (!qurl.fragment().isEmpty())
455  path += "#" + qurl.fragment();
456 
457  list << path;
458  list << "1";
459 
460  bool ok = gCoreContext->SendReceiveStringList(list);
461 
462  if ((!ok) ||
463  ((list.size() == 1) && (list[0] == "EMPTY LIST")))
464  list.clear();
465 
466  m_dirWrapperLock.lockForWrite();
467  id = getNextDirID();
468  m_remotedirs[id] = list;
469  m_remotedirPositions[id] = 0;
470  m_dirnames[id] = dirname;
471  m_dirWrapperLock.unlock();
472  }
473 
474  return id;
475 }
476 
477 int mythdir_closedir(int dirID)
478 {
479  int result = -1;
480 
481  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_closedir(%1)").arg(dirID));
482 
483  m_dirWrapperLock.lockForRead();
484  if (m_remotedirs.contains(dirID))
485  {
486  m_remotedirs.remove(dirID);
487  m_remotedirPositions.remove(dirID);
488  result = 0;
489  }
490  else if (m_localdirs.contains(dirID))
491  {
492  result = closedir(m_localdirs[dirID]);
493 
494  if (result == 0)
495  m_localdirs.remove(dirID);
496  }
497  m_dirWrapperLock.unlock();
498 
499  return result;
500 }
501 
502 char *mythdir_readdir(int dirID)
503 {
504  char *result = nullptr;
505 
506  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_readdir(%1)").arg(dirID));
507 
508  m_dirWrapperLock.lockForRead();
509  if (m_remotedirs.contains(dirID))
510  {
511  int pos = m_remotedirPositions[dirID];
512  if (m_remotedirs[dirID].size() >= (pos+1))
513  {
514  result = strdup(m_remotedirs[dirID][pos].toLocal8Bit().constData());
515  pos++;
516  m_remotedirPositions[dirID] = pos;
517  }
518  }
519  else if (m_localdirs.contains(dirID))
520  {
521  struct dirent *r = nullptr;
522  // glibc deprecated readdir_r in version 2.24,
523  // cppcheck-suppress readdirCalled
524  if ((r = readdir(m_localdirs[dirID])) != nullptr)
525  result = strdup(r->d_name);
526  }
527  m_dirWrapperLock.unlock();
528 
529  return result;
530 }
531 } // extern "C"
532 
534 
535 /* vim: set expandtab tabstop=4 shiftwidth=4: */
static const int kDefaultOpenTimeout
def write(text, progress=True)
Definition: mythburn.py:279
int mythfile_open(const char *pathname, int flags)
QMultiHash< QString, Callback > m_fileOpenCallbacks
char * mythdir_readdir(int dirID)
int mythfile_check(int id)
QReadWriteLock m_fileWrapperLock
QReadWriteLock m_dirWrapperLock
callback_t m_callback
QHash< int, QString > m_dirnames
#define lseek
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static RingBuffer * Create(const QString &xfilename, bool write, bool usereadahead=true, int timeout_ms=kDefaultOpenTimeout, bool stream_only=false)
Creates a RingBuffer instance.
Definition: ringbuffer.cpp:104
ssize_t mythfile_write(int fileID, void *buf, size_t count)
void mythfile_open_register_callback(const char *pathname, void *object, callback_t func)
#define LOC
#define off_t
QHash< int, QString > m_filenames
int mythdir_closedir(int dirID)
def read(device=None, features=[])
Definition: disc.py:35
int mythdir_opendir(const char *dirname)
QMutex m_callbackLock
static int getNextFileID(void)
int mythdir_check(int id)
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
QHash< int, DIR * > m_localdirs
QHash< int, int > m_remotedirPositions
#define close
Definition: compat.h:16
static int getNextDirID(void)
void * m_object
off_t mythfile_seek(int fileID, off_t offset, int whence)
const int maxID
QHash< int, RingBuffer * > m_ringbuffers
int mythfile_exists(const char *path, const char *file)
ssize_t mythfile_read(int fileID, void *buf, size_t count)
void Start(void)
Starts the read-ahead thread.
Definition: ringbuffer.cpp:653
Callback(void *object, callback_t callback)
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:461
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
void(* callback_t)(void *)
Definition: mythiowrapper.h:20
off_t mythfile_tell(int fileID)
int mythfile_stat_fd(int fileID, struct stat *buf)
QHash< int, QStringList > m_remotedirs
QHash< int, RemoteFile * > m_remotefiles
Implements a file/stream reader/writer.
int mythfile_close(int fileID)
QHash< int, int > m_localfiles
int mythfile_stat(const char *path, struct stat *buf)