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  {
179  rb = RingBuffer::Create(
180  pathname, true, false,
181  RingBuffer::kDefaultOpenTimeout, true); // Writeable
182  }
183  else
184  {
185  rb = RingBuffer::Create(
186  pathname, false, true,
187  RingBuffer::kDefaultOpenTimeout, true); // Read-Only
188  }
189 
190  if (!rb)
191  return -1;
192 
193  rb->Start();
194  }
195 
196  m_fileWrapperLock.lockForWrite();
197  fileID = getNextFileID();
198 
199  if (rf)
200  m_remotefiles[fileID] = rf;
201  else if (rb)
202  m_ringbuffers[fileID] = rb;
203 
204  m_filenames[fileID] = pathname;
205  m_fileWrapperLock.unlock();
206  }
207 
208  m_callbackLock.lock();
209  if (!m_fileOpenCallbacks.isEmpty())
210  {
211  QString path(pathname);
212  QHashIterator<QString,Callback> it(m_fileOpenCallbacks);
213  while (it.hasNext())
214  {
215  it.next();
216  if (path.startsWith(it.key()))
217  it.value().m_callback(it.value().m_object);
218  }
219  }
220  m_callbackLock.unlock();
221 
222  return fileID;
223 }
224 
225 int mythfile_close(int fileID)
226 {
227  int result = -1;
228 
229  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_close(%1)").arg(fileID));
230 
231  m_fileWrapperLock.lockForRead();
232  if (m_ringbuffers.contains(fileID))
233  {
234  RingBuffer *rb = m_ringbuffers[fileID];
235  m_ringbuffers.remove(fileID);
236  delete rb;
237 
238  result = 0;
239  }
240  else if (m_remotefiles.contains(fileID))
241  {
242  RemoteFile *rf = m_remotefiles[fileID];
243  m_remotefiles.remove(fileID);
244  delete rf;
245 
246  result = 0;
247  }
248  else if (m_localfiles.contains(fileID))
249  {
250  close(m_localfiles[fileID]);
251  m_localfiles.remove(fileID);
252  result = 0;
253  }
254  m_fileWrapperLock.unlock();
255 
256  return result;
257 }
258 
259 #ifdef _WIN32
260 # undef lseek
261 # define lseek _lseeki64
262 # undef off_t
263 # define off_t off64_t
264 #endif
265 off_t mythfile_seek(int fileID, off_t offset, int whence)
266 {
267  off_t result = -1;
268 
269  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_seek(%1, %2, %3)")
270  .arg(fileID).arg(offset).arg(whence));
271 
272  m_fileWrapperLock.lockForRead();
273  if (m_ringbuffers.contains(fileID))
274  result = m_ringbuffers[fileID]->Seek(offset, whence);
275  else if (m_remotefiles.contains(fileID))
276  result = m_remotefiles[fileID]->Seek(offset, whence);
277  else if (m_localfiles.contains(fileID))
278  result = lseek(m_localfiles[fileID], offset, whence);
279  m_fileWrapperLock.unlock();
280 
281  return result;
282 }
283 
284 off_t mythfile_tell(int fileID)
285 {
286  off_t result = -1;
287 
288  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_tell(%1)").arg(fileID));
289 
290  m_fileWrapperLock.lockForRead();
291  if (m_ringbuffers.contains(fileID))
292  result = m_ringbuffers[fileID]->Seek(0, SEEK_CUR);
293  else if (m_remotefiles.contains(fileID))
294  result = m_remotefiles[fileID]->Seek(0, SEEK_CUR);
295  else if (m_localfiles.contains(fileID))
296  result = lseek(m_localfiles[fileID], 0, SEEK_CUR);
297  m_fileWrapperLock.unlock();
298 
299  return result;
300 }
301 #ifdef _WIN32
302 # undef lseek
303 # undef off_t
304 #endif
305 
306 ssize_t mythfile_read(int fileID, void *buf, size_t count)
307 {
308  ssize_t result = -1;
309 
310  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_read(%1, %2, %3)")
311  .arg(fileID) .arg((long long)buf).arg(count));
312 
313  m_fileWrapperLock.lockForRead();
314  if (m_ringbuffers.contains(fileID))
315  result = m_ringbuffers[fileID]->Read(buf, count);
316  else if (m_remotefiles.contains(fileID))
317  result = m_remotefiles[fileID]->Read(buf, count);
318  else if (m_localfiles.contains(fileID))
319  result = read(m_localfiles[fileID], buf, count);
320  m_fileWrapperLock.unlock();
321 
322  return result;
323 }
324 
325 ssize_t mythfile_write(int fileID, void *buf, size_t count)
326 {
327  ssize_t result = -1;
328 
329  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythfile_write(%1, %2, %3)")
330  .arg(fileID) .arg((long long)buf).arg(count));
331 
332  m_fileWrapperLock.lockForRead();
333  if (m_ringbuffers.contains(fileID))
334  result = m_ringbuffers[fileID]->Write(buf, count);
335  else if (m_remotefiles.contains(fileID))
336  result = m_remotefiles[fileID]->Write(buf, count);
337  else if (m_localfiles.contains(fileID))
338  result = write(m_localfiles[fileID], buf, count);
339  m_fileWrapperLock.unlock();
340 
341  return result;
342 }
343 
344 int mythfile_stat(const char *path, struct stat *buf)
345 {
346  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_stat('%1', %2)")
347  .arg(path).arg((long long)buf));
348 
349  if (strncmp(path, "myth://", 7) == 0)
350  {
351  bool res = RemoteFile::Exists(path, buf);
352  if (res)
353  return 0;
354  }
355 
356  return stat(path, buf);
357 }
358 
359 int mythfile_stat_fd(int fileID, struct stat *buf)
360 {
361  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_stat_fd(%1, %2)")
362  .arg(fileID).arg((long long)buf));
363 
364  m_fileWrapperLock.lockForRead();
365  if (!m_filenames.contains(fileID))
366  {
367  m_fileWrapperLock.unlock();
368  return -1;
369  }
370  QString filename = m_filenames[fileID];
371  m_fileWrapperLock.unlock();
372 
373  return mythfile_stat(filename.toLocal8Bit().constData(), buf);
374 }
375 
376 /*
377  * This function exists for the use of dvd_reader.c, thus the return
378  * value of int instead of bool. C doesn't have a bool type.
379  */
380 int mythfile_exists(const char *path, const char *file)
381 {
382  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_exists('%1', '%2')")
383  .arg(path).arg(file));
384 
385  if (strncmp(path, "myth://", 7) == 0)
386  return RemoteFile::Exists(QString("%1/%2").arg(path).arg(file));
387 
388  return QFile::exists(QString("%1/%2").arg(path).arg(file));
389 }
390 
392 
393 static int getNextDirID(void)
394 {
395  int id = 100000;
396 
397  for (; id < maxID; ++id)
398  {
399  if (!m_localdirs.contains(id) && !m_remotedirs.contains(id))
400  break;
401  }
402 
403  if (id == maxID)
404  LOG(VB_GENERAL, LOG_ERR, "ERROR: mythiowrapper getNextDirID(), too "
405  "many files are open.");
406 
407  LOG(VB_FILE, LOG_DEBUG, LOC + QString("getNextDirID() = %1").arg(id));
408 
409  return id;
410 }
411 
412 int mythdir_check(int id)
413 {
414  LOG(VB_FILE, LOG_DEBUG, QString("mythdir_check(%1)").arg(id));
415  int result = 0;
416 
417  m_dirWrapperLock.lockForWrite();
418  if ((m_localdirs.contains(id)) ||
419  (m_remotedirs.contains(id)))
420  result = 1;
421  m_dirWrapperLock.unlock();
422 
423  return result;
424 }
425 
426 int mythdir_opendir(const char *dirname)
427 {
428  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_opendir(%1)").arg(dirname));
429 
430  int id = 0;
431  if (strncmp(dirname, "myth://", 7) != 0)
432  {
433  DIR *dir = opendir(dirname);
434  if (dir) {
435  m_dirWrapperLock.lockForWrite();
436  id = getNextDirID();
437  m_localdirs[id] = dir;
438  m_dirnames[id] = dirname;
439  m_dirWrapperLock.unlock();
440  }
441  }
442  else
443  {
444  QStringList list;
445  QUrl qurl(dirname);
446  QString storageGroup = qurl.userName();
447 
448  list.clear();
449 
450  if (storageGroup.isEmpty())
451  storageGroup = "Default";
452 
453  list << "QUERY_SG_GETFILELIST";
454  list << qurl.host();
455  list << storageGroup;
456 
457  QString path = qurl.path();
458  if (!qurl.fragment().isEmpty())
459  path += "#" + qurl.fragment();
460 
461  list << path;
462  list << "1";
463 
464  bool ok = gCoreContext->SendReceiveStringList(list);
465 
466  if ((!ok) ||
467  ((list.size() == 1) && (list[0] == "EMPTY LIST")))
468  list.clear();
469 
470  m_dirWrapperLock.lockForWrite();
471  id = getNextDirID();
472  m_remotedirs[id] = list;
473  m_remotedirPositions[id] = 0;
474  m_dirnames[id] = dirname;
475  m_dirWrapperLock.unlock();
476  }
477 
478  return id;
479 }
480 
481 int mythdir_closedir(int dirID)
482 {
483  int result = -1;
484 
485  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_closedir(%1)").arg(dirID));
486 
487  m_dirWrapperLock.lockForRead();
488  if (m_remotedirs.contains(dirID))
489  {
490  m_remotedirs.remove(dirID);
491  m_remotedirPositions.remove(dirID);
492  result = 0;
493  }
494  else if (m_localdirs.contains(dirID))
495  {
496  result = closedir(m_localdirs[dirID]);
497 
498  if (result == 0)
499  m_localdirs.remove(dirID);
500  }
501  m_dirWrapperLock.unlock();
502 
503  return result;
504 }
505 
506 char *mythdir_readdir(int dirID)
507 {
508  char *result = nullptr;
509 
510  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_readdir(%1)").arg(dirID));
511 
512  m_dirWrapperLock.lockForRead();
513  if (m_remotedirs.contains(dirID))
514  {
515  int pos = m_remotedirPositions[dirID];
516  if (m_remotedirs[dirID].size() >= (pos+1))
517  {
518  result = strdup(m_remotedirs[dirID][pos].toLocal8Bit().constData());
519  pos++;
520  m_remotedirPositions[dirID] = pos;
521  }
522  }
523  else if (m_localdirs.contains(dirID))
524  {
525  struct dirent *r = nullptr;
526  // glibc deprecated readdir_r in version 2.24,
527  // cppcheck-suppress readdirCalled
528  if ((r = readdir(m_localdirs[dirID])) != nullptr)
529  result = strdup(r->d_name);
530  }
531  m_dirWrapperLock.unlock();
532 
533  return result;
534 }
535 } // extern "C"
536 
538 
539 /* vim: set expandtab tabstop=4 shiftwidth=4: */
static const int kDefaultOpenTimeout
def write(text, progress=True)
Definition: mythburn.py:308
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:460
#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)