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