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))
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 int mythfile_exists(const char *path, const char *file)
375 {
376  LOG(VB_FILE, LOG_DEBUG, QString("mythfile_exists('%1', '%2')")
377  .arg(path).arg(file));
378 
379  if (!strncmp(path, "myth://", 7))
380  return RemoteFile::Exists(QString("%1/%2").arg(path).arg(file));
381 
382  return QFile::exists(QString("%1/%2").arg(path).arg(file));
383 }
384 
386 
387 static int getNextDirID(void)
388 {
389  int id = 100000;
390 
391  for (; id < maxID; ++id)
392  {
393  if (!m_localdirs.contains(id) && !m_remotedirs.contains(id))
394  break;
395  }
396 
397  if (id == maxID)
398  LOG(VB_GENERAL, LOG_ERR, "ERROR: mythiowrapper getNextDirID(), too "
399  "many files are open.");
400 
401  LOG(VB_FILE, LOG_DEBUG, LOC + QString("getNextDirID() = %1").arg(id));
402 
403  return id;
404 }
405 
406 int mythdir_check(int id)
407 {
408  LOG(VB_FILE, LOG_DEBUG, QString("mythdir_check(%1)").arg(id));
409  int result = 0;
410 
411  m_dirWrapperLock.lockForWrite();
412  if (m_localdirs.contains(id))
413  result = 1;
414  else if (m_remotedirs.contains(id))
415  result = 1;
416  m_dirWrapperLock.unlock();
417 
418  return result;
419 }
420 
421 int mythdir_opendir(const char *dirname)
422 {
423  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_opendir(%1)").arg(dirname));
424 
425  int id = 0;
426  if (strncmp(dirname, "myth://", 7) != 0)
427  {
428  DIR *dir = opendir(dirname);
429  if (dir) {
430  m_dirWrapperLock.lockForWrite();
431  id = getNextDirID();
432  m_localdirs[id] = dir;
433  m_dirnames[id] = dirname;
434  m_dirWrapperLock.unlock();
435  }
436  }
437  else
438  {
439  QStringList list;
440  QUrl qurl(dirname);
441  QString storageGroup = qurl.userName();
442 
443  list.clear();
444 
445  if (storageGroup.isEmpty())
446  storageGroup = "Default";
447 
448  list << "QUERY_SG_GETFILELIST";
449  list << qurl.host();
450  list << storageGroup;
451 
452  QString path = qurl.path();
453  if (!qurl.fragment().isEmpty())
454  path += "#" + qurl.fragment();
455 
456  list << path;
457  list << "1";
458 
459  bool ok = gCoreContext->SendReceiveStringList(list);
460 
461  if ((!ok) ||
462  ((list.size() == 1) && (list[0] == "EMPTY LIST")))
463  list.clear();
464 
465  m_dirWrapperLock.lockForWrite();
466  id = getNextDirID();
467  m_remotedirs[id] = list;
468  m_remotedirPositions[id] = 0;
469  m_dirnames[id] = dirname;
470  m_dirWrapperLock.unlock();
471  }
472 
473  return id;
474 }
475 
476 int mythdir_closedir(int dirID)
477 {
478  int result = -1;
479 
480  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_closedir(%1)").arg(dirID));
481 
482  m_dirWrapperLock.lockForRead();
483  if (m_remotedirs.contains(dirID))
484  {
485  m_remotedirs.remove(dirID);
486  m_remotedirPositions.remove(dirID);
487  result = 0;
488  }
489  else if (m_localdirs.contains(dirID))
490  {
491  result = closedir(m_localdirs[dirID]);
492 
493  if (result == 0)
494  m_localdirs.remove(dirID);
495  }
496  m_dirWrapperLock.unlock();
497 
498  return result;
499 }
500 
501 char *mythdir_readdir(int dirID)
502 {
503  char *result = nullptr;
504 
505  LOG(VB_FILE, LOG_DEBUG, LOC + QString("mythdir_readdir(%1)").arg(dirID));
506 
507  m_dirWrapperLock.lockForRead();
508  if (m_remotedirs.contains(dirID))
509  {
510  int pos = m_remotedirPositions[dirID];
511  if (m_remotedirs[dirID].size() >= (pos+1))
512  {
513  result = strdup(m_remotedirs[dirID][pos].toLocal8Bit().constData());
514  pos++;
515  m_remotedirPositions[dirID] = pos;
516  }
517  }
518  else if (m_localdirs.contains(dirID))
519  {
520  struct dirent *r = nullptr;
521  // glibc deprecated readdir_r in version 2.24,
522  // cppcheck-suppress readdirCalled
523  if ((r = readdir(m_localdirs[dirID])) != nullptr)
524  result = strdup(r->d_name);
525  }
526  m_dirWrapperLock.unlock();
527 
528  return result;
529 }
530 } // extern "C"
531 
533 
534 /* 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:112
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:340
#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:688
Callback(void *object, callback_t callback)
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:468
#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)